diff options
author | Jocelyn Turcotte <jocelyn.turcotte@digia.com> | 2014-08-08 14:30:41 +0200 |
---|---|---|
committer | Jocelyn Turcotte <jocelyn.turcotte@digia.com> | 2014-08-12 13:49:54 +0200 |
commit | ab0a50979b9eb4dfa3320eff7e187e41efedf7a9 (patch) | |
tree | 498dfb8a97ff3361a9f7486863a52bb4e26bb898 /chromium/media/cast | |
parent | 4ce69f7403811819800e7c5ae1318b2647e778d1 (diff) |
Update Chromium to beta version 37.0.2062.68
Change-Id: I188e3b5aff1bec75566014291b654eb19f5bc8ca
Reviewed-by: Andras Becsi <andras.becsi@digia.com>
Diffstat (limited to 'chromium/media/cast')
219 files changed, 18393 insertions, 11331 deletions
diff --git a/chromium/media/cast/DEPS b/chromium/media/cast/DEPS index f84b3fbbf5e..abee2864b31 100644 --- a/chromium/media/cast/DEPS +++ b/chromium/media/cast/DEPS @@ -2,7 +2,7 @@ include_rules = [ "+crypto", "+media", "+net", - "+third_party/webrtc", "+third_party/libyuv", + "+third_party/zlib", "+ui/gfx", ] diff --git a/chromium/media/cast/OWNERS b/chromium/media/cast/OWNERS index 49f41be49c0..f8e61c33ccc 100644 --- a/chromium/media/cast/OWNERS +++ b/chromium/media/cast/OWNERS @@ -1,4 +1,5 @@ hclam@chromium.org hubbe@chromium.org mikhal@chromium.org +miu@chromium.org pwestin@google.com diff --git a/chromium/media/cast/README b/chromium/media/cast/README index eca4cf6a1f9..0930c1e012d 100644 --- a/chromium/media/cast/README +++ b/chromium/media/cast/README @@ -16,7 +16,7 @@ cast/audio_sender/ cast/congestion_control/ Bandwidth estimation and network congestion handling. -cast/net/pacing/ +cast/transport/pacing/ Module for rate limiting data outflow. cast/rtcp/ @@ -28,7 +28,7 @@ cast/rtp_common/ cast/rtp_receiver/ Module for reciving RTP messages. -cast/net/rtp_sender/ +cast/transport/rtp_sender/ Module for sending RTP messages. cast/test/ @@ -56,9 +56,6 @@ third_party/libvpx third_party/opus Provides audio encoder. -third_party/webrtc - Provides audio signal processing. - OWNERS See OWNERS for ownership. diff --git a/chromium/media/cast/audio_receiver/audio_decoder.cc b/chromium/media/cast/audio_receiver/audio_decoder.cc deleted file mode 100644 index a761a5a84de..00000000000 --- a/chromium/media/cast/audio_receiver/audio_decoder.cc +++ /dev/null @@ -1,161 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "base/logging.h" -#include "media/cast/audio_receiver/audio_decoder.h" - -#include "third_party/webrtc/modules/audio_coding/main/interface/audio_coding_module.h" -#include "third_party/webrtc/modules/interface/module_common_types.h" - -namespace media { -namespace cast { - -AudioDecoder::AudioDecoder(scoped_refptr<CastEnvironment> cast_environment, - const AudioReceiverConfig& audio_config, - RtpPayloadFeedback* incoming_payload_feedback) - : cast_environment_(cast_environment), - audio_decoder_(webrtc::AudioCodingModule::Create(0)), - cast_message_builder_(cast_environment->Clock(), - incoming_payload_feedback, &frame_id_map_, audio_config.incoming_ssrc, - true, 0), - have_received_packets_(false), - last_played_out_timestamp_(0) { - audio_decoder_->InitializeReceiver(); - - webrtc::CodecInst receive_codec; - switch (audio_config.codec) { - case kPcm16: - receive_codec.pltype = audio_config.rtp_payload_type; - strncpy(receive_codec.plname, "L16", 4); - receive_codec.plfreq = audio_config.frequency; - receive_codec.pacsize = -1; - receive_codec.channels = audio_config.channels; - receive_codec.rate = -1; - break; - case kOpus: - receive_codec.pltype = audio_config.rtp_payload_type; - strncpy(receive_codec.plname, "opus", 5); - receive_codec.plfreq = audio_config.frequency; - receive_codec.pacsize = -1; - receive_codec.channels = audio_config.channels; - receive_codec.rate = -1; - break; - case kExternalAudio: - NOTREACHED() << "Codec must be specified for audio decoder"; - break; - } - if (audio_decoder_->RegisterReceiveCodec(receive_codec) != 0) { - NOTREACHED() << "Failed to register receive codec"; - } - - audio_decoder_->SetMaximumPlayoutDelay(audio_config.rtp_max_delay_ms); - audio_decoder_->SetPlayoutMode(webrtc::streaming); -} - -AudioDecoder::~AudioDecoder() {} - -bool AudioDecoder::GetRawAudioFrame(int number_of_10ms_blocks, - int desired_frequency, - PcmAudioFrame* audio_frame, - uint32* rtp_timestamp) { - DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::AUDIO_DECODER)); - // We don't care about the race case where a packet arrives at the same time - // as this function in called. The data will be there the next time this - // function is called. - lock_.Acquire(); - // Get a local copy under lock. - bool have_received_packets = have_received_packets_; - lock_.Release(); - - if (!have_received_packets) return false; - - audio_frame->samples.clear(); - - for (int i = 0; i < number_of_10ms_blocks; ++i) { - webrtc::AudioFrame webrtc_audio_frame; - if (0 != audio_decoder_->PlayoutData10Ms(desired_frequency, - &webrtc_audio_frame)) { - return false; - } - if (webrtc_audio_frame.speech_type_ == webrtc::AudioFrame::kPLCCNG || - webrtc_audio_frame.speech_type_ == webrtc::AudioFrame::kUndefined) { - // We are only interested in real decoded audio. - return false; - } - audio_frame->frequency = webrtc_audio_frame.sample_rate_hz_; - audio_frame->channels = webrtc_audio_frame.num_channels_; - - if (i == 0) { - // Use the timestamp from the first 10ms block. - if (0 != audio_decoder_->PlayoutTimestamp(rtp_timestamp)) { - return false; - } - lock_.Acquire(); - last_played_out_timestamp_ = *rtp_timestamp; - lock_.Release(); - } - int samples_per_10ms = webrtc_audio_frame.samples_per_channel_; - - audio_frame->samples.insert( - audio_frame->samples.end(), - &webrtc_audio_frame.data_[0], - &webrtc_audio_frame.data_[samples_per_10ms * audio_frame->channels]); - } - return true; -} - -void AudioDecoder::IncomingParsedRtpPacket(const uint8* payload_data, - size_t payload_size, - const RtpCastHeader& rtp_header) { - DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); - DCHECK_LE(payload_size, kIpPacketSize); - audio_decoder_->IncomingPacket(payload_data, static_cast<int32>(payload_size), - rtp_header.webrtc); - lock_.Acquire(); - have_received_packets_ = true; - uint32 last_played_out_timestamp = last_played_out_timestamp_; - lock_.Release(); - - bool complete = false; - if (!frame_id_map_.InsertPacket(rtp_header, &complete)) return; - if (!complete) return; - - cast_message_builder_.CompleteFrameReceived(rtp_header.frame_id, - rtp_header.is_key_frame); - - frame_id_rtp_timestamp_map_[rtp_header.frame_id] = - rtp_header.webrtc.header.timestamp; - - if (last_played_out_timestamp == 0) return; // Nothing is played out yet. - - uint32 latest_frame_id_to_remove = 0; - bool frame_to_remove = false; - - FrameIdRtpTimestampMap::iterator it = frame_id_rtp_timestamp_map_.begin(); - while (it != frame_id_rtp_timestamp_map_.end()) { - if (IsNewerRtpTimestamp(it->second, last_played_out_timestamp)) { - break; - } - frame_to_remove = true; - latest_frame_id_to_remove = it->first; - frame_id_rtp_timestamp_map_.erase(it); - it = frame_id_rtp_timestamp_map_.begin(); - } - if (!frame_to_remove) return; - - frame_id_map_.RemoveOldFrames(latest_frame_id_to_remove); -} - -bool AudioDecoder::TimeToSendNextCastMessage(base::TimeTicks* time_to_send) { - DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); - return cast_message_builder_.TimeToSendNextCastMessage(time_to_send); -} - -void AudioDecoder::SendCastMessage() { - DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); - cast_message_builder_.UpdateCastMessage(); -} - -} // namespace cast -} // namespace media diff --git a/chromium/media/cast/audio_receiver/audio_decoder.h b/chromium/media/cast/audio_receiver/audio_decoder.h deleted file mode 100644 index 8a77d79d070..00000000000 --- a/chromium/media/cast/audio_receiver/audio_decoder.h +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef MEDIA_CAST_AUDIO_RECEIVER_AUDIO_DECODER_H_ -#define MEDIA_CAST_AUDIO_RECEIVER_AUDIO_DECODER_H_ - -#include "base/callback.h" -#include "base/synchronization/lock.h" -#include "media/cast/cast_config.h" -#include "media/cast/cast_environment.h" -#include "media/cast/framer/cast_message_builder.h" -#include "media/cast/framer/frame_id_map.h" -#include "media/cast/rtp_receiver/rtp_receiver_defines.h" - -namespace webrtc { -class AudioCodingModule; -} - -namespace media { -namespace cast { - -typedef std::map<uint32, uint32> FrameIdRtpTimestampMap; - -// Thread safe class. -class AudioDecoder { - public: - AudioDecoder(scoped_refptr<CastEnvironment> cast_environment, - const AudioReceiverConfig& audio_config, - RtpPayloadFeedback* incoming_payload_feedback); - virtual ~AudioDecoder(); - - // Extract a raw audio frame from the decoder. - // Set the number of desired 10ms blocks and frequency. - // Should be called from the cast audio decoder thread; however that is not - // required. - bool GetRawAudioFrame(int number_of_10ms_blocks, - int desired_frequency, - PcmAudioFrame* audio_frame, - uint32* rtp_timestamp); - - // Insert an RTP packet to the decoder. - // Should be called from the main cast thread; however that is not required. - void IncomingParsedRtpPacket(const uint8* payload_data, - size_t payload_size, - const RtpCastHeader& rtp_header); - - bool TimeToSendNextCastMessage(base::TimeTicks* time_to_send); - void SendCastMessage(); - - private: - scoped_refptr<CastEnvironment> cast_environment_; - - // The webrtc AudioCodingModule is threadsafe. - scoped_ptr<webrtc::AudioCodingModule> audio_decoder_; - - FrameIdMap frame_id_map_; - CastMessageBuilder cast_message_builder_; - - base::Lock lock_; - bool have_received_packets_; - FrameIdRtpTimestampMap frame_id_rtp_timestamp_map_; - uint32 last_played_out_timestamp_; - - DISALLOW_COPY_AND_ASSIGN(AudioDecoder); -}; - -} // namespace cast -} // namespace media - -#endif // MEDIA_CAST_AUDIO_RECEIVER_AUDIO_DECODER_H_ diff --git a/chromium/media/cast/audio_receiver/audio_decoder_unittest.cc b/chromium/media/cast/audio_receiver/audio_decoder_unittest.cc deleted file mode 100644 index 04df4728bd9..00000000000 --- a/chromium/media/cast/audio_receiver/audio_decoder_unittest.cc +++ /dev/null @@ -1,220 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "base/test/simple_test_tick_clock.h" -#include "media/cast/audio_receiver/audio_decoder.h" -#include "media/cast/cast_environment.h" -#include "media/cast/test/fake_task_runner.h" -#include "testing/gmock/include/gmock/gmock.h" - -namespace media { -namespace cast { - -namespace { -class TestRtpPayloadFeedback : public RtpPayloadFeedback { - public: - TestRtpPayloadFeedback() {} - virtual ~TestRtpPayloadFeedback() {} - - virtual void CastFeedback(const RtcpCastMessage& cast_feedback) OVERRIDE { - EXPECT_EQ(1u, cast_feedback.ack_frame_id_); - EXPECT_EQ(0u, cast_feedback.missing_frames_and_packets_.size()); - } -}; -} // namespace. - -class AudioDecoderTest : public ::testing::Test { - protected: - AudioDecoderTest() { - testing_clock_.Advance(base::TimeDelta::FromMilliseconds(1234)); - task_runner_ = new test::FakeTaskRunner(&testing_clock_); - cast_environment_ = new CastEnvironment(&testing_clock_, task_runner_, - task_runner_, task_runner_, task_runner_, task_runner_, - GetDefaultCastLoggingConfig()); - } - virtual ~AudioDecoderTest() {} - - void Configure(const AudioReceiverConfig& audio_config) { - audio_decoder_.reset( - new AudioDecoder(cast_environment_, audio_config, &cast_feedback_)); - } - - TestRtpPayloadFeedback cast_feedback_; - base::SimpleTestTickClock testing_clock_; - scoped_refptr<test::FakeTaskRunner> task_runner_; - scoped_refptr<CastEnvironment> cast_environment_; - scoped_ptr<AudioDecoder> audio_decoder_; -}; - -TEST_F(AudioDecoderTest, Pcm16MonoNoResampleOnePacket) { - AudioReceiverConfig audio_config; - audio_config.rtp_payload_type = 127; - audio_config.frequency = 16000; - audio_config.channels = 1; - audio_config.codec = kPcm16; - audio_config.use_external_decoder = false; - Configure(audio_config); - - RtpCastHeader rtp_header; - rtp_header.webrtc.header.payloadType = 127; - rtp_header.webrtc.header.sequenceNumber = 1234; - rtp_header.webrtc.header.timestamp = 0x87654321; - rtp_header.webrtc.header.ssrc = 0x12345678; - rtp_header.webrtc.header.paddingLength = 0; - rtp_header.webrtc.header.headerLength = 12; - rtp_header.webrtc.type.Audio.channel = 1; - rtp_header.webrtc.type.Audio.isCNG = false; - - std::vector<int16> payload(640, 0x1234); - int number_of_10ms_blocks = 4; - int desired_frequency = 16000; - PcmAudioFrame audio_frame; - uint32 rtp_timestamp; - - EXPECT_FALSE(audio_decoder_->GetRawAudioFrame(number_of_10ms_blocks, - desired_frequency, - &audio_frame, - &rtp_timestamp)); - - uint8* payload_data = reinterpret_cast<uint8*>(&payload[0]); - size_t payload_size = payload.size() * sizeof(int16); - - audio_decoder_->IncomingParsedRtpPacket(payload_data, - payload_size, rtp_header); - - EXPECT_TRUE(audio_decoder_->GetRawAudioFrame(number_of_10ms_blocks, - desired_frequency, - &audio_frame, - &rtp_timestamp)); - EXPECT_EQ(1, audio_frame.channels); - EXPECT_EQ(16000, audio_frame.frequency); - EXPECT_EQ(640ul, audio_frame.samples.size()); - // First 10 samples per channel are 0 from NetEq. - for (size_t i = 10; i < audio_frame.samples.size(); ++i) { - EXPECT_EQ(0x3412, audio_frame.samples[i]); - } -} - -TEST_F(AudioDecoderTest, Pcm16StereoNoResampleTwoPackets) { - AudioReceiverConfig audio_config; - audio_config.rtp_payload_type = 127; - audio_config.frequency = 16000; - audio_config.channels = 2; - audio_config.codec = kPcm16; - audio_config.use_external_decoder = false; - Configure(audio_config); - - RtpCastHeader rtp_header; - rtp_header.frame_id = 0; - rtp_header.webrtc.header.payloadType = 127; - rtp_header.webrtc.header.sequenceNumber = 1234; - rtp_header.webrtc.header.timestamp = 0x87654321; - rtp_header.webrtc.header.ssrc = 0x12345678; - rtp_header.webrtc.header.paddingLength = 0; - rtp_header.webrtc.header.headerLength = 12; - - rtp_header.webrtc.type.Audio.isCNG = false; - rtp_header.webrtc.type.Audio.channel = 2; - - std::vector<int16> payload(640, 0x1234); - - uint8* payload_data = reinterpret_cast<uint8*>(&payload[0]); - size_t payload_size = payload.size() * sizeof(int16); - - audio_decoder_->IncomingParsedRtpPacket(payload_data, - payload_size, rtp_header); - - int number_of_10ms_blocks = 2; - int desired_frequency = 16000; - PcmAudioFrame audio_frame; - uint32 rtp_timestamp; - - EXPECT_TRUE(audio_decoder_->GetRawAudioFrame(number_of_10ms_blocks, - desired_frequency, - &audio_frame, - &rtp_timestamp)); - EXPECT_EQ(2, audio_frame.channels); - EXPECT_EQ(16000, audio_frame.frequency); - EXPECT_EQ(640ul, audio_frame.samples.size()); - // First 10 samples per channel are 0 from NetEq. - for (size_t i = 10 * audio_config.channels; i < audio_frame.samples.size(); - ++i) { - EXPECT_EQ(0x3412, audio_frame.samples[i]); - } - - rtp_header.frame_id++; - rtp_header.webrtc.header.sequenceNumber++; - rtp_header.webrtc.header.timestamp += (audio_config.frequency / 100) * 2 * 2; - - audio_decoder_->IncomingParsedRtpPacket(payload_data, - payload_size, rtp_header); - - EXPECT_TRUE(audio_decoder_->GetRawAudioFrame(number_of_10ms_blocks, - desired_frequency, - &audio_frame, - &rtp_timestamp)); - EXPECT_EQ(2, audio_frame.channels); - EXPECT_EQ(16000, audio_frame.frequency); - EXPECT_EQ(640ul, audio_frame.samples.size()); - for (size_t i = 0; i < audio_frame.samples.size(); ++i) { - EXPECT_NEAR(0x3412, audio_frame.samples[i], 1000); - } - // Test cast callback. - audio_decoder_->SendCastMessage(); - testing_clock_.Advance(base::TimeDelta::FromMilliseconds(33)); - audio_decoder_->SendCastMessage(); -} - -TEST_F(AudioDecoderTest, Pcm16Resample) { - AudioReceiverConfig audio_config; - audio_config.rtp_payload_type = 127; - audio_config.frequency = 16000; - audio_config.channels = 2; - audio_config.codec = kPcm16; - audio_config.use_external_decoder = false; - Configure(audio_config); - - RtpCastHeader rtp_header; - rtp_header.webrtc.header.payloadType = 127; - rtp_header.webrtc.header.sequenceNumber = 1234; - rtp_header.webrtc.header.timestamp = 0x87654321; - rtp_header.webrtc.header.ssrc = 0x12345678; - rtp_header.webrtc.header.paddingLength = 0; - rtp_header.webrtc.header.headerLength = 12; - - rtp_header.webrtc.type.Audio.isCNG = false; - rtp_header.webrtc.type.Audio.channel = 2; - - std::vector<int16> payload(640, 0x1234); - - uint8* payload_data = reinterpret_cast<uint8*>(&payload[0]); - size_t payload_size = payload.size() * sizeof(int16); - - audio_decoder_->IncomingParsedRtpPacket(payload_data, - payload_size, rtp_header); - - int number_of_10ms_blocks = 2; - int desired_frequency = 48000; - PcmAudioFrame audio_frame; - uint32 rtp_timestamp; - - EXPECT_TRUE(audio_decoder_->GetRawAudioFrame(number_of_10ms_blocks, - desired_frequency, - &audio_frame, - &rtp_timestamp)); - - EXPECT_EQ(2, audio_frame.channels); - EXPECT_EQ(48000, audio_frame.frequency); - EXPECT_EQ(1920ul, audio_frame.samples.size()); // Upsampled to 48 KHz. - int count = 0; - // Resampling makes the variance worse. - for (size_t i = 100 * audio_config.channels; i < audio_frame.samples.size(); - ++i) { - EXPECT_NEAR(0x3412, audio_frame.samples[i], 400); - if (0x3412 == audio_frame.samples[i]) count++; - } -} - -} // namespace cast -} // namespace media diff --git a/chromium/media/cast/audio_receiver/audio_receiver.cc b/chromium/media/cast/audio_receiver/audio_receiver.cc deleted file mode 100644 index 5aad22f628c..00000000000 --- a/chromium/media/cast/audio_receiver/audio_receiver.cc +++ /dev/null @@ -1,490 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "media/cast/audio_receiver/audio_receiver.h" - -#include "base/bind.h" -#include "base/logging.h" -#include "base/message_loop/message_loop.h" -#include "crypto/encryptor.h" -#include "crypto/symmetric_key.h" -#include "media/cast/audio_receiver/audio_decoder.h" -#include "media/cast/framer/framer.h" -#include "media/cast/rtcp/rtcp.h" -#include "media/cast/rtp_receiver/rtp_receiver.h" - -// Max time we wait until an audio frame is due to be played out is released. -static const int64 kMaxAudioFrameWaitMs = 20; -static const int64 kMinSchedulingDelayMs = 1; - -namespace media { -namespace cast { - -DecodedAudioCallbackData::DecodedAudioCallbackData() - : number_of_10ms_blocks(0), - desired_frequency(0), - callback() {} - -DecodedAudioCallbackData::~DecodedAudioCallbackData() {} - -// Local implementation of RtpData (defined in rtp_rtcp_defines.h). -// Used to pass payload data into the audio receiver. -class LocalRtpAudioData : public RtpData { - public: - explicit LocalRtpAudioData(AudioReceiver* audio_receiver) - : audio_receiver_(audio_receiver) {} - - virtual void OnReceivedPayloadData( - const uint8* payload_data, - size_t payload_size, - const RtpCastHeader* rtp_header) OVERRIDE { - audio_receiver_->IncomingParsedRtpPacket(payload_data, payload_size, - *rtp_header); - } - - private: - AudioReceiver* audio_receiver_; -}; - -// Local implementation of RtpPayloadFeedback (defined in rtp_defines.h) -// Used to convey cast-specific feedback from receiver to sender. -class LocalRtpAudioFeedback : public RtpPayloadFeedback { - public: - explicit LocalRtpAudioFeedback(AudioReceiver* audio_receiver) - : audio_receiver_(audio_receiver) { - } - - virtual void CastFeedback(const RtcpCastMessage& cast_message) OVERRIDE { - audio_receiver_->CastFeedback(cast_message); - } - - private: - AudioReceiver* audio_receiver_; -}; - -class LocalRtpReceiverStatistics : public RtpReceiverStatistics { - public: - explicit LocalRtpReceiverStatistics(RtpReceiver* rtp_receiver) - : rtp_receiver_(rtp_receiver) { - } - - virtual void GetStatistics(uint8* fraction_lost, - uint32* cumulative_lost, // 24 bits valid. - uint32* extended_high_sequence_number, - uint32* jitter) OVERRIDE { - rtp_receiver_->GetStatistics(fraction_lost, - cumulative_lost, - extended_high_sequence_number, - jitter); - } - - private: - RtpReceiver* rtp_receiver_; -}; - -AudioReceiver::AudioReceiver(scoped_refptr<CastEnvironment> cast_environment, - const AudioReceiverConfig& audio_config, - PacedPacketSender* const packet_sender) - : cast_environment_(cast_environment), - codec_(audio_config.codec), - frequency_(audio_config.frequency), - audio_buffer_(), - audio_decoder_(), - time_offset_(), - weak_factory_(this) { - target_delay_delta_ = - base::TimeDelta::FromMilliseconds(audio_config.rtp_max_delay_ms); - incoming_payload_callback_.reset(new LocalRtpAudioData(this)); - incoming_payload_feedback_.reset(new LocalRtpAudioFeedback(this)); - if (audio_config.use_external_decoder) { - audio_buffer_.reset(new Framer(cast_environment->Clock(), - incoming_payload_feedback_.get(), - audio_config.incoming_ssrc, - true, - 0)); - } else { - audio_decoder_.reset(new AudioDecoder(cast_environment, - audio_config, - incoming_payload_feedback_.get())); - } - if (audio_config.aes_iv_mask.size() == kAesKeySize && - audio_config.aes_key.size() == kAesKeySize) { - iv_mask_ = audio_config.aes_iv_mask; - crypto::SymmetricKey* key = crypto::SymmetricKey::Import( - crypto::SymmetricKey::AES, audio_config.aes_key); - decryptor_.reset(new crypto::Encryptor()); - decryptor_->Init(key, crypto::Encryptor::CTR, std::string()); - } else if (audio_config.aes_iv_mask.size() != 0 || - audio_config.aes_key.size() != 0) { - DCHECK(false) << "Invalid crypto configuration"; - } - - rtp_receiver_.reset(new RtpReceiver(cast_environment->Clock(), - &audio_config, - NULL, - incoming_payload_callback_.get())); - rtp_audio_receiver_statistics_.reset( - new LocalRtpReceiverStatistics(rtp_receiver_.get())); - base::TimeDelta rtcp_interval_delta = - base::TimeDelta::FromMilliseconds(audio_config.rtcp_interval); - rtcp_.reset(new Rtcp(cast_environment, - NULL, - packet_sender, - NULL, - rtp_audio_receiver_statistics_.get(), - audio_config.rtcp_mode, - rtcp_interval_delta, - audio_config.feedback_ssrc, - audio_config.incoming_ssrc, - audio_config.rtcp_c_name)); -} - -AudioReceiver::~AudioReceiver() {} - -void AudioReceiver::InitializeTimers() { - DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); - ScheduleNextRtcpReport(); - ScheduleNextCastMessage(); -} - -void AudioReceiver::IncomingParsedRtpPacket(const uint8* payload_data, - size_t payload_size, - const RtpCastHeader& rtp_header) { - DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); - cast_environment_->Logging()->InsertPacketEvent(kPacketReceived, - rtp_header.webrtc.header.timestamp, rtp_header.frame_id, - rtp_header.packet_id, rtp_header.max_packet_id, payload_size); - - // TODO(pwestin): update this as video to refresh over time. - DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); - if (time_first_incoming_packet_.is_null()) { - InitializeTimers(); - first_incoming_rtp_timestamp_ = rtp_header.webrtc.header.timestamp; - time_first_incoming_packet_ = cast_environment_->Clock()->NowTicks(); - } - - if (audio_decoder_) { - DCHECK(!audio_buffer_) << "Invalid internal state"; - std::string plaintext(reinterpret_cast<const char*>(payload_data), - payload_size); - if (decryptor_) { - plaintext.clear(); - if (!decryptor_->SetCounter(GetAesNonce(rtp_header.frame_id, iv_mask_))) { - NOTREACHED() << "Failed to set counter"; - return; - } - if (!decryptor_->Decrypt(base::StringPiece(reinterpret_cast<const char*>( - payload_data), payload_size), &plaintext)) { - VLOG(0) << "Decryption error"; - return; - } - } - audio_decoder_->IncomingParsedRtpPacket( - reinterpret_cast<const uint8*>(plaintext.data()), plaintext.size(), - rtp_header); - if (!queued_decoded_callbacks_.empty()) { - DecodedAudioCallbackData decoded_data = queued_decoded_callbacks_.front(); - queued_decoded_callbacks_.pop_front(); - cast_environment_->PostTask(CastEnvironment::AUDIO_DECODER, FROM_HERE, - base::Bind(&AudioReceiver::DecodeAudioFrameThread, - base::Unretained(this), - decoded_data.number_of_10ms_blocks, - decoded_data.desired_frequency, - decoded_data.callback)); - } - return; - } - - DCHECK(audio_buffer_) << "Invalid internal state"; - DCHECK(!audio_decoder_) << "Invalid internal state"; - - bool complete = audio_buffer_->InsertPacket(payload_data, payload_size, - rtp_header); - if (!complete) return; // Audio frame not complete; wait for more packets. - if (queued_encoded_callbacks_.empty()) return; - AudioFrameEncodedCallback callback = queued_encoded_callbacks_.front(); - queued_encoded_callbacks_.pop_front(); - cast_environment_->PostTask(CastEnvironment::MAIN, FROM_HERE, - base::Bind(&AudioReceiver::GetEncodedAudioFrame, - weak_factory_.GetWeakPtr(), callback)); -} - -void AudioReceiver::GetRawAudioFrame(int number_of_10ms_blocks, - int desired_frequency, const AudioFrameDecodedCallback& callback) { - DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); - DCHECK(audio_decoder_) << "Invalid function call in this configuration"; - // TODO(pwestin): we can skip this function by posting direct to the decoder. - cast_environment_->PostTask(CastEnvironment::AUDIO_DECODER, FROM_HERE, - base::Bind(&AudioReceiver::DecodeAudioFrameThread, - base::Unretained(this), - number_of_10ms_blocks, - desired_frequency, - callback)); -} - -void AudioReceiver::DecodeAudioFrameThread( - int number_of_10ms_blocks, - int desired_frequency, - const AudioFrameDecodedCallback callback) { - DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::AUDIO_DECODER)); - // TODO(mikhal): Allow the application to allocate this memory. - scoped_ptr<PcmAudioFrame> audio_frame(new PcmAudioFrame()); - - uint32 rtp_timestamp = 0; - if (!audio_decoder_->GetRawAudioFrame(number_of_10ms_blocks, - desired_frequency, - audio_frame.get(), - &rtp_timestamp)) { - DecodedAudioCallbackData callback_data; - callback_data.number_of_10ms_blocks = number_of_10ms_blocks; - callback_data.desired_frequency = desired_frequency; - callback_data.callback = callback; - queued_decoded_callbacks_.push_back(callback_data); - return; - } - base::TimeTicks now = cast_environment_->Clock()->NowTicks(); - - cast_environment_->PostTask(CastEnvironment::MAIN, FROM_HERE, - base::Bind(&AudioReceiver::ReturnDecodedFrameWithPlayoutDelay, - base::Unretained(this), base::Passed(&audio_frame), rtp_timestamp, - callback)); -} - -void AudioReceiver::ReturnDecodedFrameWithPlayoutDelay( - scoped_ptr<PcmAudioFrame> audio_frame, uint32 rtp_timestamp, - const AudioFrameDecodedCallback callback) { - DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); - cast_environment_->Logging()->InsertFrameEvent(kAudioFrameDecoded, - rtp_timestamp, kFrameIdUnknown); - - base::TimeTicks now = cast_environment_->Clock()->NowTicks(); - base::TimeTicks playout_time = GetPlayoutTime(now, rtp_timestamp); - - cast_environment_->Logging()->InsertFrameEventWithDelay(kAudioPlayoutDelay, - rtp_timestamp, kFrameIdUnknown, playout_time - now); - - // Frame is ready - Send back to the caller. - cast_environment_->PostTask(CastEnvironment::MAIN, FROM_HERE, - base::Bind(callback, base::Passed(&audio_frame), playout_time)); -} - -void AudioReceiver::PlayoutTimeout() { - DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); - DCHECK(audio_buffer_) << "Invalid function call in this configuration"; - if (queued_encoded_callbacks_.empty()) { - // Already released by incoming packet. - return; - } - uint32 rtp_timestamp = 0; - bool next_frame = false; - scoped_ptr<EncodedAudioFrame> encoded_frame(new EncodedAudioFrame()); - - if (!audio_buffer_->GetEncodedAudioFrame(encoded_frame.get(), - &rtp_timestamp, &next_frame)) { - // We have no audio frames. Wait for new packet(s). - // Since the application can post multiple AudioFrameEncodedCallback and - // we only check the next frame to play out we might have multiple timeout - // events firing after each other; however this should be a rare event. - VLOG(1) << "Failed to retrieved a complete frame at this point in time"; - return; - } - - if (decryptor_ && !DecryptAudioFrame(&encoded_frame)) { - // Logging already done. - return; - } - - if (PostEncodedAudioFrame(queued_encoded_callbacks_.front(), rtp_timestamp, - next_frame, &encoded_frame)) { - // Call succeed remove callback from list. - queued_encoded_callbacks_.pop_front(); - } -} - -void AudioReceiver::GetEncodedAudioFrame( - const AudioFrameEncodedCallback& callback) { - DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); - DCHECK(audio_buffer_) << "Invalid function call in this configuration"; - - uint32 rtp_timestamp = 0; - bool next_frame = false; - scoped_ptr<EncodedAudioFrame> encoded_frame(new EncodedAudioFrame()); - - if (!audio_buffer_->GetEncodedAudioFrame(encoded_frame.get(), - &rtp_timestamp, &next_frame)) { - // We have no audio frames. Wait for new packet(s). - VLOG(1) << "Wait for more audio packets in frame"; - queued_encoded_callbacks_.push_back(callback); - return; - } - if (decryptor_ && !DecryptAudioFrame(&encoded_frame)) { - // Logging already done. - queued_encoded_callbacks_.push_back(callback); - return; - } - if (!PostEncodedAudioFrame(callback, rtp_timestamp, next_frame, - &encoded_frame)) { - // We have an audio frame; however we are missing packets and we have time - // to wait for new packet(s). - queued_encoded_callbacks_.push_back(callback); - } -} - -bool AudioReceiver::PostEncodedAudioFrame( - const AudioFrameEncodedCallback& callback, - uint32 rtp_timestamp, - bool next_frame, - scoped_ptr<EncodedAudioFrame>* encoded_frame) { - DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); - DCHECK(audio_buffer_) << "Invalid function call in this configuration"; - - base::TimeTicks now = cast_environment_->Clock()->NowTicks(); - base::TimeTicks playout_time = GetPlayoutTime(now, rtp_timestamp); - base::TimeDelta time_until_playout = playout_time - now; - base::TimeDelta min_wait_delta = - base::TimeDelta::FromMilliseconds(kMaxAudioFrameWaitMs); - - if (!next_frame && (time_until_playout > min_wait_delta)) { - base::TimeDelta time_until_release = time_until_playout - min_wait_delta; - cast_environment_->PostDelayedTask(CastEnvironment::MAIN, FROM_HERE, - base::Bind(&AudioReceiver::PlayoutTimeout, weak_factory_.GetWeakPtr()), - time_until_release); - VLOG(1) << "Wait until time to playout:" - << time_until_release.InMilliseconds(); - return false; - } - (*encoded_frame)->codec = codec_; - audio_buffer_->ReleaseFrame((*encoded_frame)->frame_id); - - cast_environment_->PostTask(CastEnvironment::MAIN, FROM_HERE, - base::Bind(callback, base::Passed(encoded_frame), playout_time)); - return true; -} - -void AudioReceiver::IncomingPacket(const uint8* packet, size_t length, - const base::Closure callback) { - DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); - bool rtcp_packet = Rtcp::IsRtcpPacket(packet, length); - if (!rtcp_packet) { - rtp_receiver_->ReceivedPacket(packet, length); - } else { - rtcp_->IncomingRtcpPacket(packet, length); - } - cast_environment_->PostTask(CastEnvironment::MAIN, FROM_HERE, callback); -} - -void AudioReceiver::CastFeedback(const RtcpCastMessage& cast_message) { - // TODO(pwestin): add logging. - rtcp_->SendRtcpFromRtpReceiver(&cast_message, NULL); -} - -base::TimeTicks AudioReceiver::GetPlayoutTime(base::TimeTicks now, - uint32 rtp_timestamp) { - DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); - // Senders time in ms when this frame was recorded. - // Note: the senders clock and our local clock might not be synced. - base::TimeTicks rtp_timestamp_in_ticks; - if (time_offset_ == base::TimeDelta()) { - if (rtcp_->RtpTimestampInSenderTime(frequency_, - first_incoming_rtp_timestamp_, - &rtp_timestamp_in_ticks)) { - time_offset_ = time_first_incoming_packet_ - rtp_timestamp_in_ticks; - } else { - // We have not received any RTCP to sync the stream play it out as soon as - // possible. - uint32 rtp_timestamp_diff = rtp_timestamp - first_incoming_rtp_timestamp_; - - int frequency_khz = frequency_ / 1000; - base::TimeDelta rtp_time_diff_delta = - base::TimeDelta::FromMilliseconds(rtp_timestamp_diff / frequency_khz); - base::TimeDelta time_diff_delta = now - time_first_incoming_packet_; - - return now + std::max(rtp_time_diff_delta - time_diff_delta, - base::TimeDelta()); - } - } - // This can fail if we have not received any RTCP packets in a long time. - return rtcp_->RtpTimestampInSenderTime(frequency_, rtp_timestamp, - &rtp_timestamp_in_ticks) ? - rtp_timestamp_in_ticks + time_offset_ + target_delay_delta_ : - now; -} - -bool AudioReceiver::DecryptAudioFrame( - scoped_ptr<EncodedAudioFrame>* audio_frame) { - DCHECK(decryptor_) << "Invalid state"; - - if (!decryptor_->SetCounter(GetAesNonce((*audio_frame)->frame_id, - iv_mask_))) { - NOTREACHED() << "Failed to set counter"; - return false; - } - std::string decrypted_audio_data; - if (!decryptor_->Decrypt((*audio_frame)->data, &decrypted_audio_data)) { - VLOG(0) << "Decryption error"; - // Give up on this frame, release it from jitter buffer. - audio_buffer_->ReleaseFrame((*audio_frame)->frame_id); - return false; - } - (*audio_frame)->data.swap(decrypted_audio_data); - return true; -} - -void AudioReceiver::ScheduleNextRtcpReport() { - DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); - base::TimeDelta time_to_send = rtcp_->TimeToSendNextRtcpReport() - - cast_environment_->Clock()->NowTicks(); - - time_to_send = std::max(time_to_send, - base::TimeDelta::FromMilliseconds(kMinSchedulingDelayMs)); - - cast_environment_->PostDelayedTask(CastEnvironment::MAIN, FROM_HERE, - base::Bind(&AudioReceiver::SendNextRtcpReport, - weak_factory_.GetWeakPtr()), time_to_send); -} - -void AudioReceiver::SendNextRtcpReport() { - DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); - // TODO(pwestin): add logging. - rtcp_->SendRtcpFromRtpReceiver(NULL, NULL); - ScheduleNextRtcpReport(); -} - -// Cast messages should be sent within a maximum interval. Schedule a call -// if not triggered elsewhere, e.g. by the cast message_builder. -void AudioReceiver::ScheduleNextCastMessage() { - DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); - base::TimeTicks send_time; - if (audio_buffer_) { - audio_buffer_->TimeToSendNextCastMessage(&send_time); - } else if (audio_decoder_) { - audio_decoder_->TimeToSendNextCastMessage(&send_time); - } else { - NOTREACHED(); - } - base::TimeDelta time_to_send = send_time - - cast_environment_->Clock()->NowTicks(); - time_to_send = std::max(time_to_send, - base::TimeDelta::FromMilliseconds(kMinSchedulingDelayMs)); - cast_environment_->PostDelayedTask(CastEnvironment::MAIN, FROM_HERE, - base::Bind(&AudioReceiver::SendNextCastMessage, - weak_factory_.GetWeakPtr()), time_to_send); -} - -void AudioReceiver::SendNextCastMessage() { - DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); - - if (audio_buffer_) { - // Will only send a message if it is time. - audio_buffer_->SendCastMessage(); - } - if (audio_decoder_) { - // Will only send a message if it is time. - audio_decoder_->SendCastMessage(); - } - ScheduleNextCastMessage(); -} - -} // namespace cast -} // namespace media diff --git a/chromium/media/cast/audio_receiver/audio_receiver.gypi b/chromium/media/cast/audio_receiver/audio_receiver.gypi deleted file mode 100644 index a851612f721..00000000000 --- a/chromium/media/cast/audio_receiver/audio_receiver.gypi +++ /dev/null @@ -1,29 +0,0 @@ -# Copyright 2013 The Chromium Authors. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - -{ - 'targets': [ - { - 'target_name': 'cast_audio_receiver', - 'type': 'static_library', - 'include_dirs': [ - '<(DEPTH)/', - '<(DEPTH)/third_party/', - '<(DEPTH)/third_party/webrtc/', - ], - 'sources': [ - 'audio_decoder.h', - 'audio_decoder.cc', - 'audio_receiver.h', - 'audio_receiver.cc', - ], # source - 'dependencies': [ - '<(DEPTH)/crypto/crypto.gyp:crypto', - '<(DEPTH)/media/cast/rtcp/rtcp.gyp:cast_rtcp', - '<(DEPTH)/media/cast/rtp_receiver/rtp_receiver.gyp:*', - '<(DEPTH)/third_party/webrtc/webrtc.gyp:webrtc', - ], - }, - ], -} diff --git a/chromium/media/cast/audio_receiver/audio_receiver.h b/chromium/media/cast/audio_receiver/audio_receiver.h deleted file mode 100644 index c49e1c15c25..00000000000 --- a/chromium/media/cast/audio_receiver/audio_receiver.h +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef MEDIA_CAST_AUDIO_RECEIVER_AUDIO_RECEIVER_H_ -#define MEDIA_CAST_AUDIO_RECEIVER_AUDIO_RECEIVER_H_ - -#include "base/basictypes.h" -#include "base/callback.h" -#include "base/memory/ref_counted.h" -#include "base/memory/scoped_ptr.h" -#include "base/memory/weak_ptr.h" -#include "base/threading/non_thread_safe.h" -#include "base/time/tick_clock.h" -#include "base/time/time.h" -#include "media/cast/cast_config.h" -#include "media/cast/cast_environment.h" -#include "media/cast/cast_receiver.h" -#include "media/cast/rtcp/rtcp.h" // RtcpCastMessage -#include "media/cast/rtp_receiver/rtp_receiver_defines.h" // RtpCastHeader - -namespace crypto { - class Encryptor; -} - -namespace media { -namespace cast { - -class AudioDecoder; -class Framer; -class LocalRtpAudioData; -class LocalRtpAudioFeedback; -class PacedPacketSender; -class RtpReceiver; -class RtpReceiverStatistics; - -struct DecodedAudioCallbackData { - DecodedAudioCallbackData(); - ~DecodedAudioCallbackData(); - int number_of_10ms_blocks; - int desired_frequency; - AudioFrameDecodedCallback callback; -}; - -// This class is not thread safe. Should only be called from the Main cast -// thread. -class AudioReceiver : public base::NonThreadSafe, - public base::SupportsWeakPtr<AudioReceiver> { - public: - AudioReceiver(scoped_refptr<CastEnvironment> cast_environment, - const AudioReceiverConfig& audio_config, - PacedPacketSender* const packet_sender); - - virtual ~AudioReceiver(); - - // Extract a raw audio frame from the cast receiver. - // Actual decoding will be preformed on a designated audio_decoder thread. - void GetRawAudioFrame(int number_of_10ms_blocks, - int desired_frequency, - const AudioFrameDecodedCallback& callback); - - // Extract an encoded audio frame from the cast receiver. - void GetEncodedAudioFrame(const AudioFrameEncodedCallback& callback); - - // Should only be called from the main cast thread. - void IncomingPacket(const uint8* packet, size_t length, - const base::Closure callback); - - protected: - void IncomingParsedRtpPacket(const uint8* payload_data, - size_t payload_size, - const RtpCastHeader& rtp_header); - private: - friend class LocalRtpAudioData; - friend class LocalRtpAudioFeedback; - - void CastFeedback(const RtcpCastMessage& cast_message); - - // Time to pull out the audio even though we are missing data. - void PlayoutTimeout(); - - bool PostEncodedAudioFrame(const AudioFrameEncodedCallback& callback, - uint32 rtp_timestamp, - bool next_frame, - scoped_ptr<EncodedAudioFrame>* encoded_frame); - - // Actual decoding implementation - should be called under the audio decoder - // thread. - void DecodeAudioFrameThread(int number_of_10ms_blocks, - int desired_frequency, - const AudioFrameDecodedCallback callback); - void ReturnDecodedFrameWithPlayoutDelay( - scoped_ptr<PcmAudioFrame> audio_frame, uint32 rtp_timestamp, - const AudioFrameDecodedCallback callback); - - // Return the playout time based on the current time and rtp timestamp. - base::TimeTicks GetPlayoutTime(base::TimeTicks now, uint32 rtp_timestamp); - - void InitializeTimers(); - - // Decrypts the data within the |audio_frame| and replaces the data with the - // decrypted string. - bool DecryptAudioFrame(scoped_ptr<EncodedAudioFrame>* audio_frame); - - // Schedule the next RTCP report. - void ScheduleNextRtcpReport(); - - // Actually send the next RTCP report. - void SendNextRtcpReport(); - - // Schedule timing for the next cast message. - void ScheduleNextCastMessage(); - - // Actually send the next cast message. - void SendNextCastMessage(); - - scoped_refptr<CastEnvironment> cast_environment_; - base::WeakPtrFactory<AudioReceiver> weak_factory_; - - const AudioCodec codec_; - const int frequency_; - base::TimeDelta target_delay_delta_; - scoped_ptr<Framer> audio_buffer_; - scoped_ptr<AudioDecoder> audio_decoder_; - scoped_ptr<LocalRtpAudioData> incoming_payload_callback_; - scoped_ptr<LocalRtpAudioFeedback> incoming_payload_feedback_; - scoped_ptr<RtpReceiver> rtp_receiver_; - scoped_ptr<Rtcp> rtcp_; - scoped_ptr<RtpReceiverStatistics> rtp_audio_receiver_statistics_; - base::TimeDelta time_offset_; - base::TimeTicks time_first_incoming_packet_; - uint32 first_incoming_rtp_timestamp_; - scoped_ptr<crypto::Encryptor> decryptor_; - std::string iv_mask_; - - std::list<AudioFrameEncodedCallback> queued_encoded_callbacks_; - std::list<DecodedAudioCallbackData> queued_decoded_callbacks_; -}; - -} // namespace cast -} // namespace media - -#endif // MEDIA_CAST_AUDIO_RECEIVER_AUDIO_RECEIVER_H_ diff --git a/chromium/media/cast/audio_receiver/audio_receiver_unittest.cc b/chromium/media/cast/audio_receiver/audio_receiver_unittest.cc deleted file mode 100644 index a10af679925..00000000000 --- a/chromium/media/cast/audio_receiver/audio_receiver_unittest.cc +++ /dev/null @@ -1,217 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "base/bind.h" -#include "base/memory/ref_counted.h" -#include "base/memory/scoped_ptr.h" -#include "base/test/simple_test_tick_clock.h" -#include "media/cast/audio_receiver/audio_receiver.h" -#include "media/cast/cast_defines.h" -#include "media/cast/cast_environment.h" -#include "media/cast/net/pacing/mock_paced_packet_sender.h" -#include "media/cast/rtcp/test_rtcp_packet_builder.h" -#include "media/cast/test/fake_task_runner.h" -#include "testing/gmock/include/gmock/gmock.h" - -namespace media { -namespace cast { - -static const int64 kStartMillisecond = GG_INT64_C(12345678900000); - -namespace { -class TestAudioEncoderCallback : - public base::RefCountedThreadSafe<TestAudioEncoderCallback> { - public: - TestAudioEncoderCallback() - : num_called_(0) {} - - void SetExpectedResult(uint8 expected_frame_id, - const base::TimeTicks& expected_playout_time) { - expected_frame_id_ = expected_frame_id; - expected_playout_time_ = expected_playout_time; - } - - void DeliverEncodedAudioFrame(scoped_ptr<EncodedAudioFrame> audio_frame, - const base::TimeTicks& playout_time) { - EXPECT_EQ(expected_frame_id_, audio_frame->frame_id); - EXPECT_EQ(kPcm16, audio_frame->codec); - EXPECT_EQ(expected_playout_time_, playout_time); - num_called_++; - } - - int number_times_called() const { return num_called_;} - - protected: - virtual ~TestAudioEncoderCallback() {} - - private: - friend class base::RefCountedThreadSafe<TestAudioEncoderCallback>; - - int num_called_; - uint8 expected_frame_id_; - base::TimeTicks expected_playout_time_; -}; -} // namespace - -class PeerAudioReceiver : public AudioReceiver { - public: - PeerAudioReceiver(scoped_refptr<CastEnvironment> cast_environment, - const AudioReceiverConfig& audio_config, - PacedPacketSender* const packet_sender) - : AudioReceiver(cast_environment, audio_config, packet_sender) {} - - using AudioReceiver::IncomingParsedRtpPacket; -}; - -class AudioReceiverTest : public ::testing::Test { - protected: - AudioReceiverTest() { - // Configure the audio receiver to use PCM16. - audio_config_.rtp_payload_type = 127; - audio_config_.frequency = 16000; - audio_config_.channels = 1; - audio_config_.codec = kPcm16; - audio_config_.use_external_decoder = false; - audio_config_.feedback_ssrc = 1234; - testing_clock_.Advance( - base::TimeDelta::FromMilliseconds(kStartMillisecond)); - task_runner_ = new test::FakeTaskRunner(&testing_clock_); - cast_environment_ = new CastEnvironment(&testing_clock_, task_runner_, - task_runner_, task_runner_, task_runner_, task_runner_, - GetDefaultCastLoggingConfig()); - test_audio_encoder_callback_ = new TestAudioEncoderCallback(); - } - - void Configure(bool use_external_decoder) { - audio_config_.use_external_decoder = use_external_decoder; - receiver_.reset(new PeerAudioReceiver(cast_environment_, audio_config_, - &mock_transport_)); - } - - virtual ~AudioReceiverTest() {} - - static void DummyDeletePacket(const uint8* packet) {}; - - virtual void SetUp() { - payload_.assign(kIpPacketSize, 0); - rtp_header_.is_key_frame = true; - rtp_header_.frame_id = 0; - rtp_header_.packet_id = 0; - rtp_header_.max_packet_id = 0; - rtp_header_.is_reference = false; - rtp_header_.reference_frame_id = 0; - rtp_header_.webrtc.header.timestamp = 0; - } - - AudioReceiverConfig audio_config_; - std::vector<uint8> payload_; - RtpCastHeader rtp_header_; - base::SimpleTestTickClock testing_clock_; - MockPacedPacketSender mock_transport_; - scoped_refptr<test::FakeTaskRunner> task_runner_; - scoped_ptr<PeerAudioReceiver> receiver_; - scoped_refptr<CastEnvironment> cast_environment_; - scoped_refptr<TestAudioEncoderCallback> test_audio_encoder_callback_; -}; - -TEST_F(AudioReceiverTest, GetOnePacketEncodedframe) { - Configure(true); - EXPECT_CALL(mock_transport_, SendRtcpPacket(testing::_)).Times(1); - - receiver_->IncomingParsedRtpPacket(payload_.data(), - payload_.size(), rtp_header_); - EncodedAudioFrame audio_frame; - base::TimeTicks playout_time; - test_audio_encoder_callback_->SetExpectedResult(0, testing_clock_.NowTicks()); - - AudioFrameEncodedCallback frame_encoded_callback = - base::Bind(&TestAudioEncoderCallback::DeliverEncodedAudioFrame, - test_audio_encoder_callback_.get()); - - receiver_->GetEncodedAudioFrame(frame_encoded_callback); - task_runner_->RunTasks(); - EXPECT_EQ(1, test_audio_encoder_callback_->number_times_called()); -} - -TEST_F(AudioReceiverTest, MultiplePendingGetCalls) { - Configure(true); - EXPECT_CALL(mock_transport_, SendRtcpPacket(testing::_)).WillRepeatedly( - testing::Return(true)); - - AudioFrameEncodedCallback frame_encoded_callback = - base::Bind(&TestAudioEncoderCallback::DeliverEncodedAudioFrame, - test_audio_encoder_callback_.get()); - - receiver_->GetEncodedAudioFrame(frame_encoded_callback); - - receiver_->IncomingParsedRtpPacket(payload_.data(), payload_.size(), - rtp_header_); - - EncodedAudioFrame audio_frame; - base::TimeTicks playout_time; - test_audio_encoder_callback_->SetExpectedResult(0, testing_clock_.NowTicks()); - - task_runner_->RunTasks(); - EXPECT_EQ(1, test_audio_encoder_callback_->number_times_called()); - - TestRtcpPacketBuilder rtcp_packet; - - uint32 ntp_high; - uint32 ntp_low; - ConvertTimeTicksToNtp(testing_clock_.NowTicks(), &ntp_high, &ntp_low); - rtcp_packet.AddSrWithNtp(audio_config_.feedback_ssrc, ntp_high, ntp_low, - rtp_header_.webrtc.header.timestamp); - - testing_clock_.Advance(base::TimeDelta::FromMilliseconds(20)); - - receiver_->IncomingPacket(rtcp_packet.Packet(), rtcp_packet.Length(), - base::Bind(AudioReceiverTest::DummyDeletePacket, rtcp_packet.Packet())); - - // Make sure that we are not continuous and that the RTP timestamp represent a - // time in the future. - rtp_header_.is_key_frame = false; - rtp_header_.frame_id = 2; - rtp_header_.is_reference = true; - rtp_header_.reference_frame_id = 0; - rtp_header_.webrtc.header.timestamp = 960; - test_audio_encoder_callback_->SetExpectedResult(2, - testing_clock_.NowTicks() + base::TimeDelta::FromMilliseconds(100)); - - receiver_->IncomingParsedRtpPacket(payload_.data(), payload_.size(), - rtp_header_); - receiver_->GetEncodedAudioFrame(frame_encoded_callback); - task_runner_->RunTasks(); - - // Frame 2 should not come out at this point in time. - EXPECT_EQ(1, test_audio_encoder_callback_->number_times_called()); - - // Through on one more pending callback. - receiver_->GetEncodedAudioFrame(frame_encoded_callback); - - testing_clock_.Advance(base::TimeDelta::FromMilliseconds(100)); - - task_runner_->RunTasks(); - EXPECT_EQ(2, test_audio_encoder_callback_->number_times_called()); - - test_audio_encoder_callback_->SetExpectedResult(3, testing_clock_.NowTicks()); - - // Through on one more pending audio frame. - rtp_header_.frame_id = 3; - rtp_header_.is_reference = false; - rtp_header_.reference_frame_id = 0; - rtp_header_.webrtc.header.timestamp = 1280; - receiver_->IncomingParsedRtpPacket(payload_.data(), payload_.size(), - rtp_header_); - - receiver_->GetEncodedAudioFrame(frame_encoded_callback); - task_runner_->RunTasks(); - EXPECT_EQ(3, test_audio_encoder_callback_->number_times_called()); -} - -// TODO(mikhal): Add encoded frames. -TEST_F(AudioReceiverTest, GetRawFrame) { -} - -} // namespace cast -} // namespace media diff --git a/chromium/media/cast/audio_sender/audio_encoder.cc b/chromium/media/cast/audio_sender/audio_encoder.cc index a82d1de39a5..8860c7dd2d8 100644 --- a/chromium/media/cast/audio_sender/audio_encoder.cc +++ b/chromium/media/cast/audio_sender/audio_encoder.cc @@ -8,8 +8,8 @@ #include "base/bind.h" #include "base/bind_helpers.h" -#include "base/logging.h" -#include "base/message_loop/message_loop.h" +#include "base/location.h" +#include "base/stl_util.h" #include "base/sys_byteorder.h" #include "base/time/time.h" #include "media/base/audio_bus.h" @@ -20,121 +20,198 @@ namespace media { namespace cast { -void LogAudioEncodedEvent(CastEnvironment* const cast_environment, - const base::TimeTicks& recorded_time) { - // TODO(mikhal): Resolve timestamp calculation for audio. - cast_environment->Logging()->InsertFrameEvent(kAudioFrameEncoded, - GetVideoRtpTimestamp(recorded_time), kFrameIdUnknown); -} +namespace { + +// The fixed number of audio frames per second and, inversely, the duration of +// one frame's worth of samples. +const int kFramesPerSecond = 100; +const int kFrameDurationMillis = 1000 / kFramesPerSecond; // No remainder! + +// Threshold used to decide whether audio being delivered to the encoder is +// coming in too slow with respect to the capture timestamps. +const int kUnderrunThresholdMillis = 3 * kFrameDurationMillis; + +} // namespace + // Base class that handles the common problem of feeding one or more AudioBus' -// data into a 10 ms buffer and then, once the buffer is full, encoding the -// signal and emitting an EncodedAudioFrame via the FrameEncodedCallback. +// data into a buffer and then, once the buffer is full, encoding the signal and +// emitting an EncodedFrame via the FrameEncodedCallback. // // Subclasses complete the implementation by handling the actual encoding // details. -class AudioEncoder::ImplBase { +class AudioEncoder::ImplBase + : public base::RefCountedThreadSafe<AudioEncoder::ImplBase> { public: - ImplBase(CastEnvironment* cast_environment, - AudioCodec codec, int num_channels, int sampling_rate, + ImplBase(const scoped_refptr<CastEnvironment>& cast_environment, + transport::AudioCodec codec, + int num_channels, + int sampling_rate, const FrameEncodedCallback& callback) : cast_environment_(cast_environment), - codec_(codec), num_channels_(num_channels), - samples_per_10ms_(sampling_rate / 100), + codec_(codec), + num_channels_(num_channels), + samples_per_frame_(sampling_rate / kFramesPerSecond), callback_(callback), + cast_initialization_status_(STATUS_AUDIO_UNINITIALIZED), buffer_fill_end_(0), - frame_id_(0) { - CHECK_GT(num_channels_, 0); - CHECK_GT(samples_per_10ms_, 0); - CHECK_EQ(sampling_rate % 100, 0); - CHECK_LE(samples_per_10ms_ * num_channels_, - EncodedAudioFrame::kMaxNumberOfSamples); + frame_id_(0), + frame_rtp_timestamp_(0) { + // Support for max sampling rate of 48KHz, 2 channels, 100 ms duration. + const int kMaxSamplesTimesChannelsPerFrame = 48 * 2 * 100; + if (num_channels_ <= 0 || samples_per_frame_ <= 0 || + sampling_rate % kFramesPerSecond != 0 || + samples_per_frame_ * num_channels_ > kMaxSamplesTimesChannelsPerFrame) { + cast_initialization_status_ = STATUS_INVALID_AUDIO_CONFIGURATION; + } } - virtual ~ImplBase() {} + CastInitializationStatus InitializationResult() const { + return cast_initialization_status_; + } + + void EncodeAudio(scoped_ptr<AudioBus> audio_bus, + const base::TimeTicks& recorded_time) { + DCHECK_EQ(cast_initialization_status_, STATUS_AUDIO_INITIALIZED); + DCHECK(!recorded_time.is_null()); + + // Determine whether |recorded_time| is consistent with the amount of audio + // data having been processed in the past. Resolve the underrun problem by + // dropping data from the internal buffer and skipping ahead the next + // frame's RTP timestamp by the estimated number of frames missed. On the + // other hand, don't attempt to resolve overruns: A receiver should + // gracefully deal with an excess of audio data. + const base::TimeDelta frame_duration = + base::TimeDelta::FromMilliseconds(kFrameDurationMillis); + base::TimeDelta buffer_fill_duration = + buffer_fill_end_ * frame_duration / samples_per_frame_; + if (!frame_capture_time_.is_null()) { + const base::TimeDelta amount_ahead_by = + recorded_time - (frame_capture_time_ + buffer_fill_duration); + if (amount_ahead_by > + base::TimeDelta::FromMilliseconds(kUnderrunThresholdMillis)) { + buffer_fill_end_ = 0; + buffer_fill_duration = base::TimeDelta(); + const int64 num_frames_missed = amount_ahead_by / + base::TimeDelta::FromMilliseconds(kFrameDurationMillis); + frame_rtp_timestamp_ += + static_cast<uint32>(num_frames_missed * samples_per_frame_); + DVLOG(1) << "Skipping RTP timestamp ahead to account for " + << num_frames_missed * samples_per_frame_ + << " samples' worth of underrun."; + } + } + frame_capture_time_ = recorded_time - buffer_fill_duration; - void EncodeAudio(const AudioBus* audio_bus, - const base::TimeTicks& recorded_time, - const base::Closure& done_callback) { + // Encode all audio in |audio_bus| into zero or more frames. int src_pos = 0; while (src_pos < audio_bus->frames()) { - const int num_samples_to_xfer = - std::min(samples_per_10ms_ - buffer_fill_end_, - audio_bus->frames() - src_pos); + const int num_samples_to_xfer = std::min( + samples_per_frame_ - buffer_fill_end_, audio_bus->frames() - src_pos); DCHECK_EQ(audio_bus->channels(), num_channels_); TransferSamplesIntoBuffer( - audio_bus, src_pos, buffer_fill_end_, num_samples_to_xfer); + audio_bus.get(), src_pos, buffer_fill_end_, num_samples_to_xfer); src_pos += num_samples_to_xfer; buffer_fill_end_ += num_samples_to_xfer; - if (src_pos == audio_bus->frames()) { - cast_environment_->PostTask(CastEnvironment::MAIN, FROM_HERE, - done_callback); - // Note: |audio_bus| is now invalid.. + if (buffer_fill_end_ < samples_per_frame_) + break; + + scoped_ptr<transport::EncodedFrame> audio_frame( + new transport::EncodedFrame()); + audio_frame->dependency = transport::EncodedFrame::KEY; + audio_frame->frame_id = frame_id_; + audio_frame->referenced_frame_id = frame_id_; + audio_frame->rtp_timestamp = frame_rtp_timestamp_; + audio_frame->reference_time = frame_capture_time_; + + if (EncodeFromFilledBuffer(&audio_frame->data)) { + cast_environment_->PostTask( + CastEnvironment::MAIN, + FROM_HERE, + base::Bind(callback_, base::Passed(&audio_frame))); } - if (buffer_fill_end_ == samples_per_10ms_) { - scoped_ptr<EncodedAudioFrame> audio_frame(new EncodedAudioFrame()); - audio_frame->codec = codec_; - audio_frame->frame_id = frame_id_++; - audio_frame->samples = samples_per_10ms_; - if (EncodeFromFilledBuffer(&audio_frame->data)) { - // Compute an offset to determine the recorded time for the first - // audio sample in the buffer. - const base::TimeDelta buffer_time_offset = - (buffer_fill_end_ - src_pos) * - base::TimeDelta::FromMilliseconds(10) / samples_per_10ms_; - // TODO(miu): Consider batching EncodedAudioFrames so we only post a - // at most one task for each call to this method. - cast_environment_->PostTask( - CastEnvironment::MAIN, FROM_HERE, - base::Bind(callback_, base::Passed(&audio_frame), - recorded_time - buffer_time_offset)); - } - buffer_fill_end_ = 0; - } + // Reset the internal buffer, frame ID, and timestamps for the next frame. + buffer_fill_end_ = 0; + ++frame_id_; + frame_rtp_timestamp_ += samples_per_frame_; + frame_capture_time_ += frame_duration; } } protected: + friend class base::RefCountedThreadSafe<ImplBase>; + virtual ~ImplBase() {} + virtual void TransferSamplesIntoBuffer(const AudioBus* audio_bus, int source_offset, int buffer_fill_offset, int num_samples) = 0; virtual bool EncodeFromFilledBuffer(std::string* out) = 0; - CastEnvironment* const cast_environment_; - const AudioCodec codec_; + const scoped_refptr<CastEnvironment> cast_environment_; + const transport::AudioCodec codec_; const int num_channels_; - const int samples_per_10ms_; + const int samples_per_frame_; const FrameEncodedCallback callback_; + // Subclass' ctor is expected to set this to STATUS_AUDIO_INITIALIZED. + CastInitializationStatus cast_initialization_status_; + private: // In the case where a call to EncodeAudio() cannot completely fill the // buffer, this points to the position at which to populate data in a later // call. int buffer_fill_end_; - // A counter used to label EncodedAudioFrames. + // A counter used to label EncodedFrames. uint32 frame_id_; - private: + // The RTP timestamp for the next frame of encoded audio. This is defined as + // the number of audio samples encoded so far, plus the estimated number of + // samples that were missed due to data underruns. A receiver uses this value + // to detect gaps in the audio signal data being provided. Per the spec, RTP + // timestamp values are allowed to overflow and roll around past zero. + uint32 frame_rtp_timestamp_; + + // The local system time associated with the start of the next frame of + // encoded audio. This value is passed on to a receiver as a reference clock + // timestamp for the purposes of synchronizing audio and video. Its + // progression is expected to drift relative to the elapsed time implied by + // the RTP timestamps. + base::TimeTicks frame_capture_time_; + DISALLOW_COPY_AND_ASSIGN(ImplBase); }; class AudioEncoder::OpusImpl : public AudioEncoder::ImplBase { public: - OpusImpl(CastEnvironment* cast_environment, - int num_channels, int sampling_rate, int bitrate, + OpusImpl(const scoped_refptr<CastEnvironment>& cast_environment, + int num_channels, + int sampling_rate, + int bitrate, const FrameEncodedCallback& callback) - : ImplBase(cast_environment, kOpus, num_channels, sampling_rate, + : ImplBase(cast_environment, + transport::kOpus, + num_channels, + sampling_rate, callback), encoder_memory_(new uint8[opus_encoder_get_size(num_channels)]), opus_encoder_(reinterpret_cast<OpusEncoder*>(encoder_memory_.get())), - buffer_(new float[num_channels * samples_per_10ms_]) { - CHECK_EQ(opus_encoder_init(opus_encoder_, sampling_rate, num_channels, - OPUS_APPLICATION_AUDIO), - OPUS_OK); + buffer_(new float[num_channels * samples_per_frame_]) { + if (ImplBase::cast_initialization_status_ != STATUS_AUDIO_UNINITIALIZED) + return; + if (opus_encoder_init(opus_encoder_, + sampling_rate, + num_channels, + OPUS_APPLICATION_AUDIO) != OPUS_OK) { + ImplBase::cast_initialization_status_ = + STATUS_INVALID_AUDIO_CONFIGURATION; + return; + } + ImplBase::cast_initialization_status_ = STATUS_AUDIO_INITIALIZED; + if (bitrate <= 0) { // Note: As of 2013-10-31, the encoder in "auto bitrate" mode would use a // variable bitrate up to 102kbps for 2-channel, 48 kHz audio and a 10 ms @@ -146,9 +223,9 @@ class AudioEncoder::OpusImpl : public AudioEncoder::ImplBase { OPUS_OK); } + private: virtual ~OpusImpl() {} - private: virtual void TransferSamplesIntoBuffer(const AudioBus* audio_bus, int source_offset, int buffer_fill_offset, @@ -165,9 +242,12 @@ class AudioEncoder::OpusImpl : public AudioEncoder::ImplBase { virtual bool EncodeFromFilledBuffer(std::string* out) OVERRIDE { out->resize(kOpusMaxPayloadSize); - const opus_int32 result = opus_encode_float( - opus_encoder_, buffer_.get(), samples_per_10ms_, - reinterpret_cast<uint8*>(&out->at(0)), kOpusMaxPayloadSize); + const opus_int32 result = + opus_encode_float(opus_encoder_, + buffer_.get(), + samples_per_frame_, + reinterpret_cast<uint8*>(string_as_array(out)), + kOpusMaxPayloadSize); if (result > 1) { out->resize(result); return true; @@ -198,30 +278,40 @@ class AudioEncoder::OpusImpl : public AudioEncoder::ImplBase { class AudioEncoder::Pcm16Impl : public AudioEncoder::ImplBase { public: - Pcm16Impl(CastEnvironment* cast_environment, - int num_channels, int sampling_rate, + Pcm16Impl(const scoped_refptr<CastEnvironment>& cast_environment, + int num_channels, + int sampling_rate, const FrameEncodedCallback& callback) - : ImplBase(cast_environment, kPcm16, num_channels, sampling_rate, + : ImplBase(cast_environment, + transport::kPcm16, + num_channels, + sampling_rate, callback), - buffer_(new int16[num_channels * samples_per_10ms_]) {} + buffer_(new int16[num_channels * samples_per_frame_]) { + if (ImplBase::cast_initialization_status_ != STATUS_AUDIO_UNINITIALIZED) + return; + cast_initialization_status_ = STATUS_AUDIO_INITIALIZED; + } + private: virtual ~Pcm16Impl() {} - private: virtual void TransferSamplesIntoBuffer(const AudioBus* audio_bus, int source_offset, int buffer_fill_offset, int num_samples) OVERRIDE { audio_bus->ToInterleavedPartial( - source_offset, num_samples, sizeof(int16), + source_offset, + num_samples, + sizeof(int16), buffer_.get() + buffer_fill_offset * num_channels_); } virtual bool EncodeFromFilledBuffer(std::string* out) OVERRIDE { // Output 16-bit PCM integers in big-endian byte order. - out->resize(num_channels_ * samples_per_10ms_ * sizeof(int16)); + out->resize(num_channels_ * samples_per_frame_ * sizeof(int16)); const int16* src = buffer_.get(); - const int16* const src_end = src + num_channels_ * samples_per_10ms_; + const int16* const src_end = src + num_channels_ * samples_per_frame_; uint16* dest = reinterpret_cast<uint16*>(&out->at(0)); for (; src < src_end; ++src, ++dest) *dest = base::HostToNet16(*src); @@ -242,17 +332,19 @@ AudioEncoder::AudioEncoder( // Note: It doesn't matter which thread constructs AudioEncoder, just so long // as all calls to InsertAudio() are by the same thread. insert_thread_checker_.DetachFromThread(); - switch (audio_config.codec) { - case kOpus: - impl_.reset(new OpusImpl( - cast_environment, audio_config.channels, audio_config.frequency, - audio_config.bitrate, frame_encoded_callback)); + case transport::kOpus: + impl_ = new OpusImpl(cast_environment, + audio_config.channels, + audio_config.frequency, + audio_config.bitrate, + frame_encoded_callback); break; - case kPcm16: - impl_.reset(new Pcm16Impl( - cast_environment, audio_config.channels, audio_config.frequency, - frame_encoded_callback)); + case transport::kPcm16: + impl_ = new Pcm16Impl(cast_environment, + audio_config.channels, + audio_config.frequency, + frame_encoded_callback); break; default: NOTREACHED() << "Unsupported or unspecified codec for audio encoder"; @@ -262,30 +354,28 @@ AudioEncoder::AudioEncoder( AudioEncoder::~AudioEncoder() {} -void AudioEncoder::InsertAudio( - const AudioBus* audio_bus, - const base::TimeTicks& recorded_time, - const base::Closure& done_callback) { +CastInitializationStatus AudioEncoder::InitializationResult() const { + DCHECK(insert_thread_checker_.CalledOnValidThread()); + if (impl_) { + return impl_->InitializationResult(); + } + return STATUS_UNSUPPORTED_AUDIO_CODEC; +} + +void AudioEncoder::InsertAudio(scoped_ptr<AudioBus> audio_bus, + const base::TimeTicks& recorded_time) { DCHECK(insert_thread_checker_.CalledOnValidThread()); + DCHECK(audio_bus.get()); if (!impl_) { NOTREACHED(); - cast_environment_->PostTask(CastEnvironment::MAIN, FROM_HERE, - done_callback); return; } - cast_environment_->PostTask(CastEnvironment::AUDIO_ENCODER, FROM_HERE, - base::Bind(&AudioEncoder::EncodeAudio, this, audio_bus, recorded_time, - done_callback)); -} - -void AudioEncoder::EncodeAudio( - const AudioBus* audio_bus, - const base::TimeTicks& recorded_time, - const base::Closure& done_callback) { - DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::AUDIO_ENCODER)); - impl_->EncodeAudio(audio_bus, recorded_time, done_callback); - cast_environment_->PostTask(CastEnvironment::MAIN, FROM_HERE, - base::Bind(LogAudioEncodedEvent, cast_environment_, recorded_time)); + cast_environment_->PostTask(CastEnvironment::AUDIO, + FROM_HERE, + base::Bind(&AudioEncoder::ImplBase::EncodeAudio, + impl_, + base::Passed(&audio_bus), + recorded_time)); } } // namespace cast diff --git a/chromium/media/cast/audio_sender/audio_encoder.h b/chromium/media/cast/audio_sender/audio_encoder.h index 4a22d1983bd..2297672b74b 100644 --- a/chromium/media/cast/audio_sender/audio_encoder.h +++ b/chromium/media/cast/audio_sender/audio_encoder.h @@ -8,6 +8,7 @@ #include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" #include "base/threading/thread_checker.h" +#include "media/base/audio_bus.h" #include "media/cast/cast_config.h" #include "media/cast/cast_environment.h" @@ -16,47 +17,30 @@ class TimeTicks; } namespace media { -class AudioBus; -} - -namespace media { namespace cast { -class AudioEncoder : public base::RefCountedThreadSafe<AudioEncoder> { +class AudioEncoder { public: - typedef base::Callback<void(scoped_ptr<EncodedAudioFrame>, - const base::TimeTicks&)> FrameEncodedCallback; + typedef base::Callback<void(scoped_ptr<transport::EncodedFrame>)> + FrameEncodedCallback; AudioEncoder(const scoped_refptr<CastEnvironment>& cast_environment, const AudioSenderConfig& audio_config, const FrameEncodedCallback& frame_encoded_callback); + virtual ~AudioEncoder(); - // The |audio_bus| must be valid until the |done_callback| is called. - // The callback is called from the main cast thread as soon as the encoder is - // done with |audio_bus|; it does not mean that the encoded data has been - // sent out. - void InsertAudio(const AudioBus* audio_bus, - const base::TimeTicks& recorded_time, - const base::Closure& done_callback); + CastInitializationStatus InitializationResult() const; - protected: - virtual ~AudioEncoder(); + void InsertAudio(scoped_ptr<AudioBus> audio_bus, + const base::TimeTicks& recorded_time); private: - friend class base::RefCountedThreadSafe<AudioEncoder>; - class ImplBase; class OpusImpl; class Pcm16Impl; - // Invokes |impl_|'s encode method on the AUDIO_ENCODER thread while holding - // a ref-count on AudioEncoder. - void EncodeAudio(const AudioBus* audio_bus, - const base::TimeTicks& recorded_time, - const base::Closure& done_callback); - const scoped_refptr<CastEnvironment> cast_environment_; - scoped_ptr<ImplBase> impl_; + scoped_refptr<ImplBase> impl_; // Used to ensure only one thread invokes InsertAudio(). base::ThreadChecker insert_thread_checker_; diff --git a/chromium/media/cast/audio_sender/audio_encoder_unittest.cc b/chromium/media/cast/audio_sender/audio_encoder_unittest.cc index d721f71ef29..b521099243b 100644 --- a/chromium/media/cast/audio_sender/audio_encoder_unittest.cc +++ b/chromium/media/cast/audio_sender/audio_encoder_unittest.cc @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include <stdint.h> + #include <sstream> #include <string> @@ -13,53 +15,56 @@ #include "media/cast/audio_sender/audio_encoder.h" #include "media/cast/cast_config.h" #include "media/cast/cast_environment.h" -#include "media/cast/test/audio_utility.h" -#include "media/cast/test/fake_task_runner.h" +#include "media/cast/test/fake_single_thread_task_runner.h" +#include "media/cast/test/utility/audio_utility.h" #include "testing/gtest/include/gtest/gtest.h" namespace media { namespace cast { -static const int64 kStartMillisecond = GG_INT64_C(12345678900000); +static const int64 kStartMillisecond = INT64_C(12345678900000); namespace { class TestEncodedAudioFrameReceiver { public: - explicit TestEncodedAudioFrameReceiver(AudioCodec codec) : - codec_(codec), frames_received_(0) {} + explicit TestEncodedAudioFrameReceiver(transport::AudioCodec codec) + : codec_(codec), frames_received_(0), rtp_lower_bound_(0) {} virtual ~TestEncodedAudioFrameReceiver() {} - int frames_received() const { - return frames_received_; - } - - void SetRecordedTimeLowerBound(const base::TimeTicks& t) { - lower_bound_ = t; - } + int frames_received() const { return frames_received_; } - void SetRecordedTimeUpperBound(const base::TimeTicks& t) { - upper_bound_ = t; + void SetCaptureTimeBounds(const base::TimeTicks& lower_bound, + const base::TimeTicks& upper_bound) { + lower_bound_ = lower_bound; + upper_bound_ = upper_bound; } - void FrameEncoded(scoped_ptr<EncodedAudioFrame> encoded_frame, - const base::TimeTicks& recorded_time) { - EXPECT_EQ(codec_, encoded_frame->codec); + void FrameEncoded(scoped_ptr<transport::EncodedFrame> encoded_frame) { + EXPECT_EQ(encoded_frame->dependency, transport::EncodedFrame::KEY); EXPECT_EQ(static_cast<uint8>(frames_received_ & 0xff), encoded_frame->frame_id); - EXPECT_LT(0, encoded_frame->samples); + EXPECT_EQ(encoded_frame->frame_id, encoded_frame->referenced_frame_id); + // RTP timestamps should be monotonically increasing and integer multiples + // of the fixed frame size. + EXPECT_LE(rtp_lower_bound_, encoded_frame->rtp_timestamp); + rtp_lower_bound_ = encoded_frame->rtp_timestamp; + // Note: In audio_encoder.cc, 100 is the fixed audio frame rate. + const int kSamplesPerFrame = kDefaultAudioSamplingRate / 100; + EXPECT_EQ(0u, encoded_frame->rtp_timestamp % kSamplesPerFrame); EXPECT_TRUE(!encoded_frame->data.empty()); - EXPECT_LE(lower_bound_, recorded_time); - lower_bound_ = recorded_time; - EXPECT_GT(upper_bound_, recorded_time); + EXPECT_LE(lower_bound_, encoded_frame->reference_time); + lower_bound_ = encoded_frame->reference_time; + EXPECT_GT(upper_bound_, encoded_frame->reference_time); ++frames_received_; } private: - const AudioCodec codec_; + const transport::AudioCodec codec_; int frames_received_; + uint32 rtp_lower_bound_; base::TimeTicks lower_bound_; base::TimeTicks upper_bound_; @@ -90,46 +95,48 @@ class AudioEncoderTest : public ::testing::TestWithParam<TestScenario> { public: AudioEncoderTest() { InitializeMediaLibraryForTesting(); - testing_clock_.Advance( + testing_clock_ = new base::SimpleTestTickClock(); + testing_clock_->Advance( base::TimeDelta::FromMilliseconds(kStartMillisecond)); } virtual void SetUp() { - task_runner_ = new test::FakeTaskRunner(&testing_clock_); - cast_environment_ = new CastEnvironment(&testing_clock_, task_runner_, - task_runner_, task_runner_, task_runner_, task_runner_, - GetDefaultCastLoggingConfig()); + task_runner_ = new test::FakeSingleThreadTaskRunner(testing_clock_); + cast_environment_ = + new CastEnvironment(scoped_ptr<base::TickClock>(testing_clock_).Pass(), + task_runner_, + task_runner_, + task_runner_); } virtual ~AudioEncoderTest() {} - void RunTestForCodec(AudioCodec codec) { + void RunTestForCodec(transport::AudioCodec codec) { const TestScenario& scenario = GetParam(); - SCOPED_TRACE(::testing::Message() - << "Durations: " << scenario.ToString()); + SCOPED_TRACE(::testing::Message() << "Durations: " << scenario.ToString()); CreateObjectsForCodec(codec); - receiver_->SetRecordedTimeLowerBound(testing_clock_.NowTicks()); + // Note: In audio_encoder.cc, 10 ms is the fixed frame duration. + const base::TimeDelta frame_duration = + base::TimeDelta::FromMilliseconds(10); + for (size_t i = 0; i < scenario.num_durations; ++i) { - const base::TimeDelta duration = - base::TimeDelta::FromMilliseconds(scenario.durations_in_ms[i]); - receiver_->SetRecordedTimeUpperBound( - testing_clock_.NowTicks() + duration); - - const scoped_ptr<AudioBus> bus( - audio_bus_factory_->NextAudioBus(duration)); - - const int last_count = release_callback_count_; - audio_encoder_->InsertAudio( - bus.get(), testing_clock_.NowTicks(), - base::Bind(&AudioEncoderTest::IncrementReleaseCallbackCounter, - base::Unretained(this))); - task_runner_->RunTasks(); - EXPECT_EQ(1, release_callback_count_ - last_count) - << "Release callback was not invoked once."; - - testing_clock_.Advance(duration); + const bool simulate_missing_data = scenario.durations_in_ms[i] < 0; + const base::TimeDelta duration = base::TimeDelta::FromMilliseconds( + std::abs(scenario.durations_in_ms[i])); + receiver_->SetCaptureTimeBounds( + testing_clock_->NowTicks() - frame_duration, + testing_clock_->NowTicks() + duration); + if (simulate_missing_data) { + task_runner_->RunTasks(); + testing_clock_->Advance(duration); + } else { + audio_encoder_->InsertAudio(audio_bus_factory_->NextAudioBus(duration), + testing_clock_->NowTicks()); + task_runner_->RunTasks(); + testing_clock_->Advance(duration); + } } DVLOG(1) << "Received " << receiver_->frames_received() @@ -137,98 +144,101 @@ class AudioEncoderTest : public ::testing::TestWithParam<TestScenario> { } private: - void CreateObjectsForCodec(AudioCodec codec) { + void CreateObjectsForCodec(transport::AudioCodec codec) { AudioSenderConfig audio_config; audio_config.codec = codec; audio_config.use_external_encoder = false; audio_config.frequency = kDefaultAudioSamplingRate; audio_config.channels = 2; audio_config.bitrate = kDefaultAudioEncoderBitrate; - audio_config.rtp_payload_type = 127; + audio_config.rtp_config.payload_type = 127; - audio_bus_factory_.reset(new TestAudioBusFactory( - audio_config.channels, audio_config.frequency, - TestAudioBusFactory::kMiddleANoteFreq, 0.5f)); + audio_bus_factory_.reset( + new TestAudioBusFactory(audio_config.channels, + audio_config.frequency, + TestAudioBusFactory::kMiddleANoteFreq, + 0.5f)); receiver_.reset(new TestEncodedAudioFrameReceiver(codec)); - audio_encoder_ = new AudioEncoder( - cast_environment_, audio_config, + audio_encoder_.reset(new AudioEncoder( + cast_environment_, + audio_config, base::Bind(&TestEncodedAudioFrameReceiver::FrameEncoded, - base::Unretained(receiver_.get()))); - release_callback_count_ = 0; - } - - void IncrementReleaseCallbackCounter() { - ++release_callback_count_; + base::Unretained(receiver_.get())))); } - base::SimpleTestTickClock testing_clock_; - scoped_refptr<test::FakeTaskRunner> task_runner_; + base::SimpleTestTickClock* testing_clock_; // Owned by CastEnvironment. + scoped_refptr<test::FakeSingleThreadTaskRunner> task_runner_; scoped_ptr<TestAudioBusFactory> audio_bus_factory_; scoped_ptr<TestEncodedAudioFrameReceiver> receiver_; - scoped_refptr<AudioEncoder> audio_encoder_; + scoped_ptr<AudioEncoder> audio_encoder_; scoped_refptr<CastEnvironment> cast_environment_; - int release_callback_count_; DISALLOW_COPY_AND_ASSIGN(AudioEncoderTest); }; -TEST_P(AudioEncoderTest, EncodeOpus) { - RunTestForCodec(kOpus); -} - -TEST_P(AudioEncoderTest, EncodePcm16) { - RunTestForCodec(kPcm16); -} - -static const int64 kOneCall_3Millis[] = { 3 }; -static const int64 kOneCall_10Millis[] = { 10 }; -static const int64 kOneCall_13Millis[] = { 13 }; -static const int64 kOneCall_20Millis[] = { 20 }; - -static const int64 kTwoCalls_3Millis[] = { 3, 3 }; -static const int64 kTwoCalls_10Millis[] = { 10, 10 }; -static const int64 kTwoCalls_Mixed1[] = { 3, 10 }; -static const int64 kTwoCalls_Mixed2[] = { 10, 3 }; -static const int64 kTwoCalls_Mixed3[] = { 3, 17 }; -static const int64 kTwoCalls_Mixed4[] = { 17, 3 }; - -static const int64 kManyCalls_3Millis[] = - { 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 }; -static const int64 kManyCalls_10Millis[] = - { 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10 }; -static const int64 kManyCalls_Mixed1[] = - { 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10 }; -static const int64 kManyCalls_Mixed2[] = - { 10, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 3 }; -static const int64 kManyCalls_Mixed3[] = - { 3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8, 9, 7, 9, 3, 2, 3, 8, 4, 6, 2, 6, 4 }; -static const int64 kManyCalls_Mixed4[] = - { 31, 4, 15, 9, 26, 53, 5, 8, 9, 7, 9, 32, 38, 4, 62, 64, 3 }; -static const int64 kManyCalls_Mixed5[] = - { 3, 14, 15, 9, 26, 53, 58, 9, 7, 9, 3, 23, 8, 4, 6, 2, 6, 43 }; +TEST_P(AudioEncoderTest, EncodeOpus) { RunTestForCodec(transport::kOpus); } + +TEST_P(AudioEncoderTest, EncodePcm16) { RunTestForCodec(transport::kPcm16); } + +static const int64 kOneCall_3Millis[] = {3}; +static const int64 kOneCall_10Millis[] = {10}; +static const int64 kOneCall_13Millis[] = {13}; +static const int64 kOneCall_20Millis[] = {20}; + +static const int64 kTwoCalls_3Millis[] = {3, 3}; +static const int64 kTwoCalls_10Millis[] = {10, 10}; +static const int64 kTwoCalls_Mixed1[] = {3, 10}; +static const int64 kTwoCalls_Mixed2[] = {10, 3}; +static const int64 kTwoCalls_Mixed3[] = {3, 17}; +static const int64 kTwoCalls_Mixed4[] = {17, 3}; + +static const int64 kManyCalls_3Millis[] = {3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3}; +static const int64 kManyCalls_10Millis[] = {10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10}; +static const int64 kManyCalls_Mixed1[] = {3, 10, 3, 10, 3, 10, 3, 10, 3, + 10, 3, 10, 3, 10, 3, 10, 3, 10}; +static const int64 kManyCalls_Mixed2[] = {10, 3, 10, 3, 10, 3, 10, 3, 10, 3, + 10, 3, 10, 3, 10, 3, 10, 3, 10, 3}; +static const int64 kManyCalls_Mixed3[] = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8, + 9, 7, 9, 3, 2, 3, 8, 4, 6, 2, 6, 4}; +static const int64 kManyCalls_Mixed4[] = {31, 4, 15, 9, 26, 53, 5, 8, 9, + 7, 9, 32, 38, 4, 62, 64, 3}; +static const int64 kManyCalls_Mixed5[] = {3, 14, 15, 9, 26, 53, 58, 9, 7, + 9, 3, 23, 8, 4, 6, 2, 6, 43}; + +static const int64 kOneBigUnderrun[] = {10, 10, 10, 10, -1000, 10, 10, 10}; +static const int64 kTwoBigUnderruns[] = {10, 10, 10, 10, -712, 10, 10, 10, + -1311, 10, 10, 10}; +static const int64 kMixedUnderruns[] = {31, -64, 4, 15, 9, 26, -53, 5, 8, -9, + 7, 9, 32, 38, -4, 62, -64, 3}; INSTANTIATE_TEST_CASE_P( - AudioEncoderTestScenarios, AudioEncoderTest, + AudioEncoderTestScenarios, + AudioEncoderTest, ::testing::Values( - TestScenario(kOneCall_3Millis, arraysize(kOneCall_3Millis)), - TestScenario(kOneCall_10Millis, arraysize(kOneCall_10Millis)), - TestScenario(kOneCall_13Millis, arraysize(kOneCall_13Millis)), - TestScenario(kOneCall_20Millis, arraysize(kOneCall_20Millis)), - TestScenario(kTwoCalls_3Millis, arraysize(kTwoCalls_3Millis)), - TestScenario(kTwoCalls_10Millis, arraysize(kTwoCalls_10Millis)), - TestScenario(kTwoCalls_Mixed1, arraysize(kTwoCalls_Mixed1)), - TestScenario(kTwoCalls_Mixed2, arraysize(kTwoCalls_Mixed2)), - TestScenario(kTwoCalls_Mixed3, arraysize(kTwoCalls_Mixed3)), - TestScenario(kTwoCalls_Mixed4, arraysize(kTwoCalls_Mixed4)), - TestScenario(kManyCalls_3Millis, arraysize(kManyCalls_3Millis)), - TestScenario(kManyCalls_10Millis, arraysize(kManyCalls_10Millis)), - TestScenario(kManyCalls_Mixed1, arraysize(kManyCalls_Mixed1)), - TestScenario(kManyCalls_Mixed2, arraysize(kManyCalls_Mixed2)), - TestScenario(kManyCalls_Mixed3, arraysize(kManyCalls_Mixed3)), - TestScenario(kManyCalls_Mixed4, arraysize(kManyCalls_Mixed4)), - TestScenario(kManyCalls_Mixed5, arraysize(kManyCalls_Mixed5)))); + TestScenario(kOneCall_3Millis, arraysize(kOneCall_3Millis)), + TestScenario(kOneCall_10Millis, arraysize(kOneCall_10Millis)), + TestScenario(kOneCall_13Millis, arraysize(kOneCall_13Millis)), + TestScenario(kOneCall_20Millis, arraysize(kOneCall_20Millis)), + TestScenario(kTwoCalls_3Millis, arraysize(kTwoCalls_3Millis)), + TestScenario(kTwoCalls_10Millis, arraysize(kTwoCalls_10Millis)), + TestScenario(kTwoCalls_Mixed1, arraysize(kTwoCalls_Mixed1)), + TestScenario(kTwoCalls_Mixed2, arraysize(kTwoCalls_Mixed2)), + TestScenario(kTwoCalls_Mixed3, arraysize(kTwoCalls_Mixed3)), + TestScenario(kTwoCalls_Mixed4, arraysize(kTwoCalls_Mixed4)), + TestScenario(kManyCalls_3Millis, arraysize(kManyCalls_3Millis)), + TestScenario(kManyCalls_10Millis, arraysize(kManyCalls_10Millis)), + TestScenario(kManyCalls_Mixed1, arraysize(kManyCalls_Mixed1)), + TestScenario(kManyCalls_Mixed2, arraysize(kManyCalls_Mixed2)), + TestScenario(kManyCalls_Mixed3, arraysize(kManyCalls_Mixed3)), + TestScenario(kManyCalls_Mixed4, arraysize(kManyCalls_Mixed4)), + TestScenario(kManyCalls_Mixed5, arraysize(kManyCalls_Mixed5)), + TestScenario(kOneBigUnderrun, arraysize(kOneBigUnderrun)), + TestScenario(kTwoBigUnderruns, arraysize(kTwoBigUnderruns)), + TestScenario(kMixedUnderruns, arraysize(kMixedUnderruns)))); } // namespace cast } // namespace media diff --git a/chromium/media/cast/audio_sender/audio_sender.cc b/chromium/media/cast/audio_sender/audio_sender.cc index b1b177d3ec3..e56d634782c 100644 --- a/chromium/media/cast/audio_sender/audio_sender.cc +++ b/chromium/media/cast/audio_sender/audio_sender.cc @@ -7,203 +7,338 @@ #include "base/bind.h" #include "base/logging.h" #include "base/message_loop/message_loop.h" -#include "crypto/encryptor.h" -#include "crypto/symmetric_key.h" #include "media/cast/audio_sender/audio_encoder.h" -#include "media/cast/cast_environment.h" -#include "media/cast/net/rtp_sender/rtp_sender.h" -#include "media/cast/rtcp/rtcp.h" +#include "media/cast/cast_defines.h" +#include "media/cast/rtcp/rtcp_defines.h" +#include "media/cast/transport/cast_transport_config.h" namespace media { namespace cast { +namespace { -const int64 kMinSchedulingDelayMs = 1; +const int kNumAggressiveReportsSentAtStart = 100; +const int kMinSchedulingDelayMs = 1; -class LocalRtcpAudioSenderFeedback : public RtcpSenderFeedback { - public: - explicit LocalRtcpAudioSenderFeedback(AudioSender* audio_sender) - : audio_sender_(audio_sender) { - } +// TODO(miu): This should be specified in AudioSenderConfig, but currently it is +// fixed to 100 FPS (i.e., 10 ms per frame), and AudioEncoder assumes this as +// well. +const int kAudioFrameRate = 100; - virtual void OnReceivedCastFeedback( - const RtcpCastMessage& cast_feedback) OVERRIDE { - if (!cast_feedback.missing_frames_and_packets_.empty()) { - audio_sender_->ResendPackets(cast_feedback.missing_frames_and_packets_); - } - VLOG(1) << "Received audio ACK " - << static_cast<int>(cast_feedback.ack_frame_id_); - } +// Helper function to compute the maximum unacked audio frames that is sent. +int GetMaxUnackedFrames(base::TimeDelta target_delay) { + // As long as it doesn't go over |kMaxUnackedFrames|, it is okay to send more + // audio data than the target delay would suggest. Audio packets are tiny and + // receiver has the ability to drop any one of the packets. + // We send up to three times of the target delay of audio frames. + int frames = + 1 + 3 * target_delay * kAudioFrameRate / base::TimeDelta::FromSeconds(1); + return std::min(kMaxUnackedFrames, frames); +} +} // namespace - private: - AudioSender* audio_sender_; -}; +AudioSender::AudioSender(scoped_refptr<CastEnvironment> cast_environment, + const AudioSenderConfig& audio_config, + transport::CastTransportSender* const transport_sender) + : cast_environment_(cast_environment), + target_playout_delay_(base::TimeDelta::FromMilliseconds( + audio_config.rtp_config.max_delay_ms)), + transport_sender_(transport_sender), + max_unacked_frames_(GetMaxUnackedFrames(target_playout_delay_)), + configured_encoder_bitrate_(audio_config.bitrate), + rtcp_(cast_environment, + this, + transport_sender_, + NULL, // paced sender. + NULL, + audio_config.rtcp_mode, + base::TimeDelta::FromMilliseconds(audio_config.rtcp_interval), + audio_config.rtp_config.ssrc, + audio_config.incoming_feedback_ssrc, + audio_config.rtcp_c_name, + AUDIO_EVENT), + rtp_timestamp_helper_(audio_config.frequency), + num_aggressive_rtcp_reports_sent_(0), + last_sent_frame_id_(0), + latest_acked_frame_id_(0), + duplicate_ack_counter_(0), + cast_initialization_status_(STATUS_AUDIO_UNINITIALIZED), + weak_factory_(this) { + VLOG(1) << "max_unacked_frames " << max_unacked_frames_; + DCHECK_GT(max_unacked_frames_, 0); -class LocalRtpSenderStatistics : public RtpSenderStatistics { - public: - explicit LocalRtpSenderStatistics(RtpSender* rtp_sender) - : rtp_sender_(rtp_sender) { + if (!audio_config.use_external_encoder) { + audio_encoder_.reset( + new AudioEncoder(cast_environment, + audio_config, + base::Bind(&AudioSender::SendEncodedAudioFrame, + weak_factory_.GetWeakPtr()))); + cast_initialization_status_ = audio_encoder_->InitializationResult(); + } else { + NOTREACHED(); // No support for external audio encoding. + cast_initialization_status_ = STATUS_AUDIO_UNINITIALIZED; } - virtual void GetStatistics(const base::TimeTicks& now, - RtcpSenderInfo* sender_info) OVERRIDE { - rtp_sender_->RtpStatistics(now, sender_info); - } + media::cast::transport::CastTransportAudioConfig transport_config; + transport_config.codec = audio_config.codec; + transport_config.rtp.config = audio_config.rtp_config; + transport_config.frequency = audio_config.frequency; + transport_config.channels = audio_config.channels; + transport_config.rtp.max_outstanding_frames = max_unacked_frames_; + transport_sender_->InitializeAudio(transport_config); - private: - RtpSender* rtp_sender_; -}; + rtcp_.SetCastReceiverEventHistorySize(kReceiverRtcpEventHistorySize); -AudioSender::AudioSender(scoped_refptr<CastEnvironment> cast_environment, - const AudioSenderConfig& audio_config, - PacedPacketSender* const paced_packet_sender) - : cast_environment_(cast_environment), - rtp_sender_(cast_environment, &audio_config, NULL, - paced_packet_sender), - rtcp_feedback_(new LocalRtcpAudioSenderFeedback(this)), - rtp_audio_sender_statistics_( - new LocalRtpSenderStatistics(&rtp_sender_)), - rtcp_(cast_environment, - rtcp_feedback_.get(), - paced_packet_sender, - rtp_audio_sender_statistics_.get(), - NULL, - audio_config.rtcp_mode, - base::TimeDelta::FromMilliseconds(audio_config.rtcp_interval), - audio_config.sender_ssrc, - audio_config.incoming_feedback_ssrc, - audio_config.rtcp_c_name), - initialized_(false), - weak_factory_(this) { - if (audio_config.aes_iv_mask.size() == kAesKeySize && - audio_config.aes_key.size() == kAesKeySize) { - iv_mask_ = audio_config.aes_iv_mask; - crypto::SymmetricKey* key = crypto::SymmetricKey::Import( - crypto::SymmetricKey::AES, audio_config.aes_key); - encryptor_.reset(new crypto::Encryptor()); - encryptor_->Init(key, crypto::Encryptor::CTR, std::string()); - } else if (audio_config.aes_iv_mask.size() != 0 || - audio_config.aes_key.size() != 0) { - DCHECK(false) << "Invalid crypto configuration"; - } - if (!audio_config.use_external_encoder) { - audio_encoder_ = new AudioEncoder( - cast_environment, audio_config, - base::Bind(&AudioSender::SendEncodedAudioFrame, - weak_factory_.GetWeakPtr())); - } + memset(frame_id_to_rtp_timestamp_, 0, sizeof(frame_id_to_rtp_timestamp_)); } AudioSender::~AudioSender() {} -void AudioSender::InitializeTimers() { +void AudioSender::InsertAudio(scoped_ptr<AudioBus> audio_bus, + const base::TimeTicks& recorded_time) { DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); - if (!initialized_) { - initialized_ = true; - ScheduleNextRtcpReport(); + if (cast_initialization_status_ != STATUS_AUDIO_INITIALIZED) { + NOTREACHED(); + return; + } + DCHECK(audio_encoder_.get()) << "Invalid internal state"; + + if (AreTooManyFramesInFlight()) { + VLOG(1) << "Dropping frame due to too many frames currently in-flight."; + return; } + + audio_encoder_->InsertAudio(audio_bus.Pass(), recorded_time); } -void AudioSender::InsertAudio(const AudioBus* audio_bus, - const base::TimeTicks& recorded_time, - const base::Closure& done_callback) { +void AudioSender::SendEncodedAudioFrame( + scoped_ptr<transport::EncodedFrame> encoded_frame) { DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); - DCHECK(audio_encoder_.get()) << "Invalid internal state"; - // TODO(mikhal): Resolve calculation of the audio rtp_timestamp for logging. - // This is a tmp solution to allow the code to build. - cast_environment_->Logging()->InsertFrameEvent(kAudioFrameReceived, - GetVideoRtpTimestamp(recorded_time), kFrameIdUnknown); - audio_encoder_->InsertAudio(audio_bus, recorded_time, done_callback); + + const uint32 frame_id = encoded_frame->frame_id; + + const bool is_first_frame_to_be_sent = last_send_time_.is_null(); + last_send_time_ = cast_environment_->Clock()->NowTicks(); + last_sent_frame_id_ = frame_id; + // If this is the first frame about to be sent, fake the value of + // |latest_acked_frame_id_| to indicate the receiver starts out all caught up. + // Also, schedule the periodic frame re-send checks. + if (is_first_frame_to_be_sent) { + latest_acked_frame_id_ = frame_id - 1; + ScheduleNextResendCheck(); + } + + cast_environment_->Logging()->InsertEncodedFrameEvent( + last_send_time_, FRAME_ENCODED, AUDIO_EVENT, encoded_frame->rtp_timestamp, + frame_id, static_cast<int>(encoded_frame->data.size()), + encoded_frame->dependency == transport::EncodedFrame::KEY, + configured_encoder_bitrate_); + // Only use lowest 8 bits as key. + frame_id_to_rtp_timestamp_[frame_id & 0xff] = encoded_frame->rtp_timestamp; + + DCHECK(!encoded_frame->reference_time.is_null()); + rtp_timestamp_helper_.StoreLatestTime(encoded_frame->reference_time, + encoded_frame->rtp_timestamp); + + // At the start of the session, it's important to send reports before each + // frame so that the receiver can properly compute playout times. The reason + // more than one report is sent is because transmission is not guaranteed, + // only best effort, so we send enough that one should almost certainly get + // through. + if (num_aggressive_rtcp_reports_sent_ < kNumAggressiveReportsSentAtStart) { + // SendRtcpReport() will schedule future reports to be made if this is the + // last "aggressive report." + ++num_aggressive_rtcp_reports_sent_; + const bool is_last_aggressive_report = + (num_aggressive_rtcp_reports_sent_ == kNumAggressiveReportsSentAtStart); + VLOG_IF(1, is_last_aggressive_report) << "Sending last aggressive report."; + SendRtcpReport(is_last_aggressive_report); + } + + transport_sender_->InsertCodedAudioFrame(*encoded_frame); +} + +void AudioSender::IncomingRtcpPacket(scoped_ptr<Packet> packet) { + DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); + rtcp_.IncomingRtcpPacket(&packet->front(), packet->size()); } -void AudioSender::InsertCodedAudioFrame(const EncodedAudioFrame* audio_frame, - const base::TimeTicks& recorded_time, - const base::Closure callback) { +void AudioSender::ScheduleNextRtcpReport() { DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); - DCHECK(audio_encoder_.get() == NULL) << "Invalid internal state"; + base::TimeDelta time_to_next = + rtcp_.TimeToSendNextRtcpReport() - cast_environment_->Clock()->NowTicks(); - cast_environment_->Logging()->InsertFrameEvent(kAudioFrameReceived, - GetVideoRtpTimestamp(recorded_time), kFrameIdUnknown); + time_to_next = std::max( + time_to_next, base::TimeDelta::FromMilliseconds(kMinSchedulingDelayMs)); - if (encryptor_) { - EncodedAudioFrame encrypted_frame; - if (!EncryptAudioFrame(*audio_frame, &encrypted_frame)) { - // Logging already done. - return; - } - rtp_sender_.IncomingEncodedAudioFrame(&encrypted_frame, recorded_time); + cast_environment_->PostDelayedTask( + CastEnvironment::MAIN, + FROM_HERE, + base::Bind(&AudioSender::SendRtcpReport, + weak_factory_.GetWeakPtr(), + true), + time_to_next); +} + +void AudioSender::SendRtcpReport(bool schedule_future_reports) { + DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); + const base::TimeTicks now = cast_environment_->Clock()->NowTicks(); + uint32 now_as_rtp_timestamp = 0; + if (rtp_timestamp_helper_.GetCurrentTimeAsRtpTimestamp( + now, &now_as_rtp_timestamp)) { + rtcp_.SendRtcpFromRtpSender(now, now_as_rtp_timestamp); } else { - rtp_sender_.IncomingEncodedAudioFrame(audio_frame, recorded_time); + // |rtp_timestamp_helper_| should have stored a mapping by this point. + NOTREACHED(); } - callback.Run(); + if (schedule_future_reports) + ScheduleNextRtcpReport(); } -void AudioSender::SendEncodedAudioFrame( - scoped_ptr<EncodedAudioFrame> audio_frame, - const base::TimeTicks& recorded_time) { +void AudioSender::ScheduleNextResendCheck() { DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); - InitializeTimers(); - if (encryptor_) { - EncodedAudioFrame encrypted_frame; - if (!EncryptAudioFrame(*audio_frame.get(), &encrypted_frame)) { - // Logging already done. - return; + DCHECK(!last_send_time_.is_null()); + base::TimeDelta time_to_next = + last_send_time_ - cast_environment_->Clock()->NowTicks() + + target_playout_delay_; + time_to_next = std::max( + time_to_next, base::TimeDelta::FromMilliseconds(kMinSchedulingDelayMs)); + cast_environment_->PostDelayedTask( + CastEnvironment::MAIN, + FROM_HERE, + base::Bind(&AudioSender::ResendCheck, weak_factory_.GetWeakPtr()), + time_to_next); +} + +void AudioSender::ResendCheck() { + DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); + DCHECK(!last_send_time_.is_null()); + const base::TimeDelta time_since_last_send = + cast_environment_->Clock()->NowTicks() - last_send_time_; + if (time_since_last_send > target_playout_delay_) { + if (latest_acked_frame_id_ == last_sent_frame_id_) { + // Last frame acked, no point in doing anything + } else { + VLOG(1) << "ACK timeout; last acked frame: " << latest_acked_frame_id_; + ResendForKickstart(); } - rtp_sender_.IncomingEncodedAudioFrame(&encrypted_frame, recorded_time); - } else { - rtp_sender_.IncomingEncodedAudioFrame(audio_frame.get(), recorded_time); } + ScheduleNextResendCheck(); } -bool AudioSender::EncryptAudioFrame(const EncodedAudioFrame& audio_frame, - EncodedAudioFrame* encrypted_frame) { - DCHECK(encryptor_) << "Invalid state"; +void AudioSender::OnReceivedCastFeedback(const RtcpCastMessage& cast_feedback) { + DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); - if (!encryptor_->SetCounter(GetAesNonce(audio_frame.frame_id, iv_mask_))) { - NOTREACHED() << "Failed to set counter"; - return false; + if (rtcp_.is_rtt_available()) { + // Having the RTT values implies the receiver sent back a receiver report + // based on it having received a report from here. Therefore, ensure this + // sender stops aggressively sending reports. + if (num_aggressive_rtcp_reports_sent_ < kNumAggressiveReportsSentAtStart) { + VLOG(1) << "No longer a need to send reports aggressively (sent " + << num_aggressive_rtcp_reports_sent_ << ")."; + num_aggressive_rtcp_reports_sent_ = kNumAggressiveReportsSentAtStart; + ScheduleNextRtcpReport(); + } } - if (!encryptor_->Encrypt(audio_frame.data, &encrypted_frame->data)) { - NOTREACHED() << "Encrypt error"; - return false; + + if (last_send_time_.is_null()) + return; // Cannot get an ACK without having first sent a frame. + + if (cast_feedback.missing_frames_and_packets_.empty()) { + // We only count duplicate ACKs when we have sent newer frames. + if (latest_acked_frame_id_ == cast_feedback.ack_frame_id_ && + latest_acked_frame_id_ != last_sent_frame_id_) { + duplicate_ack_counter_++; + } else { + duplicate_ack_counter_ = 0; + } + // TODO(miu): The values "2" and "3" should be derived from configuration. + if (duplicate_ack_counter_ >= 2 && duplicate_ack_counter_ % 3 == 2) { + VLOG(1) << "Received duplicate ACK for frame " << latest_acked_frame_id_; + ResendForKickstart(); + } + } else { + // Only count duplicated ACKs if there is no NACK request in between. + // This is to avoid aggresive resend. + duplicate_ack_counter_ = 0; + + base::TimeDelta rtt; + base::TimeDelta avg_rtt; + base::TimeDelta min_rtt; + base::TimeDelta max_rtt; + rtcp_.Rtt(&rtt, &avg_rtt, &min_rtt, &max_rtt); + + // A NACK is also used to cancel pending re-transmissions. + transport_sender_->ResendPackets( + true, cast_feedback.missing_frames_and_packets_, false, min_rtt); } - encrypted_frame->codec = audio_frame.codec; - encrypted_frame->frame_id = audio_frame.frame_id; - encrypted_frame->samples = audio_frame.samples; - return true; -} -void AudioSender::ResendPackets( - const MissingFramesAndPacketsMap& missing_frames_and_packets) { - DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); - rtp_sender_.ResendPackets(missing_frames_and_packets); + const base::TimeTicks now = cast_environment_->Clock()->NowTicks(); + + const RtpTimestamp rtp_timestamp = + frame_id_to_rtp_timestamp_[cast_feedback.ack_frame_id_ & 0xff]; + cast_environment_->Logging()->InsertFrameEvent(now, + FRAME_ACK_RECEIVED, + AUDIO_EVENT, + rtp_timestamp, + cast_feedback.ack_frame_id_); + + const bool is_acked_out_of_order = + static_cast<int32>(cast_feedback.ack_frame_id_ - + latest_acked_frame_id_) < 0; + VLOG(2) << "Received ACK" << (is_acked_out_of_order ? " out-of-order" : "") + << " for frame " << cast_feedback.ack_frame_id_; + if (!is_acked_out_of_order) { + // Cancel resends of acked frames. + MissingFramesAndPacketsMap missing_frames_and_packets; + PacketIdSet missing; + while (latest_acked_frame_id_ != cast_feedback.ack_frame_id_) { + latest_acked_frame_id_++; + missing_frames_and_packets[latest_acked_frame_id_] = missing; + } + transport_sender_->ResendPackets( + true, missing_frames_and_packets, true, base::TimeDelta()); + latest_acked_frame_id_ = cast_feedback.ack_frame_id_; + } } -void AudioSender::IncomingRtcpPacket(const uint8* packet, size_t length, - const base::Closure callback) { +bool AudioSender::AreTooManyFramesInFlight() const { DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); - rtcp_.IncomingRtcpPacket(packet, length); - cast_environment_->PostTask(CastEnvironment::MAIN, FROM_HERE, callback); + int frames_in_flight = 0; + if (!last_send_time_.is_null()) { + frames_in_flight += + static_cast<int32>(last_sent_frame_id_ - latest_acked_frame_id_); + } + VLOG(2) << frames_in_flight + << " frames in flight; last sent: " << last_sent_frame_id_ + << " latest acked: " << latest_acked_frame_id_; + return frames_in_flight >= max_unacked_frames_; } -void AudioSender::ScheduleNextRtcpReport() { +void AudioSender::ResendForKickstart() { DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); - base::TimeDelta time_to_next = - rtcp_.TimeToSendNextRtcpReport() - cast_environment_->Clock()->NowTicks(); - - time_to_next = std::max(time_to_next, - base::TimeDelta::FromMilliseconds(kMinSchedulingDelayMs)); + DCHECK(!last_send_time_.is_null()); + VLOG(1) << "Resending last packet of frame " << last_sent_frame_id_ + << " to kick-start."; + // Send the first packet of the last encoded frame to kick start + // retransmission. This gives enough information to the receiver what + // packets and frames are missing. + MissingFramesAndPacketsMap missing_frames_and_packets; + PacketIdSet missing; + missing.insert(kRtcpCastLastPacket); + missing_frames_and_packets.insert( + std::make_pair(last_sent_frame_id_, missing)); + last_send_time_ = cast_environment_->Clock()->NowTicks(); - cast_environment_->PostDelayedTask(CastEnvironment::MAIN, FROM_HERE, - base::Bind(&AudioSender::SendRtcpReport, weak_factory_.GetWeakPtr()), - time_to_next); -} + base::TimeDelta rtt; + base::TimeDelta avg_rtt; + base::TimeDelta min_rtt; + base::TimeDelta max_rtt; + rtcp_.Rtt(&rtt, &avg_rtt, &min_rtt, &max_rtt); -void AudioSender::SendRtcpReport() { - DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); - // We don't send audio logging messages since all captured audio frames will - // be sent. - rtcp_.SendRtcpFromRtpSender(NULL); - ScheduleNextRtcpReport(); + // Sending this extra packet is to kick-start the session. There is + // no need to optimize re-transmission for this case. + transport_sender_->ResendPackets( + true, missing_frames_and_packets, false, min_rtt); } } // namespace cast diff --git a/chromium/media/cast/audio_sender/audio_sender.gypi b/chromium/media/cast/audio_sender/audio_sender.gypi deleted file mode 100644 index 9d84b79af8d..00000000000 --- a/chromium/media/cast/audio_sender/audio_sender.gypi +++ /dev/null @@ -1,32 +0,0 @@ -# Copyright 2013 The Chromium Authors. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - -{ - 'targets': [ - { - 'target_name': 'audio_sender', - 'type': 'static_library', - 'include_dirs': [ - '<(DEPTH)/', - '<(DEPTH)/third_party/', - ], - 'sources': [ - 'audio_encoder.h', - 'audio_encoder.cc', - 'audio_sender.h', - 'audio_sender.cc', - ], # source - 'dependencies': [ - '<(DEPTH)/crypto/crypto.gyp:crypto', - '<(DEPTH)/media/media.gyp:media', - '<(DEPTH)/media/media.gyp:shared_memory_support', - '<(DEPTH)/media/cast/rtcp/rtcp.gyp:cast_rtcp', - '<(DEPTH)/media/cast/net/rtp_sender/rtp_sender.gyp:*', - '<(DEPTH)/third_party/opus/opus.gyp:opus', - ], - }, - ], -} - - diff --git a/chromium/media/cast/audio_sender/audio_sender.h b/chromium/media/cast/audio_sender/audio_sender.h index 68f9e7a4172..80cf8a4e9e9 100644 --- a/chromium/media/cast/audio_sender/audio_sender.h +++ b/chromium/media/cast/audio_sender/audio_sender.h @@ -12,89 +12,146 @@ #include "base/threading/non_thread_safe.h" #include "base/time/tick_clock.h" #include "base/time/time.h" +#include "media/base/audio_bus.h" #include "media/cast/cast_config.h" #include "media/cast/cast_environment.h" -#include "media/cast/net/rtp_sender/rtp_sender.h" +#include "media/cast/logging/logging_defines.h" #include "media/cast/rtcp/rtcp.h" - -namespace crypto { - class Encryptor; -} - -namespace media { -class AudioBus; -} +#include "media/cast/rtp_timestamp_helper.h" namespace media { namespace cast { class AudioEncoder; -class LocalRtcpAudioSenderFeedback; -class LocalRtpSenderStatistics; -class PacedPacketSender; -// This class is not thread safe. -// It's only called from the main cast thread. -class AudioSender : public base::NonThreadSafe, +// Not thread safe. Only called from the main cast thread. +// This class owns all objects related to sending audio, objects that create RTP +// packets, congestion control, audio encoder, parsing and sending of +// RTCP packets. +// Additionally it posts a bunch of delayed tasks to the main thread for various +// timeouts. +class AudioSender : public RtcpSenderFeedback, + public base::NonThreadSafe, public base::SupportsWeakPtr<AudioSender> { public: AudioSender(scoped_refptr<CastEnvironment> cast_environment, const AudioSenderConfig& audio_config, - PacedPacketSender* const paced_packet_sender); + transport::CastTransportSender* const transport_sender); virtual ~AudioSender(); - // The |audio_bus| must be valid until the |done_callback| is called. - // The callback is called from the main cast thread as soon as the encoder is - // done with |audio_bus|; it does not mean that the encoded data has been - // sent out. - void InsertAudio(const AudioBus* audio_bus, - const base::TimeTicks& recorded_time, - const base::Closure& done_callback); - - // The audio_frame must be valid until the closure callback is called. - // The closure callback is called from the main cast thread as soon as - // the cast sender is done with the frame; it does not mean that the encoded - // frame has been sent out. - void InsertCodedAudioFrame(const EncodedAudioFrame* audio_frame, - const base::TimeTicks& recorded_time, - const base::Closure callback); + CastInitializationStatus InitializationResult() const { + return cast_initialization_status_; + } + + // Note: It is not guaranteed that |audio_frame| will actually be encoded and + // sent, if AudioSender detects too many frames in flight. Therefore, clients + // should be careful about the rate at which this method is called. + // + // Note: It is invalid to call this method if InitializationResult() returns + // anything but STATUS_AUDIO_INITIALIZED. + void InsertAudio(scoped_ptr<AudioBus> audio_bus, + const base::TimeTicks& recorded_time); // Only called from the main cast thread. - void IncomingRtcpPacket(const uint8* packet, size_t length, - const base::Closure callback); + void IncomingRtcpPacket(scoped_ptr<Packet> packet); protected: - void SendEncodedAudioFrame(scoped_ptr<EncodedAudioFrame> audio_frame, - const base::TimeTicks& recorded_time); + // Protected for testability. + virtual void OnReceivedCastFeedback(const RtcpCastMessage& cast_feedback) + OVERRIDE; private: - friend class LocalRtcpAudioSenderFeedback; + // Schedule and execute periodic sending of RTCP report. + void ScheduleNextRtcpReport(); + void SendRtcpReport(bool schedule_future_reports); + + // Schedule and execute periodic checks for re-sending packets. If no + // acknowledgements have been received for "too long," AudioSender will + // speculatively re-send certain packets of an unacked frame to kick-start + // re-transmission. This is a last resort tactic to prevent the session from + // getting stuck after a long outage. + void ScheduleNextResendCheck(); + void ResendCheck(); + void ResendForKickstart(); + + // Returns true if there are too many frames in flight, as defined by the + // configured target playout delay plus simple logic. When this is true, + // InsertAudio() will silenty drop frames instead of sending them to the audio + // encoder. + bool AreTooManyFramesInFlight() const; + + // Called by the |audio_encoder_| with the next EncodedFrame to send. + void SendEncodedAudioFrame(scoped_ptr<transport::EncodedFrame> audio_frame); + + const scoped_refptr<CastEnvironment> cast_environment_; + + // The total amount of time between a frame's capture/recording on the sender + // and its playback on the receiver (i.e., shown to a user). This is fixed as + // a value large enough to give the system sufficient time to encode, + // transmit/retransmit, receive, decode, and render; given its run-time + // environment (sender/receiver hardware performance, network conditions, + // etc.). + const base::TimeDelta target_playout_delay_; + + // Sends encoded frames over the configured transport (e.g., UDP). In + // Chromium, this could be a proxy that first sends the frames from a renderer + // process to the browser process over IPC, with the browser process being + // responsible for "packetizing" the frames and pushing packets into the + // network layer. + transport::CastTransportSender* const transport_sender_; + + // Maximum number of outstanding frames before the encoding and sending of + // new frames shall halt. + const int max_unacked_frames_; + + // Encodes AudioBuses into EncodedFrames. + scoped_ptr<AudioEncoder> audio_encoder_; + const int configured_encoder_bitrate_; + + // Manages sending/receiving of RTCP packets, including sender/receiver + // reports. + Rtcp rtcp_; - void ResendPackets( - const MissingFramesAndPacketsMap& missing_frames_and_packets); + // Records lip-sync (i.e., mapping of RTP <--> NTP timestamps), and + // extrapolates this mapping to any other point in time. + RtpTimestampHelper rtp_timestamp_helper_; - // Caller must allocate the destination |encrypted_frame|. The data member - // will be resized to hold the encrypted size. - bool EncryptAudioFrame(const EncodedAudioFrame& audio_frame, - EncodedAudioFrame* encrypted_frame); + // Counts how many RTCP reports are being "aggressively" sent (i.e., one per + // frame) at the start of the session. Once a threshold is reached, RTCP + // reports are instead sent at the configured interval + random drift. + int num_aggressive_rtcp_reports_sent_; - void ScheduleNextRtcpReport(); - void SendRtcpReport(); + // This is "null" until the first frame is sent. Thereafter, this tracks the + // last time any frame was sent or re-sent. + base::TimeTicks last_send_time_; - void InitializeTimers(); + // The ID of the last frame sent. Logic throughout AudioSender assumes this + // can safely wrap-around. This member is invalid until + // |!last_send_time_.is_null()|. + uint32 last_sent_frame_id_; - base::WeakPtrFactory<AudioSender> weak_factory_; + // The ID of the latest (not necessarily the last) frame that has been + // acknowledged. Logic throughout AudioSender assumes this can safely + // wrap-around. This member is invalid until |!last_send_time_.is_null()|. + uint32 latest_acked_frame_id_; - scoped_refptr<CastEnvironment> cast_environment_; - scoped_refptr<AudioEncoder> audio_encoder_; - RtpSender rtp_sender_; - scoped_ptr<LocalRtpSenderStatistics> rtp_audio_sender_statistics_; - scoped_ptr<LocalRtcpAudioSenderFeedback> rtcp_feedback_; - Rtcp rtcp_; - bool initialized_; - scoped_ptr<crypto::Encryptor> encryptor_; - std::string iv_mask_; + // Counts the number of duplicate ACK that are being received. When this + // number reaches a threshold, the sender will take this as a sign that the + // receiver hasn't yet received the first packet of the next frame. In this + // case, AudioSender will trigger a re-send of the next frame. + int duplicate_ack_counter_; + + // If this sender is ready for use, this is STATUS_AUDIO_INITIALIZED. + CastInitializationStatus cast_initialization_status_; + + // This is a "good enough" mapping for finding the RTP timestamp associated + // with a video frame. The key is the lowest 8 bits of frame id (which is + // what is sent via RTCP). This map is used for logging purposes. + RtpTimestamp frame_id_to_rtp_timestamp_[256]; + + // NOTE: Weak pointers must be invalidated before all other member variables. + base::WeakPtrFactory<AudioSender> weak_factory_; DISALLOW_COPY_AND_ASSIGN(AudioSender); }; diff --git a/chromium/media/cast/audio_sender/audio_sender_unittest.cc b/chromium/media/cast/audio_sender/audio_sender_unittest.cc index 65c2e622d8f..51edd496028 100644 --- a/chromium/media/cast/audio_sender/audio_sender_unittest.cc +++ b/chromium/media/cast/audio_sender/audio_sender_unittest.cc @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include <stdint.h> + #include "base/bind.h" #include "base/bind_helpers.h" #include "base/memory/scoped_ptr.h" @@ -10,92 +12,129 @@ #include "media/cast/audio_sender/audio_sender.h" #include "media/cast/cast_config.h" #include "media/cast/cast_environment.h" -#include "media/cast/net/pacing/mock_paced_packet_sender.h" -#include "media/cast/test/audio_utility.h" -#include "media/cast/test/fake_task_runner.h" +#include "media/cast/rtcp/rtcp.h" +#include "media/cast/test/fake_single_thread_task_runner.h" +#include "media/cast/test/utility/audio_utility.h" +#include "media/cast/transport/cast_transport_config.h" +#include "media/cast/transport/cast_transport_sender_impl.h" #include "testing/gtest/include/gtest/gtest.h" namespace media { namespace cast { -static const int64 kStartMillisecond = GG_INT64_C(12345678900000); +class TestPacketSender : public transport::PacketSender { + public: + TestPacketSender() : number_of_rtp_packets_(0), number_of_rtcp_packets_(0) {} + + virtual bool SendPacket(transport::PacketRef packet, + const base::Closure& cb) OVERRIDE { + if (Rtcp::IsRtcpPacket(&packet->data[0], packet->data.size())) { + ++number_of_rtcp_packets_; + } else { + // Check that at least one RTCP packet was sent before the first RTP + // packet. This confirms that the receiver will have the necessary lip + // sync info before it has to calculate the playout time of the first + // frame. + if (number_of_rtp_packets_ == 0) + EXPECT_LE(1, number_of_rtcp_packets_); + ++number_of_rtp_packets_; + } + return true; + } + + int number_of_rtp_packets() const { return number_of_rtp_packets_; } + + int number_of_rtcp_packets() const { return number_of_rtcp_packets_; } -using testing::_; -using testing::AtLeast; + private: + int number_of_rtp_packets_; + int number_of_rtcp_packets_; + + DISALLOW_COPY_AND_ASSIGN(TestPacketSender); +}; class AudioSenderTest : public ::testing::Test { protected: AudioSenderTest() { InitializeMediaLibraryForTesting(); - testing_clock_.Advance( - base::TimeDelta::FromMilliseconds(kStartMillisecond)); - } - - virtual void SetUp() { - task_runner_ = new test::FakeTaskRunner(&testing_clock_); - cast_environment_ = new CastEnvironment(&testing_clock_, task_runner_, - task_runner_, task_runner_, task_runner_, task_runner_, - GetDefaultCastLoggingConfig()); - audio_config_.codec = kOpus; + testing_clock_ = new base::SimpleTestTickClock(); + testing_clock_->Advance(base::TimeTicks::Now() - base::TimeTicks()); + task_runner_ = new test::FakeSingleThreadTaskRunner(testing_clock_); + cast_environment_ = + new CastEnvironment(scoped_ptr<base::TickClock>(testing_clock_).Pass(), + task_runner_, + task_runner_, + task_runner_); + audio_config_.codec = transport::kOpus; audio_config_.use_external_encoder = false; audio_config_.frequency = kDefaultAudioSamplingRate; audio_config_.channels = 2; audio_config_.bitrate = kDefaultAudioEncoderBitrate; - audio_config_.rtp_payload_type = 127; - - audio_sender_.reset( - new AudioSender(cast_environment_, audio_config_, &mock_transport_)); + audio_config_.rtp_config.payload_type = 127; + + net::IPEndPoint dummy_endpoint; + + transport_sender_.reset(new transport::CastTransportSenderImpl( + NULL, + testing_clock_, + dummy_endpoint, + base::Bind(&UpdateCastTransportStatus), + transport::BulkRawEventsCallback(), + base::TimeDelta(), + task_runner_, + &transport_)); + audio_sender_.reset(new AudioSender( + cast_environment_, audio_config_, transport_sender_.get())); + task_runner_->RunTasks(); } virtual ~AudioSenderTest() {} - base::SimpleTestTickClock testing_clock_; - MockPacedPacketSender mock_transport_; - scoped_refptr<test::FakeTaskRunner> task_runner_; + static void UpdateCastTransportStatus(transport::CastTransportStatus status) { + EXPECT_EQ(transport::TRANSPORT_AUDIO_INITIALIZED, status); + } + + base::SimpleTestTickClock* testing_clock_; // Owned by CastEnvironment. + TestPacketSender transport_; + scoped_ptr<transport::CastTransportSenderImpl> transport_sender_; + scoped_refptr<test::FakeSingleThreadTaskRunner> task_runner_; scoped_ptr<AudioSender> audio_sender_; scoped_refptr<CastEnvironment> cast_environment_; AudioSenderConfig audio_config_; }; TEST_F(AudioSenderTest, Encode20ms) { - EXPECT_CALL(mock_transport_, SendPackets(_)).Times(AtLeast(1)); - const base::TimeDelta kDuration = base::TimeDelta::FromMilliseconds(20); - scoped_ptr<AudioBus> bus(TestAudioBusFactory( - audio_config_.channels, audio_config_.frequency, - TestAudioBusFactory::kMiddleANoteFreq, 0.5f).NextAudioBus(kDuration)); - - base::TimeTicks recorded_time = base::TimeTicks::Now(); - audio_sender_->InsertAudio( - bus.get(), recorded_time, - base::Bind(base::IgnoreResult(&scoped_ptr<AudioBus>::release), - base::Unretained(&bus))); - task_runner_->RunTasks(); + scoped_ptr<AudioBus> bus( + TestAudioBusFactory(audio_config_.channels, + audio_config_.frequency, + TestAudioBusFactory::kMiddleANoteFreq, + 0.5f).NextAudioBus(kDuration)); - EXPECT_TRUE(!bus) << "AudioBus wasn't released after use."; + audio_sender_->InsertAudio(bus.Pass(), testing_clock_->NowTicks()); + task_runner_->RunTasks(); + EXPECT_LE(1, transport_.number_of_rtp_packets()); + EXPECT_LE(1, transport_.number_of_rtcp_packets()); } TEST_F(AudioSenderTest, RtcpTimer) { - EXPECT_CALL(mock_transport_, SendPackets(_)).Times(AtLeast(1)); - EXPECT_CALL(mock_transport_, SendRtcpPacket(_)).Times(1); - const base::TimeDelta kDuration = base::TimeDelta::FromMilliseconds(20); - scoped_ptr<AudioBus> bus(TestAudioBusFactory( - audio_config_.channels, audio_config_.frequency, - TestAudioBusFactory::kMiddleANoteFreq, 0.5f).NextAudioBus(kDuration)); - - base::TimeTicks recorded_time = base::TimeTicks::Now(); - audio_sender_->InsertAudio( - bus.get(), recorded_time, - base::Bind(base::IgnoreResult(&scoped_ptr<AudioBus>::release), - base::Unretained(&bus))); + scoped_ptr<AudioBus> bus( + TestAudioBusFactory(audio_config_.channels, + audio_config_.frequency, + TestAudioBusFactory::kMiddleANoteFreq, + 0.5f).NextAudioBus(kDuration)); + + audio_sender_->InsertAudio(bus.Pass(), testing_clock_->NowTicks()); task_runner_->RunTasks(); // Make sure that we send at least one RTCP packet. base::TimeDelta max_rtcp_timeout = base::TimeDelta::FromMilliseconds(1 + kDefaultRtcpIntervalMs * 3 / 2); - testing_clock_.Advance(max_rtcp_timeout); + testing_clock_->Advance(max_rtcp_timeout); task_runner_->RunTasks(); + EXPECT_LE(1, transport_.number_of_rtp_packets()); + EXPECT_LE(1, transport_.number_of_rtcp_packets()); } } // namespace cast diff --git a/chromium/media/cast/base/clock_drift_smoother.cc b/chromium/media/cast/base/clock_drift_smoother.cc new file mode 100644 index 00000000000..ca0380533ee --- /dev/null +++ b/chromium/media/cast/base/clock_drift_smoother.cc @@ -0,0 +1,58 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/cast/base/clock_drift_smoother.h" + +#include "base/logging.h" + +namespace media { +namespace cast { + +ClockDriftSmoother::ClockDriftSmoother(base::TimeDelta time_constant) + : time_constant_(time_constant), + estimate_us_(0.0) { + DCHECK(time_constant_ > base::TimeDelta()); +} + +ClockDriftSmoother::~ClockDriftSmoother() {} + +base::TimeDelta ClockDriftSmoother::Current() const { + DCHECK(!last_update_time_.is_null()); + return base::TimeDelta::FromMicroseconds( + static_cast<int64>(estimate_us_ + 0.5)); // Round to nearest microsecond. +} + +void ClockDriftSmoother::Reset(base::TimeTicks now, + base::TimeDelta measured_offset) { + DCHECK(!now.is_null()); + last_update_time_ = now; + estimate_us_ = measured_offset.InMicroseconds(); +} + +void ClockDriftSmoother::Update(base::TimeTicks now, + base::TimeDelta measured_offset) { + DCHECK(!now.is_null()); + if (last_update_time_.is_null()) { + Reset(now, measured_offset); + } else if (now < last_update_time_) { + // |now| is not monotonically non-decreasing. + NOTREACHED(); + } else { + const double elapsed_us = (now - last_update_time_).InMicroseconds(); + last_update_time_ = now; + const double weight = + elapsed_us / (elapsed_us + time_constant_.InMicroseconds()); + estimate_us_ = weight * measured_offset.InMicroseconds() + + (1.0 - weight) * estimate_us_; + } +} + +// static +base::TimeDelta ClockDriftSmoother::GetDefaultTimeConstant() { + static const int kDefaultTimeConstantInSeconds = 30; + return base::TimeDelta::FromSeconds(kDefaultTimeConstantInSeconds); +} + +} // namespace cast +} // namespace media diff --git a/chromium/media/cast/base/clock_drift_smoother.h b/chromium/media/cast/base/clock_drift_smoother.h new file mode 100644 index 00000000000..67de4cb51a8 --- /dev/null +++ b/chromium/media/cast/base/clock_drift_smoother.h @@ -0,0 +1,52 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_CAST_BASE_CLOCK_DRIFT_SMOOTHER_H_ +#define MEDIA_CAST_BASE_CLOCK_DRIFT_SMOOTHER_H_ + +#include "base/time/time.h" + +namespace media { +namespace cast { + +// Tracks the jitter and drift between clocks, providing a smoothed offset. +// Internally, a Simple IIR filter is used to maintain a running average that +// moves at a rate based on the passage of time. +class ClockDriftSmoother { + public: + // |time_constant| is the amount of time an impulse signal takes to decay by + // ~62.6%. Interpretation: If the value passed to several Update() calls is + // held constant for T seconds, then the running average will have moved + // towards the value by ~62.6% from where it started. + explicit ClockDriftSmoother(base::TimeDelta time_constant); + ~ClockDriftSmoother(); + + // Returns the current offset. + base::TimeDelta Current() const; + + // Discard all history and reset to exactly |offset|, measured |now|. + void Reset(base::TimeTicks now, base::TimeDelta offset); + + // Update the current offset, which was measured |now|. The weighting that + // |measured_offset| will have on the running average is influenced by how + // much time has passed since the last call to this method (or Reset()). + // |now| should be monotonically non-decreasing over successive calls of this + // method. + void Update(base::TimeTicks now, base::TimeDelta measured_offset); + + // Returns a time constant suitable for most use cases, where the clocks + // are expected to drift very little with respect to each other, and the + // jitter caused by clock imprecision is effectively canceled out. + static base::TimeDelta GetDefaultTimeConstant(); + + private: + const base::TimeDelta time_constant_; + base::TimeTicks last_update_time_; + double estimate_us_; +}; + +} // namespace cast +} // namespace media + +#endif // MEDIA_CAST_BASE_CLOCK_DRIFT_SMOOTHER_H_ diff --git a/chromium/media/cast/cast.gyp b/chromium/media/cast/cast.gyp index 702272fb289..5de8796079a 100644 --- a/chromium/media/cast/cast.gyp +++ b/chromium/media/cast/cast.gyp @@ -7,15 +7,25 @@ 'include_tests%': 1, 'chromium_code': 1, }, + 'conditions': [ + ['include_tests==1', { + 'includes': [ 'cast_testing.gypi' ] + }], + ], 'targets': [ { - 'target_name': 'cast_config', + 'target_name': 'cast_base', 'type': 'static_library', 'include_dirs': [ '<(DEPTH)/', ], 'dependencies': [ + 'cast_logging_proto', '<(DEPTH)/base/base.gyp:base', + '<(DEPTH)/net/net.gyp:net', + ], + 'export_dependent_settings': [ + 'cast_logging_proto', ], 'sources': [ 'cast_config.cc', @@ -23,136 +33,193 @@ 'cast_defines.h', 'cast_environment.cc', 'cast_environment.h', + 'base/clock_drift_smoother.cc', + 'base/clock_drift_smoother.h', + 'logging/encoding_event_subscriber.cc', + 'logging/encoding_event_subscriber.h', + 'logging/log_deserializer.cc', + 'logging/log_deserializer.h', + 'logging/log_serializer.cc', + 'logging/log_serializer.h', 'logging/logging_defines.cc', 'logging/logging_defines.h', 'logging/logging_impl.cc', 'logging/logging_impl.h', 'logging/logging_raw.cc', 'logging/logging_raw.h', - 'logging/logging_stats.cc', - 'logging/logging_stats.h', + 'logging/raw_event_subscriber.h', + 'logging/raw_event_subscriber_bundle.cc', + 'logging/raw_event_subscriber_bundle.h', + 'logging/receiver_time_offset_estimator.h', + 'logging/receiver_time_offset_estimator_impl.cc', + 'logging/receiver_time_offset_estimator_impl.h', + 'logging/simple_event_subscriber.cc', + 'logging/simple_event_subscriber.h', + 'logging/stats_event_subscriber.cc', + 'logging/stats_event_subscriber.h', + 'rtp_timestamp_helper.cc', + 'rtp_timestamp_helper.h', + 'transport/cast_transport_config.cc', + 'transport/cast_transport_config.h', + 'transport/cast_transport_defines.h', + 'transport/cast_transport_sender.h', + ], # source + }, + { + 'target_name': 'cast_logging_proto', + 'type': 'static_library', + 'include_dirs': [ + '<(DEPTH)/', + ], + 'sources': [ + 'logging/proto/proto_utils.cc', + 'logging/proto/raw_events.proto', + ], + 'variables': { + 'proto_in_dir': 'logging/proto', + 'proto_out_dir': 'media/cast/logging/proto', + }, + 'includes': ['../../build/protoc.gypi'], + }, + { + 'target_name': 'cast_receiver', + 'type': 'static_library', + 'include_dirs': [ + '<(DEPTH)/', + ], + 'dependencies': [ + 'cast_base', + 'cast_rtcp', + 'cast_transport', + '<(DEPTH)/base/base.gyp:base', + '<(DEPTH)/media/media.gyp:media', + '<(DEPTH)/media/media.gyp:shared_memory_support', + '<(DEPTH)/third_party/opus/opus.gyp:opus', + '<(DEPTH)/third_party/libvpx/libvpx.gyp:libvpx', + '<(DEPTH)/ui/gfx/gfx.gyp:gfx_geometry', + ], + 'sources': [ + 'cast_receiver.h', + 'framer/cast_message_builder.cc', + 'framer/cast_message_builder.h', + 'framer/frame_buffer.cc', + 'framer/frame_buffer.h', + 'framer/frame_id_map.cc', + 'framer/frame_id_map.h', + 'framer/framer.cc', + 'framer/framer.h', + 'receiver/audio_decoder.cc', + 'receiver/audio_decoder.h', + 'receiver/cast_receiver_impl.cc', + 'receiver/cast_receiver_impl.h', + 'receiver/frame_receiver.cc', + 'receiver/frame_receiver.h', + 'receiver/video_decoder.cc', + 'receiver/video_decoder.h', + 'rtp_receiver/receiver_stats.cc', + 'rtp_receiver/receiver_stats.h', + 'rtp_receiver/rtp_receiver_defines.cc', + 'rtp_receiver/rtp_receiver_defines.h', + 'rtp_receiver/rtp_parser/rtp_parser.cc', + 'rtp_receiver/rtp_parser/rtp_parser.h', + ], # source + }, + { + 'target_name': 'cast_rtcp', + 'type': 'static_library', + 'include_dirs': [ + '<(DEPTH)/', + ], + 'dependencies': [ + 'cast_base', + 'cast_transport', + '<(DEPTH)/base/base.gyp:base', + '<(DEPTH)/net/net.gyp:net', + ], + 'sources': [ + 'rtcp/rtcp_defines.cc', + 'rtcp/rtcp_defines.h', + 'rtcp/rtcp.h', + 'rtcp/rtcp.cc', + 'rtcp/rtcp_receiver.cc', + 'rtcp/rtcp_receiver.h', + 'rtcp/rtcp_sender.cc', + 'rtcp/rtcp_sender.h', + 'rtcp/rtcp_utility.cc', + 'rtcp/rtcp_utility.h', + 'rtcp/receiver_rtcp_event_subscriber.cc', + 'rtcp/receiver_rtcp_event_subscriber.cc', + ], # source + }, + { + 'target_name': 'cast_sender', + 'type': 'static_library', + 'include_dirs': [ + '<(DEPTH)/', + ], + 'dependencies': [ + 'cast_base', + 'cast_rtcp', + 'cast_transport', + '<(DEPTH)/media/media.gyp:media', + '<(DEPTH)/media/media.gyp:shared_memory_support', + '<(DEPTH)/third_party/opus/opus.gyp:opus', + '<(DEPTH)/third_party/libvpx/libvpx.gyp:libvpx', + ], # dependencies + 'sources': [ + 'audio_sender/audio_encoder.h', + 'audio_sender/audio_encoder.cc', + 'audio_sender/audio_sender.h', + 'audio_sender/audio_sender.cc', + 'cast_sender.h', + 'cast_sender_impl.cc', + 'cast_sender_impl.h', + 'congestion_control/congestion_control.h', + 'congestion_control/congestion_control.cc', + 'video_sender/codecs/vp8/vp8_encoder.cc', + 'video_sender/codecs/vp8/vp8_encoder.h', + 'video_sender/external_video_encoder.h', + 'video_sender/external_video_encoder.cc', + 'video_sender/fake_software_video_encoder.h', + 'video_sender/fake_software_video_encoder.cc', + 'video_sender/software_video_encoder.h', + 'video_sender/video_encoder.h', + 'video_sender/video_encoder_impl.h', + 'video_sender/video_encoder_impl.cc', + 'video_sender/video_sender.h', + 'video_sender/video_sender.cc', + ], # source + }, + { + 'target_name': 'cast_transport', + 'type': 'static_library', + 'include_dirs': [ + '<(DEPTH)/', + ], + 'dependencies': [ + 'cast_base', + '<(DEPTH)/base/base.gyp:base', + '<(DEPTH)/crypto/crypto.gyp:crypto', + '<(DEPTH)/net/net.gyp:net', + ], + 'sources': [ + 'transport/cast_transport_sender_impl.cc', + 'transport/cast_transport_sender_impl.h', + 'transport/pacing/paced_sender.cc', + 'transport/pacing/paced_sender.h', + 'transport/rtcp/rtcp_builder.cc', + 'transport/rtcp/rtcp_builder.h', + 'transport/rtp_sender/packet_storage/packet_storage.cc', + 'transport/rtp_sender/packet_storage/packet_storage.h', + 'transport/rtp_sender/rtp_packetizer/rtp_packetizer.cc', + 'transport/rtp_sender/rtp_packetizer/rtp_packetizer.h', + 'transport/rtp_sender/rtp_sender.cc', + 'transport/rtp_sender/rtp_sender.h', + 'transport/transport/udp_transport.cc', + 'transport/transport/udp_transport.h', + 'transport/utility/transport_encryption_handler.cc', + 'transport/utility/transport_encryption_handler.h', ], # source }, - ], # targets, - 'conditions': [ - ['include_tests==1', { - 'targets': [ - { - 'target_name': 'cast_unittests', - 'type': '<(gtest_target_type)', - 'dependencies': [ - 'cast_config', - 'cast_receiver.gyp:cast_receiver', - 'cast_sender.gyp:cast_sender', - 'test/utility/utility.gyp:cast_test_utility', - '<(DEPTH)/base/base.gyp:run_all_unittests', - '<(DEPTH)/base/base.gyp:test_support_base', - '<(DEPTH)/crypto/crypto.gyp:crypto', - '<(DEPTH)/net/net.gyp:net', - '<(DEPTH)/testing/gmock.gyp:gmock', - '<(DEPTH)/testing/gtest.gyp:gtest', - ], - 'include_dirs': [ - '<(DEPTH)/', - '<(DEPTH)/third_party/', - '<(DEPTH)/third_party/webrtc/', - ], - 'sources': [ - 'audio_receiver/audio_decoder_unittest.cc', - 'audio_receiver/audio_receiver_unittest.cc', - 'audio_sender/audio_encoder_unittest.cc', - 'audio_sender/audio_sender_unittest.cc', - 'congestion_control/congestion_control_unittest.cc', - 'framer/cast_message_builder_unittest.cc', - 'framer/frame_buffer_unittest.cc', - 'framer/framer_unittest.cc', - 'net/pacing/mock_paced_packet_sender.cc', - 'net/pacing/mock_paced_packet_sender.h', - 'net/pacing/paced_sender_unittest.cc', - 'rtcp/mock_rtcp_receiver_feedback.cc', - 'rtcp/mock_rtcp_receiver_feedback.h', - 'rtcp/mock_rtcp_sender_feedback.cc', - 'rtcp/mock_rtcp_sender_feedback.h', - 'rtcp/rtcp_receiver_unittest.cc', - 'rtcp/rtcp_sender_unittest.cc', - 'rtcp/rtcp_unittest.cc', - 'rtp_receiver/rtp_receiver_defines.h', - 'rtp_receiver/mock_rtp_payload_feedback.cc', - 'rtp_receiver/mock_rtp_payload_feedback.h', - 'rtp_receiver/receiver_stats_unittest.cc', - 'rtp_receiver/rtp_parser/test/rtp_packet_builder.cc', - 'rtp_receiver/rtp_parser/rtp_parser_unittest.cc', - 'net/rtp_sender/packet_storage/packet_storage_unittest.cc', - 'net/rtp_sender/rtp_packetizer/rtp_packetizer_unittest.cc', - 'net/rtp_sender/rtp_packetizer/test/rtp_header_parser.cc', - 'net/rtp_sender/rtp_packetizer/test/rtp_header_parser.h', - 'test/crypto_utility.cc', - 'test/crypto_utility.h', - 'test/encode_decode_test.cc', - 'test/end2end_unittest.cc', - 'video_receiver/video_decoder_unittest.cc', - 'video_receiver/video_receiver_unittest.cc', - 'video_sender/mock_video_encoder_controller.cc', - 'video_sender/mock_video_encoder_controller.h', - 'video_sender/video_encoder_unittest.cc', - 'video_sender/video_sender_unittest.cc', - ], # source - }, - { - 'target_name': 'cast_sender_app', - 'type': 'executable', - 'include_dirs': [ - '<(DEPTH)/', - ], - 'dependencies': [ - 'cast_config', - '<(DEPTH)/ui/gfx/gfx.gyp:gfx', - '<(DEPTH)/net/net.gyp:net_test_support', - '<(DEPTH)/media/cast/cast_sender.gyp:*', - '<(DEPTH)/media/media.gyp:media', - '<(DEPTH)/testing/gtest.gyp:gtest', - '<(DEPTH)/third_party/opus/opus.gyp:opus', - '<(DEPTH)/media/cast/test/transport/transport.gyp:cast_transport', - '<(DEPTH)/media/cast/test/utility/utility.gyp:cast_test_utility', - ], - 'sources': [ - '<(DEPTH)/media/cast/test/sender.cc', - ], - }, - { - 'target_name': 'cast_receiver_app', - 'type': 'executable', - 'include_dirs': [ - '<(DEPTH)/', - ], - 'dependencies': [ - 'cast_config', - '<(DEPTH)/ui/gfx/gfx.gyp:gfx', - '<(DEPTH)/net/net.gyp:net_test_support', - '<(DEPTH)/media/cast/cast_receiver.gyp:*', - '<(DEPTH)/media/media.gyp:media', - '<(DEPTH)/testing/gtest.gyp:gtest', - '<(DEPTH)/media/cast/test/transport/transport.gyp:cast_transport', - '<(DEPTH)/media/cast/test/utility/utility.gyp:cast_test_utility', - '<(DEPTH)/third_party/libyuv/libyuv.gyp:libyuv', - ], - 'sources': [ - '<(DEPTH)/media/cast/test/receiver.cc', - ], - 'conditions': [ - ['OS == "linux"', { - 'sources': [ - '<(DEPTH)/media/cast/test/linux_output_window.cc', - '<(DEPTH)/media/cast/test/linux_output_window.h', - ], - 'libraries': [ - '-lXext', - '-lX11', - ], - }], - ], - }, - ], # targets - }], # include_tests ], } diff --git a/chromium/media/cast/cast_config.cc b/chromium/media/cast/cast_config.cc index 6c324bd8759..0e7953af01e 100644 --- a/chromium/media/cast/cast_config.cc +++ b/chromium/media/cast/cast_config.cc @@ -7,48 +7,59 @@ namespace media { namespace cast { +// TODO(miu): Revisit code factoring of these structs. There are a number of +// common elements between them all, so it might be reasonable to only have one +// or two structs; or, at least a common base class. + +// TODO(miu): Make sure all POD members are initialized by ctors. Policy +// decision: Reasonable defaults or use invalid placeholder values to expose +// unset members? + +// TODO(miu): Provide IsValidConfig() functions? + +// TODO(miu): Throughout the code, there is a lot of copy-and-paste of the same +// calculations based on these config values. So, why don't we add methods to +// these classes to centralize the logic? + VideoSenderConfig::VideoSenderConfig() - : rtcp_interval(kDefaultRtcpIntervalMs), + : incoming_feedback_ssrc(0), + rtcp_interval(kDefaultRtcpIntervalMs), rtcp_mode(kRtcpReducedSize), - rtp_history_ms(kDefaultRtpHistoryMs), - rtp_max_delay_ms(kDefaultRtpMaxDelayMs), + use_external_encoder(false), + width(0), + height(0), congestion_control_back_off(kDefaultCongestionControlBackOff), + max_bitrate(5000000), + min_bitrate(1000000), + start_bitrate(5000000), max_qp(kDefaultMaxQp), min_qp(kDefaultMinQp), max_frame_rate(kDefaultMaxFrameRate), - max_number_of_video_buffers_used(kDefaultNumberOfVideoBuffers) {} + max_number_of_video_buffers_used(kDefaultNumberOfVideoBuffers), + codec(transport::kVp8), + number_of_encode_threads(1) {} AudioSenderConfig::AudioSenderConfig() - : rtcp_interval(kDefaultRtcpIntervalMs), + : incoming_feedback_ssrc(0), + rtcp_interval(kDefaultRtcpIntervalMs), rtcp_mode(kRtcpReducedSize), - rtp_history_ms(kDefaultRtpHistoryMs), - rtp_max_delay_ms(kDefaultRtpMaxDelayMs) {} + use_external_encoder(false), + frequency(0), + channels(0), + bitrate(0) {} -AudioReceiverConfig::AudioReceiverConfig() - : rtcp_interval(kDefaultRtcpIntervalMs), - rtcp_mode(kRtcpReducedSize), - rtp_max_delay_ms(kDefaultRtpMaxDelayMs) {} - -VideoReceiverConfig::VideoReceiverConfig() - : rtcp_interval(kDefaultRtcpIntervalMs), +FrameReceiverConfig::FrameReceiverConfig() + : feedback_ssrc(0), + incoming_ssrc(0), + rtcp_interval(kDefaultRtcpIntervalMs), rtcp_mode(kRtcpReducedSize), rtp_max_delay_ms(kDefaultRtpMaxDelayMs), - max_frame_rate(kDefaultMaxFrameRate), - decoder_faster_than_max_frame_rate(true) {} - -EncodedVideoFrame::EncodedVideoFrame() {} -EncodedVideoFrame::~EncodedVideoFrame() {} - -EncodedAudioFrame::EncodedAudioFrame() {} -EncodedAudioFrame::~EncodedAudioFrame() {} - -PcmAudioFrame::PcmAudioFrame() {} -PcmAudioFrame::~PcmAudioFrame() {} + rtp_payload_type(0), + frequency(0), + channels(0), + max_frame_rate(0) {} -// static -void PacketReceiver::DeletePacket(const uint8* packet) { - delete [] packet; -} +FrameReceiverConfig::~FrameReceiverConfig() {} } // namespace cast } // namespace media diff --git a/chromium/media/cast/cast_config.h b/chromium/media/cast/cast_config.h index 27cc67e5dae..ea25d6b6cf7 100644 --- a/chromium/media/cast/cast_config.h +++ b/chromium/media/cast/cast_config.h @@ -12,65 +12,53 @@ #include "base/basictypes.h" #include "base/callback.h" #include "base/memory/ref_counted.h" +#include "base/memory/shared_memory.h" +#include "base/single_thread_task_runner.h" #include "media/cast/cast_defines.h" +#include "media/cast/transport/cast_transport_config.h" namespace media { +class VideoEncodeAccelerator; + namespace cast { enum RtcpMode { - kRtcpCompound, // Compound RTCP mode is described by RFC 4585. + kRtcpCompound, // Compound RTCP mode is described by RFC 4585. kRtcpReducedSize, // Reduced-size RTCP mode is described by RFC 5506. }; -enum VideoCodec { - kVp8, - kH264, - kExternalVideo, -}; - -enum AudioCodec { - kOpus, - kPcm16, - kExternalAudio, -}; - +// TODO(miu): Merge AudioSenderConfig and VideoSenderConfig and make their +// naming/documentation consistent with FrameReceiverConfig. struct AudioSenderConfig { AudioSenderConfig(); - uint32 sender_ssrc; + // The sender ssrc is in rtp_config.ssrc. uint32 incoming_feedback_ssrc; int rtcp_interval; std::string rtcp_c_name; RtcpMode rtcp_mode; - int rtp_history_ms; // The time RTP packets are stored for retransmissions. - int rtp_max_delay_ms; - int rtp_payload_type; + transport::RtpConfig rtp_config; bool use_external_encoder; int frequency; int channels; int bitrate; // Set to <= 0 for "auto variable bitrate" (libopus knows best). - AudioCodec codec; - - std::string aes_key; // Binary string of size kAesKeySize. - std::string aes_iv_mask; // Binary string of size kAesKeySize. + transport::AudioCodec codec; }; struct VideoSenderConfig { VideoSenderConfig(); - uint32 sender_ssrc; + // The sender ssrc is in rtp_config.ssrc. uint32 incoming_feedback_ssrc; int rtcp_interval; std::string rtcp_c_name; RtcpMode rtcp_mode; - int rtp_history_ms; // The time RTP packets are stored for retransmissions. - int rtp_max_delay_ms; - int rtp_payload_type; + transport::RtpConfig rtp_config; bool use_external_encoder; int width; // Incoming frames will be scaled to this size. @@ -84,148 +72,92 @@ struct VideoSenderConfig { int min_qp; int max_frame_rate; int max_number_of_video_buffers_used; // Max value depend on codec. - VideoCodec codec; - int number_of_cores; - - std::string aes_key; // Binary string of size kAesKeySize. - std::string aes_iv_mask; // Binary string of size kAesKeySize. + transport::VideoCodec codec; + int number_of_encode_threads; }; -struct AudioReceiverConfig { - AudioReceiverConfig(); - - uint32 feedback_ssrc; - uint32 incoming_ssrc; - - int rtcp_interval; - std::string rtcp_c_name; - RtcpMode rtcp_mode; - - // The time the receiver is prepared to wait for retransmissions. - int rtp_max_delay_ms; - int rtp_payload_type; - - bool use_external_decoder; - int frequency; - int channels; - AudioCodec codec; - - std::string aes_key; // Binary string of size kAesKeySize. - std::string aes_iv_mask; // Binary string of size kAesKeySize. -}; +// TODO(miu): Naming and minor type changes are badly needed in a later CL. +struct FrameReceiverConfig { + FrameReceiverConfig(); + ~FrameReceiverConfig(); -struct VideoReceiverConfig { - VideoReceiverConfig(); + // The receiver's SSRC identifier. + uint32 feedback_ssrc; // TODO(miu): Rename to receiver_ssrc for clarity. - uint32 feedback_ssrc; - uint32 incoming_ssrc; + // The sender's SSRC identifier. + uint32 incoming_ssrc; // TODO(miu): Rename to sender_ssrc for clarity. + // Mean interval (in milliseconds) between RTCP reports. + // TODO(miu): Remove this since it's never not kDefaultRtcpIntervalMs. int rtcp_interval; - std::string rtcp_c_name; - RtcpMode rtcp_mode; - - // The time the receiver is prepared to wait for retransmissions. - int rtp_max_delay_ms; - int rtp_payload_type; - - bool use_external_decoder; - int max_frame_rate; - - // Some HW decoders can not run faster than the frame rate, preventing it - // from catching up after a glitch. - bool decoder_faster_than_max_frame_rate; - VideoCodec codec; - - std::string aes_key; // Binary string of size kAesKeySize. - std::string aes_iv_mask; // Binary string of size kAesKeySize. -}; - -struct EncodedVideoFrame { - EncodedVideoFrame(); - ~EncodedVideoFrame(); - - VideoCodec codec; - bool key_frame; - uint32 frame_id; - uint32 last_referenced_frame_id; - std::string data; -}; - -// DEPRECATED: Do not use in new code. Please migrate existing code to use -// media::AudioBus. -struct PcmAudioFrame { - PcmAudioFrame(); - ~PcmAudioFrame(); - - int channels; // Samples in interleaved stereo format. L0, R0, L1 ,R1 ,... - int frequency; - std::vector<int16> samples; -}; - -struct EncodedAudioFrame { - EncodedAudioFrame(); - ~EncodedAudioFrame(); - - AudioCodec codec; - uint32 frame_id; // Needed to release the frame. - int samples; // Needed send side to advance the RTP timestamp. - // Not used receive side. - // Support for max sampling rate of 48KHz, 2 channels, 100 ms duration. - static const int kMaxNumberOfSamples = 48 * 2 * 100; - std::string data; -}; -typedef std::vector<uint8> Packet; -typedef std::vector<Packet> PacketList; - -class PacketSender { - public: - // All packets to be sent to the network will be delivered via these - // functions. - virtual bool SendPackets(const PacketList& packets) = 0; + // CNAME representing this receiver. + // TODO(miu): Remove this since it should be derived elsewhere (probably in + // the transport layer). + std::string rtcp_c_name; - virtual bool SendPacket(const Packet& packet) = 0; + // Determines amount of detail in RTCP reports. + // TODO(miu): Remove this since it's never anything but kRtcpReducedSize. + RtcpMode rtcp_mode; - virtual ~PacketSender() {} -}; + // The total amount of time between a frame's capture/recording on the sender + // and its playback on the receiver (i.e., shown to a user). This is fixed as + // a value large enough to give the system sufficient time to encode, + // transmit/retransmit, receive, decode, and render; given its run-time + // environment (sender/receiver hardware performance, network conditions, + // etc.). + int rtp_max_delay_ms; // TODO(miu): Change to TimeDelta target_playout_delay. -class PacketReceiver : public base::RefCountedThreadSafe<PacketReceiver> { - public: - // All packets received from the network should be delivered via this - // function. - virtual void ReceivedPacket(const uint8* packet, size_t length, - const base::Closure callback) = 0; + // RTP payload type enum: Specifies the type/encoding of frame data. + int rtp_payload_type; - static void DeletePacket(const uint8* packet); + // RTP timebase: The number of RTP units advanced per one second. For audio, + // this is the sampling rate. For video, by convention, this is 90 kHz. + int frequency; // TODO(miu): Rename to rtp_timebase for clarity. - protected: - virtual ~PacketReceiver() {} + // Number of channels. For audio, this is normally 2. For video, this must + // be 1 as Cast does not have support for stereoscopic video. + int channels; - private: - friend class base::RefCountedThreadSafe<PacketReceiver>; + // The target frame rate. For audio, this is normally 100 (i.e., frames have + // a duration of 10ms each). For video, this is normally 30, but any frame + // rate is supported. + int max_frame_rate; // TODO(miu): Rename to target_frame_rate. + + // Codec used for the compression of signal data. + // TODO(miu): Merge the AudioCodec and VideoCodec enums into one so this union + // is not necessary. + union MergedCodecPlaceholder { + transport::AudioCodec audio; + transport::VideoCodec video; + MergedCodecPlaceholder() : audio(transport::kUnknownAudioCodec) {} + } codec; + + // The AES crypto key and initialization vector. Each of these strings + // contains the data in binary form, of size kAesKeySize. If they are empty + // strings, crypto is not being used. + std::string aes_key; + std::string aes_iv_mask; }; -class VideoEncoderController { - public: - // Inform the encoder about the new target bit rate. - virtual void SetBitRate(int new_bit_rate) = 0; +// import from media::cast::transport +typedef transport::Packet Packet; +typedef transport::PacketList PacketList; - // Inform the encoder to not encode the next frame. - // Note: this setting is sticky and should last until called with false. - virtual void SkipNextFrame(bool skip_next_frame) = 0; +typedef base::Callback<void(CastInitializationStatus)> + CastInitializationCallback; - // Inform the encoder to encode the next frame as a key frame. - virtual void GenerateKeyFrame() = 0; +typedef base::Callback<void(scoped_refptr<base::SingleThreadTaskRunner>, + scoped_ptr<media::VideoEncodeAccelerator>)> + ReceiveVideoEncodeAcceleratorCallback; +typedef base::Callback<void(const ReceiveVideoEncodeAcceleratorCallback&)> + CreateVideoEncodeAcceleratorCallback; - // Inform the encoder to only reference frames older or equal to frame_id; - virtual void LatestFrameIdToReference(uint32 frame_id) = 0; - - // Query the codec about how many frames it has skipped due to slow ACK. - virtual int NumberOfSkippedFrames() const = 0; - - protected: - virtual ~VideoEncoderController() {} -}; +typedef base::Callback<void(scoped_ptr<base::SharedMemory>)> + ReceiveVideoEncodeMemoryCallback; +typedef base::Callback<void(size_t size, + const ReceiveVideoEncodeMemoryCallback&)> + CreateVideoEncodeMemoryCallback; } // namespace cast } // namespace media diff --git a/chromium/media/cast/cast_defines.h b/chromium/media/cast/cast_defines.h index aad7ae2b1d8..64b20c96da6 100644 --- a/chromium/media/cast/cast_defines.h +++ b/chromium/media/cast/cast_defines.h @@ -5,6 +5,8 @@ #ifndef MEDIA_CAST_CAST_DEFINES_H_ #define MEDIA_CAST_CAST_DEFINES_H_ +#include <stdint.h> + #include <map> #include <set> @@ -12,6 +14,7 @@ #include "base/compiler_specific.h" #include "base/logging.h" #include "base/time/time.h" +#include "media/cast/transport/cast_transport_config.h" namespace media { namespace cast { @@ -19,16 +22,33 @@ namespace cast { const int64 kDontShowTimeoutMs = 33; const float kDefaultCongestionControlBackOff = 0.875f; const uint32 kVideoFrequency = 90000; -const int64 kSkippedFramesCheckPeriodkMs = 10000; -const uint32 kStartFrameId = GG_UINT32_C(0xffffffff); +const uint32 kStartFrameId = UINT32_C(0xffffffff); + +// This is an important system-wide constant. This limits how much history the +// implementation must retain in order to process the acknowledgements of past +// frames. +const int kMaxUnackedFrames = 255; -// Number of skipped frames threshold in fps (as configured) per period above. -const int kSkippedFramesThreshold = 3; -const size_t kIpPacketSize = 1500; +const size_t kMaxIpPacketSize = 1500; const int kStartRttMs = 20; const int64 kCastMessageUpdateIntervalMs = 33; const int64 kNackRepeatIntervalMs = 30; +enum CastInitializationStatus { + STATUS_AUDIO_UNINITIALIZED, + STATUS_VIDEO_UNINITIALIZED, + STATUS_AUDIO_INITIALIZED, + STATUS_VIDEO_INITIALIZED, + STATUS_INVALID_CAST_ENVIRONMENT, + STATUS_INVALID_CRYPTO_CONFIGURATION, + STATUS_UNSUPPORTED_AUDIO_CODEC, + STATUS_UNSUPPORTED_VIDEO_CODEC, + STATUS_INVALID_AUDIO_CONFIGURATION, + STATUS_INVALID_VIDEO_CONFIGURATION, + STATUS_GPU_ACCELERATION_NOT_SUPPORTED, + STATUS_GPU_ACCELERATION_ERROR, +}; + enum DefaultSettings { kDefaultAudioEncoderBitrate = 0, // This means "auto," and may mean VBR. kDefaultAudioSamplingRate = 48000, @@ -41,17 +61,29 @@ enum DefaultSettings { kDefaultRtpMaxDelayMs = 100, }; +enum PacketType { + kNewPacket, + kNewPacketCompletingFrame, + kDuplicatePacket, + kTooOldPacket, +}; + +// kRtcpCastAllPacketsLost is used in PacketIDSet and +// on the wire to mean that ALL packets for a particular +// frame are lost. const uint16 kRtcpCastAllPacketsLost = 0xffff; +// kRtcpCastLastPacket is used in PacketIDSet to ask for +// the last packet of a frame to be retransmitted. +const uint16 kRtcpCastLastPacket = 0xfffe; + const size_t kMinLengthOfRtcp = 8; // Basic RTP header + cast header. const size_t kMinLengthOfRtp = 12 + 6; -const size_t kAesBlockSize = 16; -const size_t kAesKeySize = 16; - // Each uint16 represents one packet id within a cast frame. +// Can also contain kRtcpCastAllPacketsLost and kRtcpCastLastPacket. typedef std::set<uint16> PacketIdSet; // Each uint8 represents one cast frame. typedef std::map<uint8, PacketIdSet> MissingFramesAndPacketsMap; @@ -62,20 +94,26 @@ typedef std::map<uint8, PacketIdSet> MissingFramesAndPacketsMap; // January 1970, in NTP seconds. // Network Time Protocol (NTP), which is in seconds relative to 0h UTC on // 1 January 1900. -static const int64 kUnixEpochInNtpSeconds = GG_INT64_C(2208988800); +static const int64 kUnixEpochInNtpSeconds = INT64_C(2208988800); // Magic fractional unit. Used to convert time (in microseconds) to/from // fractional NTP seconds. static const double kMagicFractionalUnit = 4.294967296E3; +// The maximum number of Cast receiver events to keep in history for the +// purpose of sending the events through RTCP. +// The number chosen should be more than the number of events that can be +// stored in a RTCP packet. +static const size_t kReceiverRtcpEventHistorySize = 512; + inline bool IsNewerFrameId(uint32 frame_id, uint32 prev_frame_id) { return (frame_id != prev_frame_id) && - static_cast<uint32>(frame_id - prev_frame_id) < 0x80000000; + static_cast<uint32>(frame_id - prev_frame_id) < 0x80000000; } inline bool IsNewerRtpTimestamp(uint32 timestamp, uint32 prev_timestamp) { return (timestamp != prev_timestamp) && - static_cast<uint32>(timestamp - prev_timestamp) < 0x80000000; + static_cast<uint32>(timestamp - prev_timestamp) < 0x80000000; } inline bool IsOlderFrameId(uint32 frame_id, uint32 prev_frame_id) { @@ -84,7 +122,7 @@ inline bool IsOlderFrameId(uint32 frame_id, uint32 prev_frame_id) { inline bool IsNewerPacketId(uint16 packet_id, uint16 prev_packet_id) { return (packet_id != prev_packet_id) && - static_cast<uint16>(packet_id - prev_packet_id) < 0x8000; + static_cast<uint16>(packet_id - prev_packet_id) < 0x8000; } inline bool IsNewerSequenceNumber(uint16 sequence_number, @@ -107,13 +145,22 @@ inline base::TimeDelta ConvertFromNtpDiff(uint32 ntp_delay) { return base::TimeDelta::FromMilliseconds(delay_ms); } -inline void ConvertTimeToFractions(int64 time_us, +inline void ConvertTimeToFractions(int64 ntp_time_us, uint32* seconds, uint32* fractions) { - DCHECK_GE(time_us, 0) << "Time must NOT be negative"; - *seconds = static_cast<uint32>(time_us / base::Time::kMicrosecondsPerSecond); + DCHECK_GE(ntp_time_us, 0) << "Time must NOT be negative"; + const int64 seconds_component = + ntp_time_us / base::Time::kMicrosecondsPerSecond; + // NTP time will overflow in the year 2036. Also, make sure unit tests don't + // regress and use an origin past the year 2036. If this overflows here, the + // inverse calculation fails to compute the correct TimeTicks value, throwing + // off the entire system. + DCHECK_LT(seconds_component, INT64_C(4263431296)) + << "One year left to fix the NTP year 2036 wrap-around issue!"; + *seconds = static_cast<uint32>(seconds_component); *fractions = static_cast<uint32>( - (time_us % base::Time::kMicrosecondsPerSecond) * kMagicFractionalUnit); + (ntp_time_us % base::Time::kMicrosecondsPerSecond) * + kMagicFractionalUnit); } inline void ConvertTimeTicksToNtp(const base::TimeTicks& time, @@ -122,7 +169,8 @@ inline void ConvertTimeTicksToNtp(const base::TimeTicks& time, base::TimeDelta elapsed_since_unix_epoch = time - base::TimeTicks::UnixEpoch(); - int64 ntp_time_us = elapsed_since_unix_epoch.InMicroseconds() + + int64 ntp_time_us = + elapsed_since_unix_epoch.InMicroseconds() + (kUnixEpochInNtpSeconds * base::Time::kMicrosecondsPerSecond); ConvertTimeToFractions(ntp_time_us, ntp_seconds, ntp_fractions); @@ -130,30 +178,19 @@ inline void ConvertTimeTicksToNtp(const base::TimeTicks& time, inline base::TimeTicks ConvertNtpToTimeTicks(uint32 ntp_seconds, uint32 ntp_fractions) { - int64 ntp_time_us = static_cast<int64>(ntp_seconds) * - base::Time::kMicrosecondsPerSecond + - static_cast<int64>(ntp_fractions) / kMagicFractionalUnit; + int64 ntp_time_us = + static_cast<int64>(ntp_seconds) * base::Time::kMicrosecondsPerSecond + + static_cast<int64>(ntp_fractions) / kMagicFractionalUnit; - base::TimeDelta elapsed_since_unix_epoch = - base::TimeDelta::FromMicroseconds(ntp_time_us - - (kUnixEpochInNtpSeconds * base::Time::kMicrosecondsPerSecond)); + base::TimeDelta elapsed_since_unix_epoch = base::TimeDelta::FromMicroseconds( + ntp_time_us - + (kUnixEpochInNtpSeconds * base::Time::kMicrosecondsPerSecond)); return base::TimeTicks::UnixEpoch() + elapsed_since_unix_epoch; } -inline std::string GetAesNonce(uint32 frame_id, const std::string& iv_mask) { - std::string aes_nonce(kAesBlockSize, 0); - - // Serializing frame_id in big-endian order (aes_nonce[8] is the most - // significant byte of frame_id). - aes_nonce[11] = frame_id & 0xff; - aes_nonce[10] = (frame_id >> 8) & 0xff; - aes_nonce[9] = (frame_id >> 16) & 0xff; - aes_nonce[8] = (frame_id >> 24) & 0xff; - - for (size_t i = 0; i < kAesBlockSize; ++i) { - aes_nonce[i] ^= iv_mask[i]; - } - return aes_nonce; +inline base::TimeDelta RtpDeltaToTimeDelta(int64 rtp_delta, int rtp_timebase) { + DCHECK_GT(rtp_timebase, 0); + return rtp_delta * base::TimeDelta::FromSeconds(1) / rtp_timebase; } inline uint32 GetVideoRtpTimestamp(const base::TimeTicks& time_ticks) { diff --git a/chromium/media/cast/cast_environment.cc b/chromium/media/cast/cast_environment.cc index be636bb253d..93eb8c72522 100644 --- a/chromium/media/cast/cast_environment.cc +++ b/chromium/media/cast/cast_environment.cc @@ -4,65 +4,66 @@ #include "media/cast/cast_environment.h" +#include "base/bind.h" +#include "base/location.h" #include "base/logging.h" -using base::TaskRunner; +using base::SingleThreadTaskRunner; + +namespace { + +void DeleteLoggingOnMainThread(scoped_ptr<media::cast::LoggingImpl> logging) { + logging.reset(); +} + +} // namespace namespace media { namespace cast { CastEnvironment::CastEnvironment( - base::TickClock* clock, - scoped_refptr<TaskRunner> main_thread_proxy, - scoped_refptr<TaskRunner> audio_encode_thread_proxy, - scoped_refptr<TaskRunner> audio_decode_thread_proxy, - scoped_refptr<TaskRunner> video_encode_thread_proxy, - scoped_refptr<TaskRunner> video_decode_thread_proxy, - const CastLoggingConfig& config) - : clock_(clock), - main_thread_proxy_(main_thread_proxy), - audio_encode_thread_proxy_(audio_encode_thread_proxy), - audio_decode_thread_proxy_(audio_decode_thread_proxy), - video_encode_thread_proxy_(video_encode_thread_proxy), - video_decode_thread_proxy_(video_decode_thread_proxy), - logging_(new LoggingImpl(clock, main_thread_proxy, config)) { - DCHECK(main_thread_proxy) << "Main thread required"; -} + scoped_ptr<base::TickClock> clock, + scoped_refptr<SingleThreadTaskRunner> main_thread_proxy, + scoped_refptr<SingleThreadTaskRunner> audio_thread_proxy, + scoped_refptr<SingleThreadTaskRunner> video_thread_proxy) + : main_thread_proxy_(main_thread_proxy), + audio_thread_proxy_(audio_thread_proxy), + video_thread_proxy_(video_thread_proxy), + clock_(clock.Pass()), + logging_(new LoggingImpl) {} -CastEnvironment::~CastEnvironment() {} +CastEnvironment::~CastEnvironment() { + // Logging must be deleted on the main thread. + if (main_thread_proxy_ && !main_thread_proxy_->RunsTasksOnCurrentThread()) { + main_thread_proxy_->PostTask( + FROM_HERE, + base::Bind(&DeleteLoggingOnMainThread, base::Passed(&logging_))); + } +} bool CastEnvironment::PostTask(ThreadId identifier, - const tracked_objects::Location& from_here, - const base::Closure& task) { - scoped_refptr<TaskRunner> task_runner = - GetMessageTaskRunnerForThread(identifier); - - return task_runner->PostTask(from_here, task); + const tracked_objects::Location& from_here, + const base::Closure& task) { + return GetTaskRunner(identifier)->PostTask(from_here, task); } -bool CastEnvironment::PostDelayedTask(ThreadId identifier, - const tracked_objects::Location& from_here, - const base::Closure& task, - base::TimeDelta delay) { - scoped_refptr<TaskRunner> task_runner = - GetMessageTaskRunnerForThread(identifier); - - return task_runner->PostDelayedTask(from_here, task, delay); +bool CastEnvironment::PostDelayedTask( + ThreadId identifier, + const tracked_objects::Location& from_here, + const base::Closure& task, + base::TimeDelta delay) { + return GetTaskRunner(identifier)->PostDelayedTask(from_here, task, delay); } -scoped_refptr<TaskRunner> CastEnvironment::GetMessageTaskRunnerForThread( - ThreadId identifier) { +scoped_refptr<SingleThreadTaskRunner> CastEnvironment::GetTaskRunner( + ThreadId identifier) const { switch (identifier) { case CastEnvironment::MAIN: return main_thread_proxy_; - case CastEnvironment::AUDIO_ENCODER: - return audio_encode_thread_proxy_; - case CastEnvironment::AUDIO_DECODER: - return audio_decode_thread_proxy_; - case CastEnvironment::VIDEO_ENCODER: - return video_encode_thread_proxy_; - case CastEnvironment::VIDEO_DECODER: - return video_decode_thread_proxy_; + case CastEnvironment::AUDIO: + return audio_thread_proxy_; + case CastEnvironment::VIDEO: + return video_thread_proxy_; default: NOTREACHED() << "Invalid Thread identifier"; return NULL; @@ -72,30 +73,19 @@ scoped_refptr<TaskRunner> CastEnvironment::GetMessageTaskRunnerForThread( bool CastEnvironment::CurrentlyOn(ThreadId identifier) { switch (identifier) { case CastEnvironment::MAIN: - return main_thread_proxy_->RunsTasksOnCurrentThread(); - case CastEnvironment::AUDIO_ENCODER: - return audio_encode_thread_proxy_->RunsTasksOnCurrentThread(); - case CastEnvironment::AUDIO_DECODER: - return audio_decode_thread_proxy_->RunsTasksOnCurrentThread(); - case CastEnvironment::VIDEO_ENCODER: - return video_encode_thread_proxy_->RunsTasksOnCurrentThread(); - case CastEnvironment::VIDEO_DECODER: - return video_decode_thread_proxy_->RunsTasksOnCurrentThread(); + return main_thread_proxy_ && + main_thread_proxy_->RunsTasksOnCurrentThread(); + case CastEnvironment::AUDIO: + return audio_thread_proxy_ && + audio_thread_proxy_->RunsTasksOnCurrentThread(); + case CastEnvironment::VIDEO: + return video_thread_proxy_ && + video_thread_proxy_->RunsTasksOnCurrentThread(); default: NOTREACHED() << "Invalid thread identifier"; return false; } } -base::TickClock* CastEnvironment::Clock() const { - return clock_; -} - -LoggingImpl* CastEnvironment::Logging() { - DCHECK(CurrentlyOn(CastEnvironment::MAIN)) << - "Must be called from main thread"; - return logging_.get(); -} - } // namespace cast } // namespace media diff --git a/chromium/media/cast/cast_environment.h b/chromium/media/cast/cast_environment.h index 8a135733c04..1549747ee22 100644 --- a/chromium/media/cast/cast_environment.h +++ b/chromium/media/cast/cast_environment.h @@ -8,7 +8,7 @@ #include "base/basictypes.h" #include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" -#include "base/task_runner.h" +#include "base/single_thread_task_runner.h" #include "base/time/tick_clock.h" #include "base/time/time.h" #include "media/cast/logging/logging_defines.h" @@ -24,25 +24,18 @@ class CastEnvironment : public base::RefCountedThreadSafe<CastEnvironment> { // The main thread is where the cast system is configured and where timers // and network IO is performed. MAIN, - // The audio encoder thread is where all send side audio processing is done, - // primarily encoding but also re-sampling. - AUDIO_ENCODER, - // The audio decoder thread is where all receive side audio processing is - // done, primarily decoding but also error concealment and re-sampling. - AUDIO_DECODER, - // The video encoder thread is where the video encode processing is done. - VIDEO_ENCODER, - // The video decoder thread is where the video decode processing is done. - VIDEO_DECODER, + // The audio thread is where all send side audio processing is done, + // primarily encoding / decoding but also re-sampling. + AUDIO, + // The video encoder thread is where the video processing is done. + VIDEO, }; - CastEnvironment(base::TickClock* clock, - scoped_refptr<base::TaskRunner> main_thread_proxy, - scoped_refptr<base::TaskRunner> audio_encode_thread_proxy, - scoped_refptr<base::TaskRunner> audio_decode_thread_proxy, - scoped_refptr<base::TaskRunner> video_encode_thread_proxy, - scoped_refptr<base::TaskRunner> video_decode_thread_proxy, - const CastLoggingConfig& config); + CastEnvironment( + scoped_ptr<base::TickClock> clock, + scoped_refptr<base::SingleThreadTaskRunner> main_thread_proxy, + scoped_refptr<base::SingleThreadTaskRunner> audio_thread_proxy, + scoped_refptr<base::SingleThreadTaskRunner> video_thread_proxy); // These are the same methods in message_loop.h, but are guaranteed to either // get posted to the MessageLoop if it's still alive, or be deleted otherwise. @@ -60,29 +53,38 @@ class CastEnvironment : public base::RefCountedThreadSafe<CastEnvironment> { bool CurrentlyOn(ThreadId identifier); - base::TickClock* Clock() const; + // All of the media::cast implementation must use this TickClock. + base::TickClock* Clock() const { return clock_.get(); } - // Logging is not thread safe. Should always be called from the main thread. - LoggingImpl* Logging(); + // Logging is not thread safe. Its methods should always be called from the + // main thread. + // TODO(hubbe): Logging should be a thread-safe interface. + LoggingImpl* Logging() const { return logging_.get(); } - protected: - virtual ~CastEnvironment(); + scoped_refptr<base::SingleThreadTaskRunner> GetTaskRunner( + ThreadId identifier) const; - private: - friend class base::RefCountedThreadSafe<CastEnvironment>; + bool HasAudioThread() { + return audio_thread_proxy_ ? true : false; + } - scoped_refptr<base::TaskRunner> GetMessageTaskRunnerForThread( - ThreadId identifier); + bool HasVideoThread() { + return video_thread_proxy_ ? true : false; + } - base::TickClock* const clock_; // Not owned by this class. - scoped_refptr<base::TaskRunner> main_thread_proxy_; - scoped_refptr<base::TaskRunner> audio_encode_thread_proxy_; - scoped_refptr<base::TaskRunner> audio_decode_thread_proxy_; - scoped_refptr<base::TaskRunner> video_encode_thread_proxy_; - scoped_refptr<base::TaskRunner> video_decode_thread_proxy_; + protected: + virtual ~CastEnvironment(); + // Subclasses may override these. + scoped_refptr<base::SingleThreadTaskRunner> main_thread_proxy_; + scoped_refptr<base::SingleThreadTaskRunner> audio_thread_proxy_; + scoped_refptr<base::SingleThreadTaskRunner> video_thread_proxy_; + scoped_ptr<base::TickClock> clock_; scoped_ptr<LoggingImpl> logging_; + private: + friend class base::RefCountedThreadSafe<CastEnvironment>; + DISALLOW_COPY_AND_ASSIGN(CastEnvironment); }; diff --git a/chromium/media/cast/cast_receiver.gyp b/chromium/media/cast/cast_receiver.gyp deleted file mode 100644 index 031aec7e16a..00000000000 --- a/chromium/media/cast/cast_receiver.gyp +++ /dev/null @@ -1,33 +0,0 @@ -# Copyright 2013 The Chromium Authors. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - -{ - 'includes': [ - 'audio_receiver/audio_receiver.gypi', - 'video_receiver/video_receiver.gypi', - ], - 'targets': [ - { - 'target_name': 'cast_receiver', - 'type': 'static_library', - 'include_dirs': [ - '<(DEPTH)/', - '<(DEPTH)/third_party/', - '<(DEPTH)/third_party/webrtc/', - ], - 'sources': [ - 'cast_receiver.h', - 'cast_receiver_impl.cc', - 'cast_receiver_impl.h', - ], # source - 'dependencies': [ - '<(DEPTH)/crypto/crypto.gyp:crypto', - 'cast_audio_receiver', - 'cast_video_receiver', - 'net/pacing/paced_sender.gyp:cast_paced_sender', - 'rtp_receiver/rtp_receiver.gyp:cast_rtp_receiver', - ], - }, - ], -} diff --git a/chromium/media/cast/cast_receiver.h b/chromium/media/cast/cast_receiver.h index 75e6f68d3bb..a9d3edeb78e 100644 --- a/chromium/media/cast/cast_receiver.h +++ b/chromium/media/cast/cast_receiver.h @@ -13,71 +13,71 @@ #include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" #include "base/time/time.h" +#include "media/base/audio_bus.h" #include "media/cast/cast_config.h" #include "media/cast/cast_environment.h" namespace media { class VideoFrame; -} -namespace media { namespace cast { -// Callback in which the raw audio frame and play-out time will be returned -// once decoding is complete. -typedef base::Callback<void(scoped_ptr<PcmAudioFrame>, const base::TimeTicks&)> - AudioFrameDecodedCallback; -// Callback in which the encoded audio frame and play-out time will be returned. -typedef base::Callback<void(scoped_ptr<EncodedAudioFrame>, - const base::TimeTicks&)> AudioFrameEncodedCallback; +namespace transport { +class PacketSender; +} -// Callback in which the raw frame and render time will be returned once -// decoding is complete. +// The following callbacks are used to deliver decoded audio/video frame data, +// the frame's corresponding play-out time, and a continuity flag. +// |is_continuous| will be false to indicate the loss of data due to a loss of +// frames (or decoding errors). This allows the client to take steps to smooth +// discontinuities for playback. Note: A NULL pointer can be returned when data +// is not available (e.g., bad/missing packet). +typedef base::Callback<void(scoped_ptr<AudioBus> audio_bus, + const base::TimeTicks& playout_time, + bool is_continuous)> AudioFrameDecodedCallback; +// TODO(miu): |video_frame| includes a timestamp, so use that instead. typedef base::Callback<void(const scoped_refptr<media::VideoFrame>& video_frame, - const base::TimeTicks&)> - VideoFrameDecodedCallback; - -// Callback in which the encoded video frame and render time will be returned. -typedef base::Callback<void(scoped_ptr<EncodedVideoFrame>, - const base::TimeTicks&)> VideoFrameEncodedCallback; - -// This Class is thread safe. -class FrameReceiver : public base::RefCountedThreadSafe<FrameReceiver> { - public: - virtual void GetRawAudioFrame(int number_of_10ms_blocks, - int desired_frequency, - const AudioFrameDecodedCallback& callback) = 0; - - virtual void GetCodedAudioFrame( - const AudioFrameEncodedCallback& callback) = 0; + const base::TimeTicks& playout_time, + bool is_continuous)> VideoFrameDecodedCallback; - virtual void GetRawVideoFrame(const VideoFrameDecodedCallback& callback) = 0; +// The following callback delivers encoded frame data and metadata. The client +// should examine the |frame_id| field to determine whether any frames have been +// dropped (i.e., frame_id should be incrementing by one each time). Note: A +// NULL pointer can be returned on error. +typedef base::Callback<void(scoped_ptr<transport::EncodedFrame>)> + ReceiveEncodedFrameCallback; - virtual void GetEncodedVideoFrame( - const VideoFrameEncodedCallback& callback) = 0; - - protected: - virtual ~FrameReceiver() {} - - private: - friend class base::RefCountedThreadSafe<FrameReceiver>; -}; - -// This Class is thread safe. class CastReceiver { public: - static CastReceiver* CreateCastReceiver( + static scoped_ptr<CastReceiver> Create( scoped_refptr<CastEnvironment> cast_environment, - const AudioReceiverConfig& audio_config, - const VideoReceiverConfig& video_config, - PacketSender* const packet_sender); - - // All received RTP and RTCP packets for the call should be inserted to this - // PacketReceiver. - virtual scoped_refptr<PacketReceiver> packet_receiver() = 0; - - // Polling interface to get audio and video frames from the CastReceiver. - virtual scoped_refptr<FrameReceiver> frame_receiver() = 0; + const FrameReceiverConfig& audio_config, + const FrameReceiverConfig& video_config, + transport::PacketSender* const packet_sender); + + // All received RTP and RTCP packets for the call should be sent to this + // PacketReceiver. Can be called from any thread. + // TODO(hubbe): Replace with: + // virtual void ReceivePacket(scoped_ptr<Packet> packet) = 0; + virtual transport::PacketReceiverCallback packet_receiver() = 0; + + // Polling interface to get audio and video frames from the CastReceiver. The + // the RequestDecodedXXXXXFrame() methods utilize internal software-based + // decoding, while the RequestEncodedXXXXXFrame() methods provides + // still-encoded frames for use with external/hardware decoders. + // + // In all cases, the given |callback| is guaranteed to be run at some point in + // the future, except for those requests still enqueued at destruction time. + // + // These methods should all be called on the CastEnvironment's MAIN thread. + virtual void RequestDecodedAudioFrame( + const AudioFrameDecodedCallback& callback) = 0; + virtual void RequestEncodedAudioFrame( + const ReceiveEncodedFrameCallback& callback) = 0; + virtual void RequestDecodedVideoFrame( + const VideoFrameDecodedCallback& callback) = 0; + virtual void RequestEncodedVideoFrame( + const ReceiveEncodedFrameCallback& callback) = 0; virtual ~CastReceiver() {} }; diff --git a/chromium/media/cast/cast_receiver_impl.cc b/chromium/media/cast/cast_receiver_impl.cc deleted file mode 100644 index e2c004fe963..00000000000 --- a/chromium/media/cast/cast_receiver_impl.cc +++ /dev/null @@ -1,175 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "media/cast/cast_receiver_impl.h" - -#include "base/bind.h" -#include "base/callback.h" -#include "base/logging.h" -#include "base/message_loop/message_loop.h" - -namespace media { -namespace cast { - -// The video and audio receivers should only be called from the main thread. -// LocalFrameReciever posts tasks to the main thread, making the cast interface -// thread safe. -class LocalFrameReceiver : public FrameReceiver { - public: - LocalFrameReceiver(scoped_refptr<CastEnvironment> cast_environment, - AudioReceiver* audio_receiver, - VideoReceiver* video_receiver) - : cast_environment_(cast_environment), - audio_receiver_(audio_receiver), - video_receiver_(video_receiver) {} - - virtual void GetRawVideoFrame( - const VideoFrameDecodedCallback& callback) OVERRIDE { - cast_environment_->PostTask(CastEnvironment::MAIN, FROM_HERE, - base::Bind(&VideoReceiver::GetRawVideoFrame, - video_receiver_->AsWeakPtr(), callback)); - } - - virtual void GetEncodedVideoFrame( - const VideoFrameEncodedCallback& callback) OVERRIDE { - cast_environment_->PostTask(CastEnvironment::MAIN, FROM_HERE, - base::Bind(&VideoReceiver::GetEncodedVideoFrame, - video_receiver_->AsWeakPtr(), callback)); - } - - virtual void GetRawAudioFrame( - int number_of_10ms_blocks, - int desired_frequency, - const AudioFrameDecodedCallback& callback) OVERRIDE { - cast_environment_->PostTask(CastEnvironment::MAIN, FROM_HERE, base::Bind( - &AudioReceiver::GetRawAudioFrame, audio_receiver_->AsWeakPtr(), - number_of_10ms_blocks, desired_frequency, callback)); - } - - virtual void GetCodedAudioFrame( - const AudioFrameEncodedCallback& callback) OVERRIDE { - cast_environment_->PostTask(CastEnvironment::MAIN, FROM_HERE, - base::Bind(&AudioReceiver::GetEncodedAudioFrame, - audio_receiver_->AsWeakPtr(), callback)); - } - - protected: - virtual ~LocalFrameReceiver() {} - - private: - friend class base::RefCountedThreadSafe<LocalFrameReceiver>; - - scoped_refptr<CastEnvironment> cast_environment_; - AudioReceiver* audio_receiver_; - VideoReceiver* video_receiver_; -}; - -// The video and audio receivers should only be called from the main thread. -class LocalPacketReceiver : public PacketReceiver { - public: - LocalPacketReceiver(scoped_refptr<CastEnvironment> cast_environment, - AudioReceiver* audio_receiver, - VideoReceiver* video_receiver, - uint32 ssrc_of_audio_sender, - uint32 ssrc_of_video_sender) - : cast_environment_(cast_environment), - audio_receiver_(audio_receiver), - video_receiver_(video_receiver), - ssrc_of_audio_sender_(ssrc_of_audio_sender), - ssrc_of_video_sender_(ssrc_of_video_sender) {} - - virtual void ReceivedPacket(const uint8* packet, - size_t length, - const base::Closure callback) OVERRIDE { - if (length < kMinLengthOfRtcp) { - // No action; just log and call the callback informing that we are done - // with the packet. - VLOG(1) << "Received a packet which is too short " << length; - cast_environment_->PostTask(CastEnvironment::MAIN, FROM_HERE, callback); - return; - } - uint32 ssrc_of_sender; - if (!Rtcp::IsRtcpPacket(packet, length)) { - if (length < kMinLengthOfRtp) { - // No action; just log and call the callback informing that we are done - // with the packet. - VLOG(1) << "Received a RTP packet which is too short " << length; - cast_environment_->PostTask(CastEnvironment::MAIN, FROM_HERE, callback); - return; - } - ssrc_of_sender = RtpReceiver::GetSsrcOfSender(packet, length); - } else { - ssrc_of_sender = Rtcp::GetSsrcOfSender(packet, length); - } - if (ssrc_of_sender == ssrc_of_audio_sender_) { - cast_environment_->PostTask(CastEnvironment::MAIN, FROM_HERE, - base::Bind(&AudioReceiver::IncomingPacket, - audio_receiver_->AsWeakPtr(), packet, length, callback)); - } else if (ssrc_of_sender == ssrc_of_video_sender_) { - cast_environment_->PostTask(CastEnvironment::MAIN, FROM_HERE, - base::Bind(&VideoReceiver::IncomingPacket, - video_receiver_->AsWeakPtr(), packet, length, callback)); - } else { - // No action; just log and call the callback informing that we are done - // with the packet. - VLOG(1) << "Received a packet with a non matching sender SSRC " - << ssrc_of_sender; - - cast_environment_->PostTask(CastEnvironment::MAIN, FROM_HERE, callback); - } - } - - protected: - virtual ~LocalPacketReceiver() {} - - private: - friend class base::RefCountedThreadSafe<LocalPacketReceiver>; - - scoped_refptr<CastEnvironment> cast_environment_; - AudioReceiver* audio_receiver_; - VideoReceiver* video_receiver_; - const uint32 ssrc_of_audio_sender_; - const uint32 ssrc_of_video_sender_; -}; - -CastReceiver* CastReceiver::CreateCastReceiver( - scoped_refptr<CastEnvironment> cast_environment, - const AudioReceiverConfig& audio_config, - const VideoReceiverConfig& video_config, - PacketSender* const packet_sender) { - return new CastReceiverImpl(cast_environment, - audio_config, - video_config, - packet_sender); -} - -CastReceiverImpl::CastReceiverImpl( - scoped_refptr<CastEnvironment> cast_environment, - const AudioReceiverConfig& audio_config, - const VideoReceiverConfig& video_config, - PacketSender* const packet_sender) - : pacer_(cast_environment, packet_sender), - audio_receiver_(cast_environment, audio_config, &pacer_), - video_receiver_(cast_environment, video_config, &pacer_), - frame_receiver_(new LocalFrameReceiver(cast_environment, - &audio_receiver_, - &video_receiver_)), - packet_receiver_(new LocalPacketReceiver(cast_environment, - &audio_receiver_, - &video_receiver_, - audio_config.incoming_ssrc, - video_config.incoming_ssrc)) {} - -CastReceiverImpl::~CastReceiverImpl() {} - -scoped_refptr<PacketReceiver> CastReceiverImpl::packet_receiver() { - return packet_receiver_; -} - -scoped_refptr<FrameReceiver> CastReceiverImpl::frame_receiver() { - return frame_receiver_; -} - -} // namespace cast -} // namespace media diff --git a/chromium/media/cast/cast_receiver_impl.h b/chromium/media/cast/cast_receiver_impl.h deleted file mode 100644 index d34a3de6514..00000000000 --- a/chromium/media/cast/cast_receiver_impl.h +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef MEDIA_CAST_CAST_RECEIVER_IMPL_H_ -#define MEDIA_CAST_CAST_RECEIVER_IMPL_H_ - -#include "base/memory/ref_counted.h" -#include "base/memory/scoped_ptr.h" -#include "media/cast/audio_receiver/audio_receiver.h" -#include "media/cast/cast_config.h" -#include "media/cast/cast_environment.h" -#include "media/cast/cast_receiver.h" -#include "media/cast/net/pacing/paced_sender.h" -#include "media/cast/video_receiver/video_receiver.h" - -namespace media { -namespace cast { - -// This calls is a pure owner class that group all required receive objects -// together such as pacer, packet receiver, frame receiver, audio and video -// receivers. -class CastReceiverImpl : public CastReceiver { - public: - CastReceiverImpl(scoped_refptr<CastEnvironment> cast_environment, - const AudioReceiverConfig& audio_config, - const VideoReceiverConfig& video_config, - PacketSender* const packet_sender); - - virtual ~CastReceiverImpl(); - - // All received RTP and RTCP packets for the call should be inserted to this - // PacketReceiver. - virtual scoped_refptr<PacketReceiver> packet_receiver() OVERRIDE; - - // Interface to get audio and video frames from the CastReceiver. - virtual scoped_refptr<FrameReceiver> frame_receiver() OVERRIDE; - - private: - PacedSender pacer_; - AudioReceiver audio_receiver_; - VideoReceiver video_receiver_; - scoped_refptr<FrameReceiver> frame_receiver_; - scoped_refptr<PacketReceiver> packet_receiver_; -}; - -} // namespace cast -} // namespace media - -#endif // MEDIA_CAST_CAST_RECEIVER_IMPL_ diff --git a/chromium/media/cast/cast_sender.gyp b/chromium/media/cast/cast_sender.gyp deleted file mode 100644 index 1f9b07e4a42..00000000000 --- a/chromium/media/cast/cast_sender.gyp +++ /dev/null @@ -1,36 +0,0 @@ -# Copyright 2013 The Chromium Authors. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - -{ - 'includes': [ - 'audio_sender/audio_sender.gypi', - 'congestion_control/congestion_control.gypi', - 'video_sender/video_sender.gypi', - ], - 'targets': [ - { - 'target_name': 'cast_sender', - 'type': 'static_library', - 'include_dirs': [ - '<(DEPTH)/', - '<(DEPTH)/third_party/', - '<(DEPTH)/third_party/webrtc/', - ], - 'sources': [ - 'cast_sender.h', - 'cast_sender_impl.cc', - 'cast_sender_impl.h', - ], # source - 'dependencies': [ - '<(DEPTH)/crypto/crypto.gyp:crypto', - 'audio_sender', - 'congestion_control', - 'net/pacing/paced_sender.gyp:cast_paced_sender', - 'net/rtp_sender/rtp_sender.gyp:cast_rtp_sender', - 'rtcp/rtcp.gyp:cast_rtcp', - 'video_sender', - ], # dependencies - }, - ], -} diff --git a/chromium/media/cast/cast_sender.h b/chromium/media/cast/cast_sender.h index abe22f56345..eb3327ff3df 100644 --- a/chromium/media/cast/cast_sender.h +++ b/chromium/media/cast/cast_sender.h @@ -2,11 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // -// This is the main interface for the cast sender. All configuration are done -// at creation. +// This is the main interface for the cast sender. // -// The FrameInput and PacketReciever interfaces should normally be accessed from -// the IO thread. However they are allowed to be called from any thread. +// The AudioFrameInput, VideoFrameInput and PacketReciever interfaces should +// be accessed from the main thread. #ifndef MEDIA_CAST_CAST_SENDER_H_ #define MEDIA_CAST_CAST_SENDER_H_ @@ -14,83 +13,83 @@ #include "base/basictypes.h" #include "base/callback.h" #include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" #include "base/time/tick_clock.h" #include "base/time/time.h" +#include "media/base/audio_bus.h" #include "media/cast/cast_config.h" #include "media/cast/cast_environment.h" +#include "media/cast/transport/cast_transport_sender.h" namespace media { -class AudioBus; class VideoFrame; -} -namespace media { namespace cast { +class AudioSender; +class VideoSender; -// This Class is thread safe. -class FrameInput : public base::RefCountedThreadSafe<FrameInput> { +class VideoFrameInput : public base::RefCountedThreadSafe<VideoFrameInput> { public: - // The video_frame must be valid until the callback is called. - // The callback is called from the main cast thread as soon as - // the encoder is done with the frame; it does not mean that the encoded frame - // has been sent out. + // Insert video frames into Cast sender. Frames will be encoded, packetized + // and sent to the network. virtual void InsertRawVideoFrame( const scoped_refptr<media::VideoFrame>& video_frame, const base::TimeTicks& capture_time) = 0; - // The video_frame must be valid until the callback is called. - // The callback is called from the main cast thread as soon as - // the cast sender is done with the frame; it does not mean that the encoded - // frame has been sent out. - virtual void InsertCodedVideoFrame(const EncodedVideoFrame* video_frame, - const base::TimeTicks& capture_time, - const base::Closure callback) = 0; - - // The |audio_bus| must be valid until the |done_callback| is called. - // The callback is called from the main cast thread as soon as the encoder is - // done with |audio_bus|; it does not mean that the encoded data has been - // sent out. - virtual void InsertAudio(const AudioBus* audio_bus, - const base::TimeTicks& recorded_time, - const base::Closure& done_callback) = 0; - - // The audio_frame must be valid until the callback is called. - // The callback is called from the main cast thread as soon as - // the cast sender is done with the frame; it does not mean that the encoded - // frame has been sent out. - virtual void InsertCodedAudioFrame(const EncodedAudioFrame* audio_frame, - const base::TimeTicks& recorded_time, - const base::Closure callback) = 0; + protected: + virtual ~VideoFrameInput() {} + + private: + friend class base::RefCountedThreadSafe<VideoFrameInput>; +}; + +class AudioFrameInput : public base::RefCountedThreadSafe<AudioFrameInput> { + public: + // Insert audio frames into Cast sender. Frames will be encoded, packetized + // and sent to the network. + virtual void InsertAudio(scoped_ptr<AudioBus> audio_bus, + const base::TimeTicks& recorded_time) = 0; protected: - virtual ~FrameInput() {} + virtual ~AudioFrameInput() {} private: - friend class base::RefCountedThreadSafe<FrameInput>; + friend class base::RefCountedThreadSafe<AudioFrameInput>; }; -// This Class is thread safe. -// The provided PacketSender object will always be called form the main cast -// thread. +// All methods of CastSender must be called on the main thread. +// Provided CastTransportSender will also be called on the main thread. class CastSender { public: - static CastSender* CreateCastSender( + static scoped_ptr<CastSender> Create( scoped_refptr<CastEnvironment> cast_environment, - const AudioSenderConfig& audio_config, - const VideoSenderConfig& video_config, - VideoEncoderController* const video_encoder_controller, - PacketSender* const packet_sender); + transport::CastTransportSender* const transport_sender); virtual ~CastSender() {} - // All audio and video frames for the session should be inserted to this - // object. - // Can be called from any thread. - virtual scoped_refptr<FrameInput> frame_input() = 0; + // All video frames for the session should be inserted to this object. + virtual scoped_refptr<VideoFrameInput> video_frame_input() = 0; + + // All audio frames for the session should be inserted to this object. + virtual scoped_refptr<AudioFrameInput> audio_frame_input() = 0; // All RTCP packets for the session should be inserted to this object. - // Can be called from any thread. - virtual scoped_refptr<PacketReceiver> packet_receiver() = 0; + // This function and the callback must be called on the main thread. + virtual transport::PacketReceiverCallback packet_receiver() = 0; + + // Initialize the audio stack. Must be called in order to send audio frames. + // Status of the initialization will be returned on cast_initialization_cb. + virtual void InitializeAudio( + const AudioSenderConfig& audio_config, + const CastInitializationCallback& cast_initialization_cb) = 0; + + // Initialize the video stack. Must be called in order to send video frames. + // Status of the initialization will be returned on cast_initialization_cb. + virtual void InitializeVideo( + const VideoSenderConfig& video_config, + const CastInitializationCallback& cast_initialization_cb, + const CreateVideoEncodeAcceleratorCallback& create_vea_cb, + const CreateVideoEncodeMemoryCallback& create_video_encode_mem_cb) = 0; }; } // namespace cast diff --git a/chromium/media/cast/cast_sender_impl.cc b/chromium/media/cast/cast_sender_impl.cc index 69ebd53c6bd..361e4d8dc12 100644 --- a/chromium/media/cast/cast_sender_impl.cc +++ b/chromium/media/cast/cast_sender_impl.cc @@ -12,62 +12,136 @@ namespace media { namespace cast { -// The LocalFrameInput class posts all incoming frames; audio and video to the -// main cast thread for processing. -// This make the cast sender interface thread safe. -class LocalFrameInput : public FrameInput { +// The LocalVideoFrameInput class posts all incoming video frames to the main +// cast thread for processing. +class LocalVideoFrameInput : public VideoFrameInput { public: - LocalFrameInput(scoped_refptr<CastEnvironment> cast_environment, - base::WeakPtr<AudioSender> audio_sender, - base::WeakPtr<VideoSender> video_sender) - : cast_environment_(cast_environment), - audio_sender_(audio_sender), - video_sender_(video_sender) {} + LocalVideoFrameInput(scoped_refptr<CastEnvironment> cast_environment, + base::WeakPtr<VideoSender> video_sender) + : cast_environment_(cast_environment), video_sender_(video_sender) {} virtual void InsertRawVideoFrame( const scoped_refptr<media::VideoFrame>& video_frame, const base::TimeTicks& capture_time) OVERRIDE { - cast_environment_->PostTask(CastEnvironment::MAIN, FROM_HERE, - base::Bind(&VideoSender::InsertRawVideoFrame, video_sender_, - video_frame, capture_time)); + cast_environment_->PostTask(CastEnvironment::MAIN, + FROM_HERE, + base::Bind(&VideoSender::InsertRawVideoFrame, + video_sender_, + video_frame, + capture_time)); } - virtual void InsertCodedVideoFrame(const EncodedVideoFrame* video_frame, - const base::TimeTicks& capture_time, - const base::Closure callback) OVERRIDE { - cast_environment_->PostTask(CastEnvironment::MAIN, FROM_HERE, - base::Bind(&VideoSender::InsertCodedVideoFrame, video_sender_, - video_frame, capture_time, callback)); - } + protected: + virtual ~LocalVideoFrameInput() {} - virtual void InsertAudio(const AudioBus* audio_bus, - const base::TimeTicks& recorded_time, - const base::Closure& done_callback) OVERRIDE { - cast_environment_->PostTask(CastEnvironment::MAIN, FROM_HERE, - base::Bind(&AudioSender::InsertAudio, audio_sender_, - audio_bus, recorded_time, done_callback)); - } + private: + friend class base::RefCountedThreadSafe<LocalVideoFrameInput>; + + scoped_refptr<CastEnvironment> cast_environment_; + base::WeakPtr<VideoSender> video_sender_; - virtual void InsertCodedAudioFrame(const EncodedAudioFrame* audio_frame, - const base::TimeTicks& recorded_time, - const base::Closure callback) OVERRIDE { - cast_environment_->PostTask(CastEnvironment::MAIN, FROM_HERE, - base::Bind(&AudioSender::InsertCodedAudioFrame, audio_sender_, - audio_frame, recorded_time, callback)); + DISALLOW_COPY_AND_ASSIGN(LocalVideoFrameInput); +}; + +// The LocalAudioFrameInput class posts all incoming audio frames to the main +// cast thread for processing. Therefore frames can be inserted from any thread. +class LocalAudioFrameInput : public AudioFrameInput { + public: + LocalAudioFrameInput(scoped_refptr<CastEnvironment> cast_environment, + base::WeakPtr<AudioSender> audio_sender) + : cast_environment_(cast_environment), audio_sender_(audio_sender) {} + + virtual void InsertAudio(scoped_ptr<AudioBus> audio_bus, + const base::TimeTicks& recorded_time) OVERRIDE { + cast_environment_->PostTask(CastEnvironment::MAIN, + FROM_HERE, + base::Bind(&AudioSender::InsertAudio, + audio_sender_, + base::Passed(&audio_bus), + recorded_time)); } protected: - virtual ~LocalFrameInput() {} + virtual ~LocalAudioFrameInput() {} private: - friend class base::RefCountedThreadSafe<LocalFrameInput>; + friend class base::RefCountedThreadSafe<LocalAudioFrameInput>; scoped_refptr<CastEnvironment> cast_environment_; base::WeakPtr<AudioSender> audio_sender_; - base::WeakPtr<VideoSender> video_sender_; + + DISALLOW_COPY_AND_ASSIGN(LocalAudioFrameInput); }; -// LocalCastSenderPacketReceiver handle the incoming packets to the cast sender +scoped_ptr<CastSender> CastSender::Create( + scoped_refptr<CastEnvironment> cast_environment, + transport::CastTransportSender* const transport_sender) { + CHECK(cast_environment); + return scoped_ptr<CastSender>( + new CastSenderImpl(cast_environment, transport_sender)); +} + +CastSenderImpl::CastSenderImpl( + scoped_refptr<CastEnvironment> cast_environment, + transport::CastTransportSender* const transport_sender) + : cast_environment_(cast_environment), + transport_sender_(transport_sender), + weak_factory_(this) { + CHECK(cast_environment); +} + +void CastSenderImpl::InitializeAudio( + const AudioSenderConfig& audio_config, + const CastInitializationCallback& cast_initialization_cb) { + DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); + CHECK(audio_config.use_external_encoder || + cast_environment_->HasAudioThread()); + + VLOG(1) << "CastSenderImpl@" << this << "::InitializeAudio()"; + + audio_sender_.reset( + new AudioSender(cast_environment_, audio_config, transport_sender_)); + + const CastInitializationStatus status = audio_sender_->InitializationResult(); + if (status == STATUS_AUDIO_INITIALIZED) { + ssrc_of_audio_sender_ = audio_config.incoming_feedback_ssrc; + audio_frame_input_ = + new LocalAudioFrameInput(cast_environment_, audio_sender_->AsWeakPtr()); + } + cast_initialization_cb.Run(status); +} + +void CastSenderImpl::InitializeVideo( + const VideoSenderConfig& video_config, + const CastInitializationCallback& cast_initialization_cb, + const CreateVideoEncodeAcceleratorCallback& create_vea_cb, + const CreateVideoEncodeMemoryCallback& create_video_encode_mem_cb) { + DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); + CHECK(video_config.use_external_encoder || + cast_environment_->HasVideoThread()); + + VLOG(1) << "CastSenderImpl@" << this << "::InitializeVideo()"; + + video_sender_.reset(new VideoSender(cast_environment_, + video_config, + create_vea_cb, + create_video_encode_mem_cb, + transport_sender_)); + + const CastInitializationStatus status = video_sender_->InitializationResult(); + if (status == STATUS_VIDEO_INITIALIZED) { + ssrc_of_video_sender_ = video_config.incoming_feedback_ssrc; + video_frame_input_ = + new LocalVideoFrameInput(cast_environment_, video_sender_->AsWeakPtr()); + } + cast_initialization_cb.Run(status); +} + +CastSenderImpl::~CastSenderImpl() { + VLOG(1) << "CastSenderImpl@" << this << "::~CastSenderImpl()"; +} + +// ReceivedPacket handle the incoming packets to the cast sender // it's only expected to receive RTCP feedback packets from the remote cast // receiver. The class verifies that that it is a RTCP packet and based on the // SSRC of the incoming packet route the packet to the correct sender; audio or @@ -92,102 +166,54 @@ class LocalFrameInput : public FrameInput { // generates multiple streams in one RTP session, for example from // separate video cameras, each MUST be identified as a different // SSRC. - -class LocalCastSenderPacketReceiver : public PacketReceiver { - public: - LocalCastSenderPacketReceiver(scoped_refptr<CastEnvironment> cast_environment, - base::WeakPtr<AudioSender> audio_sender, - base::WeakPtr<VideoSender> video_sender, - uint32 ssrc_of_audio_sender, - uint32 ssrc_of_video_sender) - : cast_environment_(cast_environment), - audio_sender_(audio_sender), - video_sender_(video_sender), - ssrc_of_audio_sender_(ssrc_of_audio_sender), - ssrc_of_video_sender_(ssrc_of_video_sender) {} - - virtual void ReceivedPacket(const uint8* packet, - size_t length, - const base::Closure callback) OVERRIDE { - if (!Rtcp::IsRtcpPacket(packet, length)) { - // We should have no incoming RTP packets. - // No action; just log and call the callback informing that we are done - // with the packet. - VLOG(1) << "Unexpectedly received a RTP packet in the cast sender"; - cast_environment_->PostTask(CastEnvironment::MAIN, FROM_HERE, callback); +void CastSenderImpl::ReceivedPacket(scoped_ptr<Packet> packet) { + DCHECK(cast_environment_); + size_t length = packet->size(); + const uint8_t* data = &packet->front(); + if (!Rtcp::IsRtcpPacket(data, length)) { + VLOG(1) << "CastSenderImpl@" << this << "::ReceivedPacket() -- " + << "Received an invalid (non-RTCP?) packet in the cast sender."; + return; + } + uint32 ssrc_of_sender = Rtcp::GetSsrcOfSender(data, length); + if (ssrc_of_sender == ssrc_of_audio_sender_) { + if (!audio_sender_) { + NOTREACHED(); return; } - uint32 ssrc_of_sender = Rtcp::GetSsrcOfSender(packet, length); - if (ssrc_of_sender == ssrc_of_audio_sender_) { - cast_environment_->PostTask(CastEnvironment::MAIN, FROM_HERE, - base::Bind(&AudioSender::IncomingRtcpPacket, audio_sender_, - packet, length, callback)); - } else if (ssrc_of_sender == ssrc_of_video_sender_) { - cast_environment_->PostTask(CastEnvironment::MAIN, FROM_HERE, - base::Bind(&VideoSender::IncomingRtcpPacket, video_sender_, - packet, length, callback)); - } else { - // No action; just log and call the callback informing that we are done - // with the packet. - VLOG(1) << "Received a RTCP packet with a non matching sender SSRC " - << ssrc_of_sender; - - cast_environment_->PostTask(CastEnvironment::MAIN, FROM_HERE, callback); + cast_environment_->PostTask(CastEnvironment::MAIN, + FROM_HERE, + base::Bind(&AudioSender::IncomingRtcpPacket, + audio_sender_->AsWeakPtr(), + base::Passed(&packet))); + } else if (ssrc_of_sender == ssrc_of_video_sender_) { + if (!video_sender_) { + NOTREACHED(); + return; } + cast_environment_->PostTask(CastEnvironment::MAIN, + FROM_HERE, + base::Bind(&VideoSender::IncomingRtcpPacket, + video_sender_->AsWeakPtr(), + base::Passed(&packet))); + } else { + VLOG(1) << "CastSenderImpl@" << this << "::ReceivedPacket() -- " + << "Received a RTCP packet with a non matching sender SSRC " + << ssrc_of_sender; } +} - protected: - virtual ~LocalCastSenderPacketReceiver() {} - - private: - friend class base::RefCountedThreadSafe<LocalCastSenderPacketReceiver>; - - scoped_refptr<CastEnvironment> cast_environment_; - base::WeakPtr<AudioSender> audio_sender_; - base::WeakPtr<VideoSender> video_sender_; - const uint32 ssrc_of_audio_sender_; - const uint32 ssrc_of_video_sender_; -}; - -CastSender* CastSender::CreateCastSender( - scoped_refptr<CastEnvironment> cast_environment, - const AudioSenderConfig& audio_config, - const VideoSenderConfig& video_config, - VideoEncoderController* const video_encoder_controller, - PacketSender* const packet_sender) { - return new CastSenderImpl(cast_environment, - audio_config, - video_config, - video_encoder_controller, - packet_sender); +scoped_refptr<AudioFrameInput> CastSenderImpl::audio_frame_input() { + return audio_frame_input_; } -CastSenderImpl::CastSenderImpl( - scoped_refptr<CastEnvironment> cast_environment, - const AudioSenderConfig& audio_config, - const VideoSenderConfig& video_config, - VideoEncoderController* const video_encoder_controller, - PacketSender* const packet_sender) - : pacer_(cast_environment, packet_sender), - audio_sender_(cast_environment, audio_config, &pacer_), - video_sender_(cast_environment, video_config, video_encoder_controller, - &pacer_), - frame_input_(new LocalFrameInput(cast_environment, - audio_sender_.AsWeakPtr(), - video_sender_.AsWeakPtr())), - packet_receiver_(new LocalCastSenderPacketReceiver(cast_environment, - audio_sender_.AsWeakPtr(), video_sender_.AsWeakPtr(), - audio_config.incoming_feedback_ssrc, - video_config.incoming_feedback_ssrc)) {} - -CastSenderImpl::~CastSenderImpl() {} - -scoped_refptr<FrameInput> CastSenderImpl::frame_input() { - return frame_input_; +scoped_refptr<VideoFrameInput> CastSenderImpl::video_frame_input() { + return video_frame_input_; } -scoped_refptr<PacketReceiver> CastSenderImpl::packet_receiver() { - return packet_receiver_; +transport::PacketReceiverCallback CastSenderImpl::packet_receiver() { + return base::Bind(&CastSenderImpl::ReceivedPacket, + weak_factory_.GetWeakPtr()); } } // namespace cast diff --git a/chromium/media/cast/cast_sender_impl.h b/chromium/media/cast/cast_sender_impl.h index 2c5dd222e1a..d09a869712c 100644 --- a/chromium/media/cast/cast_sender_impl.h +++ b/chromium/media/cast/cast_sender_impl.h @@ -8,47 +8,64 @@ #include "base/memory/scoped_ptr.h" #include "media/cast/audio_sender/audio_sender.h" #include "media/cast/cast_config.h" +#include "media/cast/cast_defines.h" #include "media/cast/cast_environment.h" #include "media/cast/cast_sender.h" -#include "media/cast/net/pacing/paced_sender.h" #include "media/cast/video_sender/video_sender.h" namespace media { - class VideoFrame; -} +class VideoFrame; -namespace media { namespace cast { - class AudioSender; -class PacedSender; class VideoSender; -// This calls is a pure owner class that group all required sending objects -// together such as pacer, packet receiver, frame input, audio and video sender. +// This class combines all required sending objects such as the audio and video +// senders, pacer, packet receiver and frame input. class CastSenderImpl : public CastSender { public: CastSenderImpl(scoped_refptr<CastEnvironment> cast_environment, - const AudioSenderConfig& audio_config, - const VideoSenderConfig& video_config, - VideoEncoderController* const video_encoder_controller, - PacketSender* const packet_sender); + transport::CastTransportSender* const transport_sender); + + virtual void InitializeAudio( + const AudioSenderConfig& audio_config, + const CastInitializationCallback& cast_initialization_cb) OVERRIDE; + virtual void InitializeVideo( + const VideoSenderConfig& video_config, + const CastInitializationCallback& cast_initialization_cb, + const CreateVideoEncodeAcceleratorCallback& create_vea_cb, + const CreateVideoEncodeMemoryCallback& create_video_encode_mem_cb) + OVERRIDE; virtual ~CastSenderImpl(); - virtual scoped_refptr<FrameInput> frame_input() OVERRIDE; - virtual scoped_refptr<PacketReceiver> packet_receiver() OVERRIDE; + virtual scoped_refptr<AudioFrameInput> audio_frame_input() OVERRIDE; + virtual scoped_refptr<VideoFrameInput> video_frame_input() OVERRIDE; + + virtual transport::PacketReceiverCallback packet_receiver() OVERRIDE; private: - PacedSender pacer_; - AudioSender audio_sender_; - VideoSender video_sender_; - scoped_refptr<FrameInput> frame_input_; - scoped_refptr<PacketReceiver> packet_receiver_; + void ReceivedPacket(scoped_ptr<Packet> packet); + + CastInitializationCallback initialization_callback_; + scoped_ptr<AudioSender> audio_sender_; + scoped_ptr<VideoSender> video_sender_; + scoped_refptr<AudioFrameInput> audio_frame_input_; + scoped_refptr<VideoFrameInput> video_frame_input_; + scoped_refptr<CastEnvironment> cast_environment_; + // The transport sender is owned by the owner of the CastSender, and should be + // valid throughout the lifetime of the CastSender. + transport::CastTransportSender* const transport_sender_; + uint32 ssrc_of_audio_sender_; + uint32 ssrc_of_video_sender_; + + // NOTE: Weak pointers must be invalidated before all other member variables. + base::WeakPtrFactory<CastSenderImpl> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(CastSenderImpl); }; } // namespace cast } // namespace media #endif // MEDIA_CAST_CAST_SENDER_IMPL_H_ - diff --git a/chromium/media/cast/cast_testing.gypi b/chromium/media/cast/cast_testing.gypi new file mode 100644 index 00000000000..aef0fbd8c3e --- /dev/null +++ b/chromium/media/cast/cast_testing.gypi @@ -0,0 +1,276 @@ +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +{ + 'targets': [ + { + 'target_name': 'cast_test_utility', + 'type': 'static_library', + 'include_dirs': [ + '<(DEPTH)/', + ], + 'dependencies': [ + 'cast_receiver', + 'cast_transport', + '<(DEPTH)/testing/gtest.gyp:gtest', + '<(DEPTH)/third_party/libyuv/libyuv.gyp:libyuv', + '<(DEPTH)/ui/gfx/gfx.gyp:gfx_geometry', + ], + 'sources': [ + 'test/fake_single_thread_task_runner.cc', + 'test/fake_single_thread_task_runner.h', + 'test/skewed_single_thread_task_runner.cc', + 'test/skewed_single_thread_task_runner.h', + 'test/skewed_tick_clock.cc', + 'test/skewed_tick_clock.h', + 'test/utility/audio_utility.cc', + 'test/utility/audio_utility.h', + 'test/utility/barcode.cc', + 'test/utility/barcode.h', + 'test/utility/default_config.cc', + 'test/utility/default_config.h', + 'test/utility/in_process_receiver.cc', + 'test/utility/in_process_receiver.h', + 'test/utility/input_builder.cc', + 'test/utility/input_builder.h', + 'test/utility/net_utility.cc', + 'test/utility/net_utility.h', + 'test/utility/standalone_cast_environment.cc', + 'test/utility/standalone_cast_environment.h', + 'test/utility/video_utility.cc', + 'test/utility/video_utility.h', + 'test/utility/udp_proxy.cc', + 'test/utility/udp_proxy.h', + ], # source + }, + { + 'target_name': 'cast_unittests', + 'type': '<(gtest_target_type)', + 'include_dirs': [ + '<(DEPTH)/', + ], + 'dependencies': [ + 'cast_base', + 'cast_receiver', + 'cast_rtcp', + 'cast_sender', + 'cast_test_utility', + # Not a true dependency. This is here to make sure the CQ can verify + # the tools compile correctly. + 'cast_tools', + 'cast_transport', + '<(DEPTH)/base/base.gyp:test_support_base', + '<(DEPTH)/net/net.gyp:net', + '<(DEPTH)/testing/gmock.gyp:gmock', + '<(DEPTH)/testing/gtest.gyp:gtest', + ], + 'sources': [ + '<(DEPTH)/media/base/run_all_unittests.cc', + 'audio_sender/audio_encoder_unittest.cc', + 'audio_sender/audio_sender_unittest.cc', + 'congestion_control/congestion_control_unittest.cc', + 'framer/cast_message_builder_unittest.cc', + 'framer/frame_buffer_unittest.cc', + 'framer/framer_unittest.cc', + 'logging/encoding_event_subscriber_unittest.cc', + 'logging/serialize_deserialize_test.cc', + 'logging/logging_impl_unittest.cc', + 'logging/logging_raw_unittest.cc', + 'logging/receiver_time_offset_estimator_impl_unittest.cc', + 'logging/simple_event_subscriber_unittest.cc', + 'logging/stats_event_subscriber_unittest.cc', + 'receiver/audio_decoder_unittest.cc', + 'receiver/frame_receiver_unittest.cc', + 'receiver/video_decoder_unittest.cc', + 'rtcp/mock_rtcp_receiver_feedback.cc', + 'rtcp/mock_rtcp_receiver_feedback.h', + 'rtcp/mock_rtcp_sender_feedback.cc', + 'rtcp/mock_rtcp_sender_feedback.h', + 'rtcp/rtcp_receiver_unittest.cc', + 'rtcp/rtcp_sender_unittest.cc', + 'rtcp/rtcp_unittest.cc', + 'rtcp/receiver_rtcp_event_subscriber_unittest.cc', +# TODO(miu): The following two are test utility modules. Rename/move the files. + 'rtcp/test_rtcp_packet_builder.cc', + 'rtcp/test_rtcp_packet_builder.h', + 'rtp_receiver/rtp_receiver_defines.h', + 'rtp_receiver/mock_rtp_payload_feedback.cc', + 'rtp_receiver/mock_rtp_payload_feedback.h', + 'rtp_receiver/receiver_stats_unittest.cc', + 'rtp_receiver/rtp_parser/test/rtp_packet_builder.cc', + 'rtp_receiver/rtp_parser/rtp_parser_unittest.cc', + 'test/end2end_unittest.cc', + 'test/fake_receiver_time_offset_estimator.cc', + 'test/fake_receiver_time_offset_estimator.h', + 'test/fake_single_thread_task_runner.cc', + 'test/fake_single_thread_task_runner.h', + 'test/fake_video_encode_accelerator.cc', + 'test/fake_video_encode_accelerator.h', + 'test/utility/audio_utility_unittest.cc', + 'test/utility/barcode_unittest.cc', + 'transport/cast_transport_sender_impl_unittest.cc', + 'transport/pacing/mock_paced_packet_sender.cc', + 'transport/pacing/mock_paced_packet_sender.h', + 'transport/pacing/paced_sender_unittest.cc', + 'transport/rtp_sender/packet_storage/packet_storage_unittest.cc', + 'transport/rtp_sender/rtp_packetizer/rtp_packetizer_unittest.cc', + 'transport/rtp_sender/rtp_packetizer/test/rtp_header_parser.cc', + 'transport/rtp_sender/rtp_packetizer/test/rtp_header_parser.h', + 'transport/transport/udp_transport_unittest.cc', + 'video_sender/external_video_encoder_unittest.cc', + 'video_sender/video_encoder_impl_unittest.cc', + 'video_sender/video_sender_unittest.cc', + ], # source + }, + { + 'target_name': 'cast_benchmarks', + 'type': '<(gtest_target_type)', + 'include_dirs': [ + '<(DEPTH)/', + ], + 'dependencies': [ + 'cast_base', + 'cast_receiver', + 'cast_rtcp', + 'cast_sender', + 'cast_test_utility', + 'cast_transport', + '<(DEPTH)/base/base.gyp:test_support_base', + '<(DEPTH)/net/net.gyp:net', + '<(DEPTH)/testing/gtest.gyp:gtest', + ], + 'sources': [ + 'test/cast_benchmarks.cc', + 'test/fake_single_thread_task_runner.cc', + 'test/fake_single_thread_task_runner.h', + 'test/fake_video_encode_accelerator.cc', + 'test/fake_video_encode_accelerator.h', + 'test/utility/test_util.cc', + 'test/utility/test_util.h', + ], # source + 'conditions': [ + ['os_posix==1 and OS!="mac" and OS!="ios" and use_allocator!="none"', + { + 'dependencies': [ + '<(DEPTH)/base/allocator/allocator.gyp:allocator', + ], + } + ], + ], + }, + { + # This is a target for the collection of cast development tools. + # They are built on bots but not shipped. + 'target_name': 'cast_tools', + 'type': 'none', + 'dependencies': [ + 'cast_receiver_app', + 'cast_sender_app', + 'udp_proxy', + ], + }, + { + 'target_name': 'cast_receiver_app', + 'type': 'executable', + 'include_dirs': [ + '<(DEPTH)/', + ], + 'dependencies': [ + 'cast_base', + 'cast_receiver', + 'cast_test_utility', + 'cast_transport', + '<(DEPTH)/net/net.gyp:net_test_support', + '<(DEPTH)/media/media.gyp:media', + '<(DEPTH)/testing/gtest.gyp:gtest', + '<(DEPTH)/third_party/libyuv/libyuv.gyp:libyuv', + ], + 'sources': [ + '<(DEPTH)/media/cast/test/receiver.cc', + ], + 'conditions': [ + ['OS == "linux" and use_x11==1', { + 'dependencies': [ + '<(DEPTH)/build/linux/system.gyp:x11', + '<(DEPTH)/build/linux/system.gyp:xext', + ], + 'sources': [ + '<(DEPTH)/media/cast/test/linux_output_window.cc', + '<(DEPTH)/media/cast/test/linux_output_window.h', + '<(DEPTH)/ui/gfx/gfx.gyp:gfx_geometry', + ], + }], + ], + }, + { + 'target_name': 'cast_sender_app', + 'type': 'executable', + 'include_dirs': [ + '<(DEPTH)/', + ], + 'dependencies': [ + 'cast_base', + 'cast_sender', + 'cast_test_utility', + 'cast_transport', + '<(DEPTH)/net/net.gyp:net_test_support', + '<(DEPTH)/media/media.gyp:media', + '<(DEPTH)/testing/gtest.gyp:gtest', + '<(DEPTH)/third_party/ffmpeg/ffmpeg.gyp:ffmpeg', + '<(DEPTH)/third_party/opus/opus.gyp:opus', + '<(DEPTH)/ui/gfx/gfx.gyp:gfx_geometry', + ], + 'sources': [ + '<(DEPTH)/media/cast/test/sender.cc', + ], + }, + { + 'target_name': 'generate_barcode_video', + 'type': 'executable', + 'include_dirs': [ + '<(DEPTH)/', + ], + 'dependencies': [ + 'cast_test_utility', + '<(DEPTH)/base/base.gyp:base', + '<(DEPTH)/media/media.gyp:media', + ], + 'sources': [ + 'test/utility/generate_barcode_video.cc', + ], + }, + { + 'target_name': 'generate_timecode_audio', + 'type': 'executable', + 'include_dirs': [ + '<(DEPTH)/', + ], + 'dependencies': [ + 'cast_base', + 'cast_test_utility', + 'cast_transport', + '<(DEPTH)/base/base.gyp:base', + '<(DEPTH)/media/media.gyp:media', + ], + 'sources': [ + 'test/utility/generate_timecode_audio.cc', + ], + }, + { + 'target_name': 'udp_proxy', + 'type': 'executable', + 'include_dirs': [ + '<(DEPTH)/', + ], + 'dependencies': [ + 'cast_test_utility', + '<(DEPTH)/base/base.gyp:base', + '<(DEPTH)/media/media.gyp:media', + ], + 'sources': [ + 'test/utility/udp_proxy_main.cc', + ], + } + ], # targets +} diff --git a/chromium/media/cast/congestion_control/congestion_control.cc b/chromium/media/cast/congestion_control/congestion_control.cc index 35687e7477a..d24e0ac3d0f 100644 --- a/chromium/media/cast/congestion_control/congestion_control.cc +++ b/chromium/media/cast/congestion_control/congestion_control.cc @@ -2,6 +2,17 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// The purpose of this file is determine what bitrate to use for mirroring. +// Ideally this should be as much as possible, without causing any frames to +// arrive late. + +// The current algorithm is to measure how much bandwidth we've been using +// recently. We also keep track of how much data has been queued up for sending +// in a virtual "buffer" (this virtual buffer represents all the buffers between +// the sender and the receiver, including retransmissions and so forth.) +// If we estimate that our virtual buffer is mostly empty, we try to use +// more bandwidth than our recent usage, otherwise we use less. + #include "media/cast/congestion_control/congestion_control.h" #include "base/logging.h" @@ -11,104 +22,176 @@ namespace media { namespace cast { -static const int64 kCongestionControlMinChangeIntervalMs = 10; -static const int64 kCongestionControlMaxChangeIntervalMs = 100; +// This means that we *try* to keep our buffer 90% empty. +// If it is less full, we increase the bandwidth, if it is more +// we decrease the bandwidth. Making this smaller makes the +// congestion control more aggressive. +static const double kTargetEmptyBufferFraction = 0.9; -// At 10 ms RTT TCP Reno would ramp 1500 * 8 * 100 = 1200 Kbit/s. -// NACK is sent after a maximum of 10 ms. -static const int kCongestionControlMaxBitrateIncreasePerMillisecond = 1200; +// This is the size of our history in frames. Larger values makes the +// congestion control adapt slower. +static const size_t kHistorySize = 100; -static const int64 kMaxElapsedTimeMs = kCongestionControlMaxChangeIntervalMs; +CongestionControl::FrameStats::FrameStats() : frame_size(0) { +} CongestionControl::CongestionControl(base::TickClock* clock, - float congestion_control_back_off, uint32 max_bitrate_configured, uint32 min_bitrate_configured, - uint32 start_bitrate) + size_t max_unacked_frames) : clock_(clock), - congestion_control_back_off_(congestion_control_back_off), max_bitrate_configured_(max_bitrate_configured), min_bitrate_configured_(min_bitrate_configured), - bitrate_(start_bitrate) { - DCHECK_GT(congestion_control_back_off, 0.0f) << "Invalid config"; - DCHECK_LT(congestion_control_back_off, 1.0f) << "Invalid config"; + last_frame_stats_(static_cast<uint32>(-1)), + last_acked_frame_(static_cast<uint32>(-1)), + last_encoded_frame_(static_cast<uint32>(-1)), + history_size_(max_unacked_frames + kHistorySize), + acked_bits_in_history_(0) { DCHECK_GE(max_bitrate_configured, min_bitrate_configured) << "Invalid config"; - DCHECK_GE(max_bitrate_configured, start_bitrate) << "Invalid config"; - DCHECK_GE(start_bitrate, min_bitrate_configured) << "Invalid config"; + frame_stats_.resize(2); + base::TimeTicks now = clock->NowTicks(); + frame_stats_[0].ack_time = now; + frame_stats_[0].sent_time = now; + frame_stats_[1].ack_time = now; + DCHECK(!frame_stats_[0].ack_time.is_null()); } -CongestionControl::~CongestionControl() { +CongestionControl::~CongestionControl() {} + +void CongestionControl::UpdateRtt(base::TimeDelta rtt) { + rtt_ = base::TimeDelta::FromSecondsD( + (rtt_.InSecondsF() * 7 + rtt.InSecondsF()) / 8); } -bool CongestionControl::OnAck(base::TimeDelta rtt, uint32* new_bitrate) { - base::TimeTicks now = clock_->NowTicks(); +// Calculate how much "dead air" there is between two frames. +base::TimeDelta CongestionControl::DeadTime(const FrameStats& a, + const FrameStats& b) { + if (b.sent_time > a.ack_time) { + return b.sent_time - a.ack_time; + } else { + return base::TimeDelta(); + } +} + +double CongestionControl::CalculateSafeBitrate() { + double transmit_time = + (GetFrameStats(last_acked_frame_)->ack_time - + frame_stats_.front().sent_time - dead_time_in_history_).InSecondsF(); - // First feedback? - if (time_last_increase_.is_null()) { - time_last_increase_ = now; - time_last_decrease_ = now; - return false; + if (acked_bits_in_history_ == 0 || transmit_time <= 0.0) { + return min_bitrate_configured_; } - // Are we at the max bitrate? - if (max_bitrate_configured_ == bitrate_) return false; - - // Make sure RTT is never less than 1 ms. - rtt = std::max(rtt, base::TimeDelta::FromMilliseconds(1)); - - base::TimeDelta elapsed_time = std::min(now - time_last_increase_, - base::TimeDelta::FromMilliseconds(kMaxElapsedTimeMs)); - base::TimeDelta change_interval = std::max(rtt, - base::TimeDelta::FromMilliseconds(kCongestionControlMinChangeIntervalMs)); - change_interval = std::min(change_interval, - base::TimeDelta::FromMilliseconds(kCongestionControlMaxChangeIntervalMs)); - - // Have enough time have passed? - if (elapsed_time < change_interval) return false; - - time_last_increase_ = now; - - // One packet per RTT multiplied by the elapsed time fraction. - // 1500 * 8 * (1000 / rtt_ms) * (elapsed_time_ms / 1000) => - // 1500 * 8 * elapsed_time_ms / rtt_ms. - uint32 bitrate_increase = (1500 * 8 * elapsed_time.InMilliseconds()) / - rtt.InMilliseconds(); - uint32 max_bitrate_increase = - kCongestionControlMaxBitrateIncreasePerMillisecond * - elapsed_time.InMilliseconds(); - bitrate_increase = std::min(max_bitrate_increase, bitrate_increase); - *new_bitrate = std::min(bitrate_increase + bitrate_, max_bitrate_configured_); - bitrate_ = *new_bitrate; - return true; + return acked_bits_in_history_ / std::max(transmit_time, 1E-3); } -bool CongestionControl::OnNack(base::TimeDelta rtt, uint32* new_bitrate) { - base::TimeTicks now = clock_->NowTicks(); +CongestionControl::FrameStats* CongestionControl::GetFrameStats( + uint32 frame_id) { + int32 offset = static_cast<int32>(frame_id - last_frame_stats_); + DCHECK_LT(offset, static_cast<int32>(kHistorySize)); + if (offset > 0) { + frame_stats_.resize(frame_stats_.size() + offset); + last_frame_stats_ += offset; + offset = 0; + } + while (frame_stats_.size() > history_size_) { + DCHECK_GT(frame_stats_.size(), 1UL); + DCHECK(!frame_stats_[0].ack_time.is_null()); + acked_bits_in_history_ -= frame_stats_[0].frame_size; + dead_time_in_history_ -= DeadTime(frame_stats_[0], frame_stats_[1]); + DCHECK_GE(acked_bits_in_history_, 0UL); + VLOG(2) << "DT: " << dead_time_in_history_.InSecondsF(); + DCHECK_GE(dead_time_in_history_.InSecondsF(), 0.0); + frame_stats_.pop_front(); + } + offset += frame_stats_.size() - 1; + if (offset < 0 || offset >= static_cast<int32>(frame_stats_.size())) { + return NULL; + } + return &frame_stats_[offset]; +} - // First feedback? - if (time_last_decrease_.is_null()) { - time_last_increase_ = now; - time_last_decrease_ = now; - return false; +void CongestionControl::AckFrame(uint32 frame_id, base::TimeTicks when) { + FrameStats* frame_stats = GetFrameStats(last_acked_frame_); + while (IsNewerFrameId(frame_id, last_acked_frame_)) { + FrameStats* last_frame_stats = frame_stats; + last_acked_frame_++; + frame_stats = GetFrameStats(last_acked_frame_); + DCHECK(frame_stats); + frame_stats->ack_time = when; + acked_bits_in_history_ += frame_stats->frame_size; + dead_time_in_history_ += DeadTime(*last_frame_stats, *frame_stats); } - base::TimeDelta elapsed_time = std::min(now - time_last_decrease_, - base::TimeDelta::FromMilliseconds(kMaxElapsedTimeMs)); - base::TimeDelta change_interval = std::max(rtt, - base::TimeDelta::FromMilliseconds(kCongestionControlMinChangeIntervalMs)); - change_interval = std::min(change_interval, - base::TimeDelta::FromMilliseconds(kCongestionControlMaxChangeIntervalMs)); +} - // Have enough time have passed? - if (elapsed_time < change_interval) return false; +void CongestionControl::SendFrameToTransport(uint32 frame_id, + size_t frame_size, + base::TimeTicks when) { + last_encoded_frame_ = frame_id; + FrameStats* frame_stats = GetFrameStats(frame_id); + DCHECK(frame_stats); + frame_stats->frame_size = frame_size; + frame_stats->sent_time = when; +} - time_last_decrease_ = now; - time_last_increase_ = now; +base::TimeTicks CongestionControl::EstimatedAckTime(uint32 frame_id, + double bitrate) { + FrameStats* frame_stats = GetFrameStats(frame_id); + DCHECK(frame_stats); + if (frame_stats->ack_time.is_null()) { + DCHECK(frame_stats->frame_size) << "frame_id: " << frame_id; + base::TimeTicks ret = EstimatedSendingTime(frame_id, bitrate); + ret += base::TimeDelta::FromSecondsD(frame_stats->frame_size / bitrate); + ret += rtt_; + base::TimeTicks now = clock_->NowTicks(); + if (ret < now) { + // This is a little counter-intuitive, but it seems to work. + // Basically, when we estimate that the ACK should have already happened, + // we figure out how long ago it should have happened and guess that the + // ACK will happen half of that time in the future. This will cause some + // over-estimation when acks are late, which is actually what we want. + return now + (now - ret) / 2; + } else { + return ret; + } + } else { + return frame_stats->ack_time; + } +} - *new_bitrate = std::max( - static_cast<uint32>(bitrate_ * congestion_control_back_off_), - min_bitrate_configured_); +base::TimeTicks CongestionControl::EstimatedSendingTime(uint32 frame_id, + double bitrate) { + FrameStats* frame_stats = GetFrameStats(frame_id); + DCHECK(frame_stats); + base::TimeTicks ret = EstimatedAckTime(frame_id - 1, bitrate) - rtt_; + if (frame_stats->sent_time.is_null()) { + // Not sent yet, but we can't start sending it in the past. + return std::max(ret, clock_->NowTicks()); + } else { + return std::max(ret, frame_stats->sent_time); + } +} - bitrate_ = *new_bitrate; - return true; +uint32 CongestionControl::GetBitrate(base::TimeTicks playout_time, + base::TimeDelta playout_delay) { + double safe_bitrate = CalculateSafeBitrate(); + // Estimate when we might start sending the next frame. + base::TimeDelta time_to_catch_up = + playout_time - + EstimatedSendingTime(last_encoded_frame_ + 1, safe_bitrate); + + double empty_buffer_fraction = + time_to_catch_up.InSecondsF() / playout_delay.InSecondsF(); + empty_buffer_fraction = std::min(empty_buffer_fraction, 1.0); + empty_buffer_fraction = std::max(empty_buffer_fraction, 0.0); + + uint32 bits_per_second = static_cast<uint32>( + safe_bitrate * empty_buffer_fraction / kTargetEmptyBufferFraction); + VLOG(3) << " FBR:" << (bits_per_second / 1E6) + << " EBF:" << empty_buffer_fraction + << " SBR:" << (safe_bitrate / 1E6); + bits_per_second = std::max(bits_per_second, min_bitrate_configured_); + bits_per_second = std::min(bits_per_second, max_bitrate_configured_); + return bits_per_second; } } // namespace cast diff --git a/chromium/media/cast/congestion_control/congestion_control.gypi b/chromium/media/cast/congestion_control/congestion_control.gypi deleted file mode 100644 index 20a57ca2a30..00000000000 --- a/chromium/media/cast/congestion_control/congestion_control.gypi +++ /dev/null @@ -1,23 +0,0 @@ -# Copyright 2013 The Chromium Authors. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - -{ - 'targets': [ - { - 'target_name': 'congestion_control', - 'type': 'static_library', - 'include_dirs': [ - '<(DEPTH)/', - ], - 'sources': [ - 'congestion_control.h', - 'congestion_control.cc', - ], # source - 'dependencies': [ - '<(DEPTH)/base/base.gyp:base', - ], - }, - ], -} - diff --git a/chromium/media/cast/congestion_control/congestion_control.h b/chromium/media/cast/congestion_control/congestion_control.h index df88151eb8f..54622ab114d 100644 --- a/chromium/media/cast/congestion_control/congestion_control.h +++ b/chromium/media/cast/congestion_control/congestion_control.h @@ -5,6 +5,8 @@ #ifndef MEDIA_CAST_CONGESTION_CONTROL_CONGESTION_CONTROL_H_ #define MEDIA_CAST_CONGESTION_CONTROL_CONGESTION_CONTROL_H_ +#include <deque> + #include "base/basictypes.h" #include "base/memory/scoped_ptr.h" #include "base/time/tick_clock.h" @@ -16,29 +18,65 @@ namespace cast { class CongestionControl { public: CongestionControl(base::TickClock* clock, - float congestion_control_back_off, uint32 max_bitrate_configured, uint32 min_bitrate_configured, - uint32 start_bitrate); + size_t max_unacked_frames); virtual ~CongestionControl(); - // Don't call OnAck if the same message contain a NACK. - // Returns true if the bitrate have changed. - bool OnAck(base::TimeDelta rtt_ms, uint32* new_bitrate); + void UpdateRtt(base::TimeDelta rtt); + + // Called when an encoded frame is sent to the transport. + void SendFrameToTransport(uint32 frame_id, + size_t frame_size, + base::TimeTicks when); - // Returns true if the bitrate have changed. - bool OnNack(base::TimeDelta rtt_ms, uint32* new_bitrate); + // Called when we receive an ACK for a frame. + void AckFrame(uint32 frame_id, base::TimeTicks when); + // Returns the bitrate we should use for the next frame. + uint32 GetBitrate(base::TimeTicks playout_time, + base::TimeDelta playout_delay); private: + struct FrameStats { + FrameStats(); + // Time this frame was sent to the transport. + base::TimeTicks sent_time; + // Time this frame was acked. + base::TimeTicks ack_time; + // Size of encoded frame in bits. + size_t frame_size; + }; + + // Calculate how much "dead air" (idle time) there is between two frames. + static base::TimeDelta DeadTime(const FrameStats& a, const FrameStats& b); + // Get the FrameStats for a given |frame_id|. + // Note: Older FrameStats will be removed automatically. + FrameStats* GetFrameStats(uint32 frame_id); + // Calculata safe bitrate. This is based on how much we've been + // sending in the past. + double CalculateSafeBitrate(); + + // For a given frame, calculate when it might be acked. + // (Or return the time it was acked, if it was.) + base::TimeTicks EstimatedAckTime(uint32 frame_id, double bitrate); + // Calculate when we start sending the data for a given frame. + // This is done by calculating when we were done sending the previous + // frame, but obvoiusly can't be less than |sent_time| (if known). + base::TimeTicks EstimatedSendingTime(uint32 frame_id, double bitrate); + base::TickClock* const clock_; // Not owned by this class. - const float congestion_control_back_off_; const uint32 max_bitrate_configured_; const uint32 min_bitrate_configured_; - uint32 bitrate_; - base::TimeTicks time_last_increase_; - base::TimeTicks time_last_decrease_; + std::deque<FrameStats> frame_stats_; + uint32 last_frame_stats_; + uint32 last_acked_frame_; + uint32 last_encoded_frame_; + base::TimeDelta rtt_; + size_t history_size_; + size_t acked_bits_in_history_; + base::TimeDelta dead_time_in_history_; DISALLOW_COPY_AND_ASSIGN(CongestionControl); }; diff --git a/chromium/media/cast/congestion_control/congestion_control_unittest.cc b/chromium/media/cast/congestion_control/congestion_control_unittest.cc index 108d2b340b7..5745eab21df 100644 --- a/chromium/media/cast/congestion_control/congestion_control_unittest.cc +++ b/chromium/media/cast/congestion_control/congestion_control_unittest.cc @@ -2,9 +2,12 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include <stdint.h> + #include "base/test/simple_test_tick_clock.h" #include "media/cast/cast_defines.h" #include "media/cast/congestion_control/congestion_control.h" +#include "media/cast/test/fake_single_thread_task_runner.h" #include "testing/gtest/include/gtest/gtest.h" namespace media { @@ -12,169 +15,106 @@ namespace cast { static const uint32 kMaxBitrateConfigured = 5000000; static const uint32 kMinBitrateConfigured = 500000; -static const uint32 kStartBitrate = 2000000; -static const int64 kStartMillisecond = GG_INT64_C(12345678900000); -static const int64 kRttMs = 20; -static const int64 kAckRateMs = 33; +static const int64 kStartMillisecond = INT64_C(12345678900000); +static const double kTargetEmptyBufferFraction = 0.9; class CongestionControlTest : public ::testing::Test { protected: CongestionControlTest() - : congestion_control_(&testing_clock_, - kDefaultCongestionControlBackOff, - kMaxBitrateConfigured, - kMinBitrateConfigured, - kStartBitrate) { + : task_runner_(new test::FakeSingleThreadTaskRunner(&testing_clock_)) { testing_clock_.Advance( base::TimeDelta::FromMilliseconds(kStartMillisecond)); + congestion_control_.reset(new CongestionControl( + &testing_clock_, kMaxBitrateConfigured, kMinBitrateConfigured, 10)); } - // Returns the last bitrate of the run. - uint32 RunWithOneLossEventPerSecond(int fps, int rtt_ms, - int runtime_in_seconds) { - const base::TimeDelta rtt = base::TimeDelta::FromMilliseconds(rtt_ms); - const base::TimeDelta ack_rate = - base::TimeDelta::FromMilliseconds(GG_INT64_C(1000) / fps); - uint32 new_bitrate = 0; - EXPECT_FALSE(congestion_control_.OnAck(rtt, &new_bitrate)); - - for (int seconds = 0; seconds < runtime_in_seconds; ++seconds) { - for (int i = 1; i < fps; ++i) { - testing_clock_.Advance(ack_rate); - congestion_control_.OnAck(rtt, &new_bitrate); - } - EXPECT_TRUE(congestion_control_.OnNack(rtt, &new_bitrate)); - } - return new_bitrate; - } - - base::SimpleTestTickClock testing_clock_; - CongestionControl congestion_control_; -}; - -TEST_F(CongestionControlTest, Max) { - uint32 new_bitrate = 0; - const base::TimeDelta rtt = base::TimeDelta::FromMilliseconds(kRttMs); - const base::TimeDelta ack_rate = - base::TimeDelta::FromMilliseconds(kAckRateMs); - EXPECT_FALSE(congestion_control_.OnAck(rtt, &new_bitrate)); - - uint32 expected_increase_bitrate = 0; - - // Expected time is 5 seconds. 500000 - 2000000 = 5 * 1500 * 8 * (1000 / 20). - for (int i = 0; i < 151; ++i) { - testing_clock_.Advance(ack_rate); - EXPECT_TRUE(congestion_control_.OnAck(rtt, &new_bitrate)); - expected_increase_bitrate += 1500 * 8 * kAckRateMs / kRttMs; - EXPECT_EQ(kStartBitrate + expected_increase_bitrate, new_bitrate); - } - testing_clock_.Advance(ack_rate); - EXPECT_TRUE(congestion_control_.OnAck(rtt, &new_bitrate)); - EXPECT_EQ(kMaxBitrateConfigured, new_bitrate); -} - -TEST_F(CongestionControlTest, Min) { - uint32 new_bitrate = 0; - const base::TimeDelta rtt = base::TimeDelta::FromMilliseconds(kRttMs); - const base::TimeDelta ack_rate = - base::TimeDelta::FromMilliseconds(kAckRateMs); - EXPECT_FALSE(congestion_control_.OnNack(rtt, &new_bitrate)); - - uint32 expected_decrease_bitrate = kStartBitrate; - - // Expected number is 10. 2000 * 0.875^10 <= 500. - for (int i = 0; i < 10; ++i) { - testing_clock_.Advance(ack_rate); - EXPECT_TRUE(congestion_control_.OnNack(rtt, &new_bitrate)); - expected_decrease_bitrate = static_cast<uint32>( - expected_decrease_bitrate * kDefaultCongestionControlBackOff); - EXPECT_EQ(expected_decrease_bitrate, new_bitrate); + void AckFrame(uint32 frame_id) { + congestion_control_->AckFrame(frame_id, testing_clock_.NowTicks()); } - testing_clock_.Advance(ack_rate); - EXPECT_TRUE(congestion_control_.OnNack(rtt, &new_bitrate)); - EXPECT_EQ(kMinBitrateConfigured, new_bitrate); -} -TEST_F(CongestionControlTest, Timing) { - const base::TimeDelta rtt = base::TimeDelta::FromMilliseconds(kRttMs); - const base::TimeDelta ack_rate = - base::TimeDelta::FromMilliseconds(kAckRateMs); - uint32 new_bitrate = 0; - uint32 expected_bitrate = kStartBitrate; - - EXPECT_FALSE(congestion_control_.OnAck(rtt, &new_bitrate)); - - testing_clock_.Advance(ack_rate); - EXPECT_TRUE(congestion_control_.OnAck(rtt, &new_bitrate)); - expected_bitrate += 1500 * 8 * kAckRateMs / kRttMs; - EXPECT_EQ(expected_bitrate, new_bitrate); - - // We should back immediately. - EXPECT_TRUE(congestion_control_.OnNack(rtt, &new_bitrate)); - expected_bitrate = static_cast<uint32>( - expected_bitrate * kDefaultCongestionControlBackOff); - EXPECT_EQ(expected_bitrate, new_bitrate); - - // Less than one RTT have passed don't back again. - testing_clock_.Advance(base::TimeDelta::FromMilliseconds(10)); - EXPECT_FALSE(congestion_control_.OnNack(rtt, &new_bitrate)); - - testing_clock_.Advance(base::TimeDelta::FromMilliseconds(10)); - EXPECT_TRUE(congestion_control_.OnNack(rtt, &new_bitrate)); - expected_bitrate = static_cast<uint32>( - expected_bitrate * kDefaultCongestionControlBackOff); - EXPECT_EQ(expected_bitrate, new_bitrate); - - testing_clock_.Advance(base::TimeDelta::FromMilliseconds(10)); - EXPECT_FALSE(congestion_control_.OnAck(rtt, &new_bitrate)); - testing_clock_.Advance(base::TimeDelta::FromMilliseconds(10)); - EXPECT_TRUE(congestion_control_.OnAck(rtt, &new_bitrate)); - expected_bitrate += 1500 * 8 * 20 / kRttMs; - EXPECT_EQ(expected_bitrate, new_bitrate); - - testing_clock_.Advance(base::TimeDelta::FromMilliseconds(10)); - EXPECT_FALSE(congestion_control_.OnAck(rtt, &new_bitrate)); - testing_clock_.Advance(base::TimeDelta::FromMilliseconds(10)); - EXPECT_TRUE(congestion_control_.OnAck(rtt, &new_bitrate)); - expected_bitrate += 1500 * 8 * 20 / kRttMs; - EXPECT_EQ(expected_bitrate, new_bitrate); - - // Test long elapsed time (300 ms). - testing_clock_.Advance(base::TimeDelta::FromMilliseconds(300)); - EXPECT_TRUE(congestion_control_.OnAck(rtt, &new_bitrate)); - expected_bitrate += 1500 * 8 * 100 / kRttMs; - EXPECT_EQ(expected_bitrate, new_bitrate); - - // Test many short elapsed time (1 ms). - for (int i = 0; i < 19; ++i) { - testing_clock_.Advance(base::TimeDelta::FromMilliseconds(1)); - EXPECT_FALSE(congestion_control_.OnAck(rtt, &new_bitrate)); + void Run(uint32 frames, + size_t frame_size, + base::TimeDelta rtt, + base::TimeDelta frame_delay, + base::TimeDelta ack_time) { + for (frame_id_ = 0; frame_id_ < frames; frame_id_++) { + congestion_control_->UpdateRtt(rtt); + congestion_control_->SendFrameToTransport( + frame_id_, frame_size, testing_clock_.NowTicks()); + task_runner_->PostDelayedTask(FROM_HERE, + base::Bind(&CongestionControlTest::AckFrame, + base::Unretained(this), + frame_id_), + ack_time); + task_runner_->Sleep(frame_delay); + } } - testing_clock_.Advance(base::TimeDelta::FromMilliseconds(1)); - EXPECT_TRUE(congestion_control_.OnAck(rtt, &new_bitrate)); - expected_bitrate += 1500 * 8 * 20 / kRttMs; - EXPECT_EQ(expected_bitrate, new_bitrate); -} -TEST_F(CongestionControlTest, Convergence24fps) { - EXPECT_GE(RunWithOneLossEventPerSecond(24, kRttMs, 100), - GG_UINT32_C(3000000)); -} + base::SimpleTestTickClock testing_clock_; + scoped_ptr<CongestionControl> congestion_control_; + scoped_refptr<test::FakeSingleThreadTaskRunner> task_runner_; + uint32 frame_id_; -TEST_F(CongestionControlTest, Convergence24fpsLongRtt) { - EXPECT_GE(RunWithOneLossEventPerSecond(24, 100, 100), - GG_UINT32_C(500000)); -} + DISALLOW_COPY_AND_ASSIGN(CongestionControlTest); +}; -TEST_F(CongestionControlTest, Convergence60fps) { - EXPECT_GE(RunWithOneLossEventPerSecond(60, kRttMs, 100), - GG_UINT32_C(3500000)); +TEST_F(CongestionControlTest, SimpleRun) { + uint32 frame_delay = 33; + uint32 frame_size = 10000 * 8; + Run(500, + frame_size, + base::TimeDelta::FromMilliseconds(10), + base::TimeDelta::FromMilliseconds(frame_delay), + base::TimeDelta::FromMilliseconds(45)); + // Empty the buffer. + task_runner_->Sleep(base::TimeDelta::FromMilliseconds(100)); + + uint32 safe_bitrate = frame_size * 1000 / frame_delay; + uint32 bitrate = congestion_control_->GetBitrate( + testing_clock_.NowTicks() + base::TimeDelta::FromMilliseconds(300), + base::TimeDelta::FromMilliseconds(300)); + EXPECT_NEAR( + safe_bitrate / kTargetEmptyBufferFraction, bitrate, safe_bitrate * 0.05); + + bitrate = congestion_control_->GetBitrate( + testing_clock_.NowTicks() + base::TimeDelta::FromMilliseconds(200), + base::TimeDelta::FromMilliseconds(300)); + EXPECT_NEAR(safe_bitrate / kTargetEmptyBufferFraction * 2 / 3, + bitrate, + safe_bitrate * 0.05); + + bitrate = congestion_control_->GetBitrate( + testing_clock_.NowTicks() + base::TimeDelta::FromMilliseconds(100), + base::TimeDelta::FromMilliseconds(300)); + EXPECT_NEAR(safe_bitrate / kTargetEmptyBufferFraction * 1 / 3, + bitrate, + safe_bitrate * 0.05); + + // Add a large (100ms) frame. + congestion_control_->SendFrameToTransport( + frame_id_++, safe_bitrate * 100 / 1000, testing_clock_.NowTicks()); + + // Results should show that we have ~200ms to send + bitrate = congestion_control_->GetBitrate( + testing_clock_.NowTicks() + base::TimeDelta::FromMilliseconds(300), + base::TimeDelta::FromMilliseconds(300)); + EXPECT_NEAR(safe_bitrate / kTargetEmptyBufferFraction * 2 / 3, + bitrate, + safe_bitrate * 0.05); + + // Add another large (100ms) frame. + congestion_control_->SendFrameToTransport( + frame_id_++, safe_bitrate * 100 / 1000, testing_clock_.NowTicks()); + + // Resulst should show that we have ~100ms to send + bitrate = congestion_control_->GetBitrate( + testing_clock_.NowTicks() + base::TimeDelta::FromMilliseconds(300), + base::TimeDelta::FromMilliseconds(300)); + EXPECT_NEAR(safe_bitrate / kTargetEmptyBufferFraction * 1 / 3, + bitrate, + safe_bitrate * 0.05); } -TEST_F(CongestionControlTest, Convergence60fpsLongRtt) { - EXPECT_GE(RunWithOneLossEventPerSecond(60, 100, 100), - GG_UINT32_C(500000)); -} } // namespace cast } // namespace media diff --git a/chromium/media/cast/framer/cast_message_builder.cc b/chromium/media/cast/framer/cast_message_builder.cc index 7d89f744315..f3473f96902 100644 --- a/chromium/media/cast/framer/cast_message_builder.cc +++ b/chromium/media/cast/framer/cast_message_builder.cc @@ -23,7 +23,6 @@ CastMessageBuilder::CastMessageBuilder( decoder_faster_than_max_frame_rate_(decoder_faster_than_max_frame_rate), max_unacked_frames_(max_unacked_frames), cast_msg_(media_ssrc), - waiting_for_key_frame_(true), slowing_down_ack_(false), acked_last_frame_(true), last_acked_frame_id_(kStartFrameId) { @@ -32,65 +31,61 @@ CastMessageBuilder::CastMessageBuilder( CastMessageBuilder::~CastMessageBuilder() {} -void CastMessageBuilder::CompleteFrameReceived(uint32 frame_id, - bool is_key_frame) { +void CastMessageBuilder::CompleteFrameReceived(uint32 frame_id) { + DCHECK_GE(static_cast<int32>(frame_id - last_acked_frame_id_), 0); + VLOG(2) << "CompleteFrameReceived: " << frame_id; if (last_update_time_.is_null()) { // Our first update. last_update_time_ = clock_->NowTicks(); } - if (waiting_for_key_frame_) { - if (!is_key_frame) { - // Ignore that we have received this complete frame since we are - // waiting on a key frame. - return; - } - waiting_for_key_frame_ = false; - cast_msg_.missing_frames_and_packets_.clear(); - cast_msg_.ack_frame_id_ = frame_id; - last_update_time_ = clock_->NowTicks(); - // We might have other complete frames waiting after we receive the last - // packet in the key-frame. - UpdateAckMessage(); - } else { - if (!UpdateAckMessage()) return; - BuildPacketList(); + if (!UpdateAckMessage(frame_id)) { + return; } + BuildPacketList(); + // Send cast message. - VLOG(1) << "Send cast message Ack:" << static_cast<int>(frame_id); + VLOG(2) << "Send cast message Ack:" << static_cast<int>(frame_id); cast_feedback_->CastFeedback(cast_msg_); } -bool CastMessageBuilder::UpdateAckMessage() { +bool CastMessageBuilder::UpdateAckMessage(uint32 frame_id) { if (!decoder_faster_than_max_frame_rate_) { int complete_frame_count = frame_id_map_->NumberOfCompleteFrames(); if (complete_frame_count > max_unacked_frames_) { // We have too many frames pending in our framer; slow down ACK. - slowing_down_ack_ = true; + if (!slowing_down_ack_) { + slowing_down_ack_ = true; + ack_queue_.push_back(last_acked_frame_id_); + } } else if (complete_frame_count <= 1) { // We are down to one or less frames in our framer; ACK normally. slowing_down_ack_ = false; + ack_queue_.clear(); } } + if (slowing_down_ack_) { // We are slowing down acknowledgment by acknowledging every other frame. - if (acked_last_frame_) { - acked_last_frame_ = false; - } else { - acked_last_frame_ = true; - last_acked_frame_id_++; - // Note: frame skipping and slowdown ACK is not supported at the same - // time; and it's not needed since we can skip frames to catch up. + // Note: frame skipping and slowdown ACK is not supported at the same + // time; and it's not needed since we can skip frames to catch up. + if (!ack_queue_.empty() && ack_queue_.back() == frame_id) { + return false; } - } else { - uint32 frame_id = frame_id_map_->LastContinuousFrame(); - - // Is it a new frame? - if (last_acked_frame_id_ == frame_id) return false; + ack_queue_.push_back(frame_id); + if (!acked_last_frame_) { + ack_queue_.pop_front(); + } + frame_id = ack_queue_.front(); + } - last_acked_frame_id_ = frame_id; - acked_last_frame_ = true; + acked_last_frame_ = false; + // Is it a new frame? + if (last_acked_frame_id_ == frame_id) { + return false; } + acked_last_frame_ = true; + last_acked_frame_id_ = frame_id; cast_msg_.ack_frame_id_ = last_acked_frame_id_; cast_msg_.missing_frames_and_packets_.clear(); last_update_time_ = clock_->NowTicks(); @@ -100,23 +95,24 @@ bool CastMessageBuilder::UpdateAckMessage() { bool CastMessageBuilder::TimeToSendNextCastMessage( base::TimeTicks* time_to_send) { // We haven't received any packets. - if (last_update_time_.is_null() && frame_id_map_->Empty()) return false; + if (last_update_time_.is_null() && frame_id_map_->Empty()) + return false; - *time_to_send = last_update_time_ + - base::TimeDelta::FromMilliseconds(kCastMessageUpdateIntervalMs); + *time_to_send = last_update_time_ + base::TimeDelta::FromMilliseconds( + kCastMessageUpdateIntervalMs); return true; } void CastMessageBuilder::UpdateCastMessage() { RtcpCastMessage message(media_ssrc_); - if (!UpdateCastMessageInternal(&message)) return; + if (!UpdateCastMessageInternal(&message)) + return; // Send cast message. cast_feedback_->CastFeedback(message); } void CastMessageBuilder::Reset() { - waiting_for_key_frame_ = true; cast_msg_.ack_frame_id_ = kStartFrameId; cast_msg_.missing_frames_and_packets_.clear(); time_last_nacked_map_.clear(); @@ -138,9 +134,10 @@ bool CastMessageBuilder::UpdateCastMessageInternal(RtcpCastMessage* message) { } last_update_time_ = now; - UpdateAckMessage(); // Needed to cover when a frame is skipped. + // Needed to cover when a frame is skipped. + UpdateAckMessage(last_acked_frame_id_); BuildPacketList(); - *message = cast_msg_; + message->Copy(cast_msg_); return true; } @@ -151,7 +148,8 @@ void CastMessageBuilder::BuildPacketList() { cast_msg_.missing_frames_and_packets_.clear(); // Are we missing packets? - if (frame_id_map_->Empty()) return; + if (frame_id_map_->Empty()) + return; uint32 newest_frame_id = frame_id_map_->NewestFrameId(); uint32 next_expected_frame_id = cast_msg_.ack_frame_id_ + 1; @@ -173,8 +171,8 @@ void CastMessageBuilder::BuildPacketList() { PacketIdSet missing; if (frame_id_map_->FrameExists(next_expected_frame_id)) { bool last_frame = (newest_frame_id == next_expected_frame_id); - frame_id_map_->GetMissingPackets(next_expected_frame_id, last_frame, - &missing); + frame_id_map_->GetMissingPackets( + next_expected_frame_id, last_frame, &missing); if (!missing.empty()) { time_last_nacked_map_[next_expected_frame_id] = now; cast_msg_.missing_frames_and_packets_.insert( diff --git a/chromium/media/cast/framer/cast_message_builder.h b/chromium/media/cast/framer/cast_message_builder.h index b76a196111c..9db88d4a990 100644 --- a/chromium/media/cast/framer/cast_message_builder.h +++ b/chromium/media/cast/framer/cast_message_builder.h @@ -7,6 +7,7 @@ #ifndef MEDIA_CAST_FRAMER_CAST_MESSAGE_BUILDER_H_ #define MEDIA_CAST_FRAMER_CAST_MESSAGE_BUILDER_H_ +#include <deque> #include <map> #include "media/cast/framer/frame_id_map.h" @@ -30,13 +31,13 @@ class CastMessageBuilder { int max_unacked_frames); ~CastMessageBuilder(); - void CompleteFrameReceived(uint32 frame_id, bool is_key_frame); + void CompleteFrameReceived(uint32 frame_id); bool TimeToSendNextCastMessage(base::TimeTicks* time_to_send); void UpdateCastMessage(); void Reset(); private: - bool UpdateAckMessage(); + bool UpdateAckMessage(uint32 frame_id); void BuildPacketList(); bool UpdateCastMessageInternal(RtcpCastMessage* message); @@ -51,13 +52,13 @@ class CastMessageBuilder { RtcpCastMessage cast_msg_; base::TimeTicks last_update_time_; - bool waiting_for_key_frame_; TimeLastNackMap time_last_nacked_map_; bool slowing_down_ack_; bool acked_last_frame_; uint32 last_acked_frame_id_; + std::deque<uint32> ack_queue_; DISALLOW_COPY_AND_ASSIGN(CastMessageBuilder); }; diff --git a/chromium/media/cast/framer/cast_message_builder_unittest.cc b/chromium/media/cast/framer/cast_message_builder_unittest.cc index f4b708c90ef..ef75162a086 100644 --- a/chromium/media/cast/framer/cast_message_builder_unittest.cc +++ b/chromium/media/cast/framer/cast_message_builder_unittest.cc @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include <stdint.h> + #include "base/memory/scoped_ptr.h" #include "base/test/simple_test_tick_clock.h" #include "media/cast/framer/cast_message_builder.h" @@ -12,21 +14,18 @@ namespace media { namespace cast { +namespace { static const uint32 kSsrc = 0x1234; static const uint32 kShortTimeIncrementMs = 10; static const uint32 kLongTimeIncrementMs = 40; -static const int64 kStartMillisecond = GG_INT64_C(12345678900000); +static const int64 kStartMillisecond = INT64_C(12345678900000); -namespace { typedef std::map<uint32, size_t> MissingPacketsMap; class NackFeedbackVerification : public RtpPayloadFeedback { public: NackFeedbackVerification() - : triggered_(false), - missing_packets_(), - last_frame_acked_(0) {} - + : triggered_(false), missing_packets_(), last_frame_acked_(0) {} virtual void CastFeedback(const RtcpCastMessage& cast_feedback) OVERRIDE { EXPECT_EQ(kSsrc, cast_feedback.media_ssrc_); @@ -43,10 +42,10 @@ class NackFeedbackVerification : public RtpPayloadFeedback { if ((frame_it->second.size() == 1) && (*frame_it->second.begin() == kRtcpCastAllPacketsLost)) { missing_packets_.insert( - std::make_pair(frame_it->first, kRtcpCastAllPacketsLost)); + std::make_pair(frame_it->first, kRtcpCastAllPacketsLost)); } else { - missing_packets_.insert( - std::make_pair(frame_it->first, frame_it->second.size())); + missing_packets_.insert( + std::make_pair(frame_it->first, frame_it->second.size())); } ++frame_it; } @@ -56,14 +55,15 @@ class NackFeedbackVerification : public RtpPayloadFeedback { size_t num_missing_packets(uint32 frame_id) { MissingPacketsMap::iterator it; it = missing_packets_.find(frame_id); - if (it == missing_packets_.end()) return 0; + if (it == missing_packets_.end()) + return 0; return it->second; } // Holds value for one call. bool triggered() { - bool ret_val = triggered_; + bool ret_val = triggered_; triggered_ = false; return ret_val; } @@ -74,6 +74,8 @@ class NackFeedbackVerification : public RtpPayloadFeedback { bool triggered_; MissingPacketsMap missing_packets_; // Missing packets per frame. uint32 last_frame_acked_; + + DISALLOW_COPY_AND_ASSIGN(NackFeedbackVerification); }; } // namespace @@ -86,7 +88,7 @@ class CastMessageBuilderTest : public ::testing::Test { kSsrc, true, 0)) { - rtp_header_.webrtc.header.ssrc = kSsrc; + rtp_header_.sender_ssrc = kSsrc; rtp_header_.is_key_frame = false; testing_clock_.Advance( base::TimeDelta::FromMilliseconds(kStartMillisecond)); @@ -94,33 +96,23 @@ class CastMessageBuilderTest : public ::testing::Test { virtual ~CastMessageBuilderTest() {} - void SetFrameId(uint32 frame_id) { + void SetFrameIds(uint32 frame_id, uint32 reference_frame_id) { rtp_header_.frame_id = frame_id; + rtp_header_.reference_frame_id = reference_frame_id; } - void SetPacketId(uint16 packet_id) { - rtp_header_.packet_id = packet_id; - } + void SetPacketId(uint16 packet_id) { rtp_header_.packet_id = packet_id; } void SetMaxPacketId(uint16 max_packet_id) { rtp_header_.max_packet_id = max_packet_id; } - void SetKeyFrame(bool is_key) { - rtp_header_.is_key_frame = is_key; - } - - void SetReferenceFrameId(uint32 reference_frame_id) { - rtp_header_.is_reference = true; - rtp_header_.reference_frame_id = reference_frame_id; - } + void SetKeyFrame(bool is_key) { rtp_header_.is_key_frame = is_key; } void InsertPacket() { - bool complete = false; - frame_id_map_.InsertPacket(rtp_header_, &complete); - if (complete) { - cast_msg_builder_->CompleteFrameReceived(rtp_header_.frame_id, - rtp_header_.is_key_frame); + PacketType packet_type = frame_id_map_.InsertPacket(rtp_header_); + if (packet_type == kNewPacketCompletingFrame) { + cast_msg_builder_->CompleteFrameReceived(rtp_header_.frame_id); } cast_msg_builder_->UpdateCastMessage(); } @@ -139,30 +131,12 @@ class CastMessageBuilderTest : public ::testing::Test { RtpCastHeader rtp_header_; FrameIdMap frame_id_map_; base::SimpleTestTickClock testing_clock_; -}; -TEST_F(CastMessageBuilderTest, StartWithAKeyFrame) { - SetFrameId(3); - SetPacketId(0); - SetMaxPacketId(0); - InsertPacket(); - // Should not trigger ack. - EXPECT_FALSE(feedback_.triggered()); - SetFrameId(5); - SetPacketId(0); - SetMaxPacketId(0); - SetKeyFrame(true); - InsertPacket(); - frame_id_map_.RemoveOldFrames(5); // Simulate 5 being pulled for rendering. - testing_clock_.Advance( - base::TimeDelta::FromMilliseconds(kLongTimeIncrementMs)); - cast_msg_builder_->UpdateCastMessage(); - EXPECT_TRUE(feedback_.triggered()); - EXPECT_EQ(5u, feedback_.last_frame_acked()); -} + DISALLOW_COPY_AND_ASSIGN(CastMessageBuilderTest); +}; TEST_F(CastMessageBuilderTest, OneFrameNackList) { - SetFrameId(0); + SetFrameIds(0, 0); SetPacketId(4); SetMaxPacketId(10); InsertPacket(); @@ -178,13 +152,13 @@ TEST_F(CastMessageBuilderTest, OneFrameNackList) { } TEST_F(CastMessageBuilderTest, CompleteFrameMissing) { - SetFrameId(0); + SetFrameIds(0, 0); SetPacketId(2); SetMaxPacketId(5); InsertPacket(); testing_clock_.Advance( base::TimeDelta::FromMilliseconds(kLongTimeIncrementMs)); - SetFrameId(2); + SetFrameIds(2, 1); SetPacketId(2); SetMaxPacketId(5); InsertPacket(); @@ -192,55 +166,30 @@ TEST_F(CastMessageBuilderTest, CompleteFrameMissing) { EXPECT_EQ(kRtcpCastAllPacketsLost, feedback_.num_missing_packets(1)); } -TEST_F(CastMessageBuilderTest, FastForwardAck) { - SetFrameId(1); - SetPacketId(0); - SetMaxPacketId(0); - InsertPacket(); - EXPECT_FALSE(feedback_.triggered()); - testing_clock_.Advance( - base::TimeDelta::FromMilliseconds(kLongTimeIncrementMs)); - SetFrameId(2); - SetPacketId(0); - SetMaxPacketId(0); - InsertPacket(); - EXPECT_TRUE(feedback_.triggered()); - EXPECT_EQ(kStartFrameId, feedback_.last_frame_acked()); - testing_clock_.Advance( - base::TimeDelta::FromMilliseconds(kLongTimeIncrementMs)); - SetFrameId(0); - SetPacketId(0); - SetMaxPacketId(0); - SetKeyFrame(true); - InsertPacket(); - EXPECT_TRUE(feedback_.triggered()); - EXPECT_EQ(2u, feedback_.last_frame_acked()); -} - TEST_F(CastMessageBuilderTest, RemoveOldFrames) { - SetFrameId(1); + SetFrameIds(1, 0); SetPacketId(0); SetMaxPacketId(1); InsertPacket(); EXPECT_FALSE(feedback_.triggered()); testing_clock_.Advance( base::TimeDelta::FromMilliseconds(kLongTimeIncrementMs)); - SetFrameId(2); + SetFrameIds(2, 1); SetPacketId(0); SetMaxPacketId(0); InsertPacket(); EXPECT_TRUE(feedback_.triggered()); testing_clock_.Advance( base::TimeDelta::FromMilliseconds(kLongTimeIncrementMs)); - SetFrameId(3); + SetFrameIds(3, 2); SetPacketId(0); SetMaxPacketId(5); InsertPacket(); EXPECT_TRUE(feedback_.triggered()); - EXPECT_EQ(kStartFrameId, feedback_.last_frame_acked()); + EXPECT_EQ(2u, feedback_.last_frame_acked()); testing_clock_.Advance( base::TimeDelta::FromMilliseconds(kLongTimeIncrementMs)); - SetFrameId(5); + SetFrameIds(5, 5); SetPacketId(0); SetMaxPacketId(0); SetKeyFrame(true); @@ -253,7 +202,7 @@ TEST_F(CastMessageBuilderTest, RemoveOldFrames) { EXPECT_EQ(5u, feedback_.last_frame_acked()); testing_clock_.Advance( base::TimeDelta::FromMilliseconds(kShortTimeIncrementMs)); - SetFrameId(1); + SetFrameIds(1, 0); SetPacketId(1); SetMaxPacketId(1); InsertPacket(); @@ -265,44 +214,8 @@ TEST_F(CastMessageBuilderTest, RemoveOldFrames) { EXPECT_EQ(5u, feedback_.last_frame_acked()); } -TEST_F(CastMessageBuilderTest, WrapFastForward) { - SetFrameId(254); - SetPacketId(0); - SetMaxPacketId(1); - SetKeyFrame(true); - InsertPacket(); - EXPECT_FALSE(feedback_.triggered()); - testing_clock_.Advance( - base::TimeDelta::FromMilliseconds(kLongTimeIncrementMs)); - SetFrameId(255); - SetPacketId(0); - SetMaxPacketId(0); - SetKeyFrame(false); - InsertPacket(); - EXPECT_TRUE(feedback_.triggered()); - EXPECT_EQ(253u, feedback_.last_frame_acked()); - testing_clock_.Advance( - base::TimeDelta::FromMilliseconds(kLongTimeIncrementMs)); - SetFrameId(256); - SetPacketId(0); - SetMaxPacketId(0); - SetKeyFrame(false); - InsertPacket(); - EXPECT_TRUE(feedback_.triggered()); - EXPECT_EQ(253u, feedback_.last_frame_acked()); - testing_clock_.Advance( - base::TimeDelta::FromMilliseconds(kLongTimeIncrementMs)); - SetFrameId(254); - SetPacketId(1); - SetMaxPacketId(1); - SetKeyFrame(true); - InsertPacket(); - EXPECT_TRUE(feedback_.triggered()); - EXPECT_EQ(256u, feedback_.last_frame_acked()); -} - TEST_F(CastMessageBuilderTest, NackUntilMaxReceivedPacket) { - SetFrameId(0); + SetFrameIds(0, 0); SetPacketId(0); SetMaxPacketId(20); SetKeyFrame(true); @@ -316,7 +229,7 @@ TEST_F(CastMessageBuilderTest, NackUntilMaxReceivedPacket) { } TEST_F(CastMessageBuilderTest, NackUntilMaxReceivedPacketNextFrame) { - SetFrameId(0); + SetFrameIds(0, 0); SetPacketId(0); SetMaxPacketId(20); SetKeyFrame(true); @@ -329,7 +242,7 @@ TEST_F(CastMessageBuilderTest, NackUntilMaxReceivedPacketNextFrame) { base::TimeDelta::FromMilliseconds(kLongTimeIncrementMs)); EXPECT_TRUE(feedback_.triggered()); EXPECT_EQ(4u, feedback_.num_missing_packets(0)); - SetFrameId(1); + SetFrameIds(1, 0); SetMaxPacketId(2); SetPacketId(0); SetKeyFrame(false); @@ -341,7 +254,7 @@ TEST_F(CastMessageBuilderTest, NackUntilMaxReceivedPacketNextFrame) { } TEST_F(CastMessageBuilderTest, NackUntilMaxReceivedPacketNextKey) { - SetFrameId(0); + SetFrameIds(0, 0); SetPacketId(0); SetMaxPacketId(20); SetKeyFrame(true); @@ -354,7 +267,7 @@ TEST_F(CastMessageBuilderTest, NackUntilMaxReceivedPacketNextKey) { base::TimeDelta::FromMilliseconds(kLongTimeIncrementMs)); EXPECT_TRUE(feedback_.triggered()); EXPECT_EQ(4u, feedback_.num_missing_packets(0)); - SetFrameId(1); + SetFrameIds(1, 1); SetMaxPacketId(0); SetPacketId(0); SetKeyFrame(true); @@ -378,7 +291,7 @@ TEST_F(CastMessageBuilderTest, Reset) { } TEST_F(CastMessageBuilderTest, DeltaAfterReset) { - SetFrameId(0); + SetFrameIds(0, 0); SetPacketId(0); SetMaxPacketId(0); SetKeyFrame(true); @@ -388,7 +301,7 @@ TEST_F(CastMessageBuilderTest, DeltaAfterReset) { testing_clock_.Advance( base::TimeDelta::FromMilliseconds(kLongTimeIncrementMs)); cast_msg_builder_->Reset(); - SetFrameId(1); + SetFrameIds(1, 0); SetPacketId(0); SetMaxPacketId(0); SetKeyFrame(true); @@ -396,7 +309,7 @@ TEST_F(CastMessageBuilderTest, DeltaAfterReset) { } TEST_F(CastMessageBuilderTest, BasicRps) { - SetFrameId(0); + SetFrameIds(0, 0); SetPacketId(0); SetMaxPacketId(0); SetKeyFrame(true); @@ -405,12 +318,11 @@ TEST_F(CastMessageBuilderTest, BasicRps) { base::TimeDelta::FromMilliseconds(kLongTimeIncrementMs)); EXPECT_TRUE(feedback_.triggered()); EXPECT_EQ(0u, feedback_.last_frame_acked()); - SetFrameId(3); + SetFrameIds(3, 0); SetKeyFrame(false); - SetReferenceFrameId(0); InsertPacket(); EXPECT_TRUE(feedback_.triggered()); - EXPECT_EQ(0u, feedback_.last_frame_acked()); + EXPECT_EQ(3u, feedback_.last_frame_acked()); testing_clock_.Advance( base::TimeDelta::FromMilliseconds(kLongTimeIncrementMs)); frame_id_map_.RemoveOldFrames(3); // Simulate 3 being pulled for rendering. @@ -421,7 +333,7 @@ TEST_F(CastMessageBuilderTest, BasicRps) { TEST_F(CastMessageBuilderTest, InOrderRps) { // Create a pattern - skip to rps, and don't look back. - SetFrameId(0); + SetFrameIds(0, 0); SetPacketId(0); SetMaxPacketId(0); SetKeyFrame(true); @@ -430,7 +342,7 @@ TEST_F(CastMessageBuilderTest, InOrderRps) { base::TimeDelta::FromMilliseconds(kShortTimeIncrementMs)); EXPECT_TRUE(feedback_.triggered()); EXPECT_EQ(0u, feedback_.last_frame_acked()); - SetFrameId(1); + SetFrameIds(1, 0); SetPacketId(0); SetMaxPacketId(1); SetKeyFrame(false); @@ -438,11 +350,10 @@ TEST_F(CastMessageBuilderTest, InOrderRps) { testing_clock_.Advance( base::TimeDelta::FromMilliseconds(kShortTimeIncrementMs)); EXPECT_FALSE(feedback_.triggered()); - SetFrameId(3); + SetFrameIds(3, 0); SetPacketId(0); SetMaxPacketId(0); SetKeyFrame(false); - SetReferenceFrameId(0); InsertPacket(); testing_clock_.Advance( base::TimeDelta::FromMilliseconds(kShortTimeIncrementMs)); @@ -453,7 +364,7 @@ TEST_F(CastMessageBuilderTest, InOrderRps) { EXPECT_TRUE(feedback_.triggered()); EXPECT_EQ(3u, feedback_.last_frame_acked()); // Make an old frame complete - should not trigger an ack. - SetFrameId(1); + SetFrameIds(1, 0); SetPacketId(1); SetMaxPacketId(1); SetKeyFrame(false); @@ -466,7 +377,7 @@ TEST_F(CastMessageBuilderTest, InOrderRps) { TEST_F(CastMessageBuilderTest, SlowDownAck) { SetDecoderSlowerThanMaxFrameRate(3); - SetFrameId(0); + SetFrameIds(0, 0); SetPacketId(0); SetMaxPacketId(0); SetKeyFrame(true); @@ -479,30 +390,34 @@ TEST_F(CastMessageBuilderTest, SlowDownAck) { for (frame_id = 1; frame_id < 3; ++frame_id) { EXPECT_TRUE(feedback_.triggered()); EXPECT_EQ(frame_id - 1, feedback_.last_frame_acked()); - SetFrameId(frame_id); + SetFrameIds(frame_id, frame_id - 1); InsertPacket(); testing_clock_.Advance( - base::TimeDelta::FromMilliseconds(kShortTimeIncrementMs)); + base::TimeDelta::FromMilliseconds(kShortTimeIncrementMs)); } // We should now have entered the slowdown ACK state. uint32 expected_frame_id = 1; for (; frame_id < 10; ++frame_id) { - if (frame_id % 2) ++expected_frame_id; - EXPECT_TRUE(feedback_.triggered()); + if (frame_id % 2) { + ++expected_frame_id; + EXPECT_TRUE(feedback_.triggered()); + } else { + EXPECT_FALSE(feedback_.triggered()); + } EXPECT_EQ(expected_frame_id, feedback_.last_frame_acked()); - SetFrameId(frame_id); + SetFrameIds(frame_id, frame_id - 1); InsertPacket(); testing_clock_.Advance( - base::TimeDelta::FromMilliseconds(kShortTimeIncrementMs)); + base::TimeDelta::FromMilliseconds(kShortTimeIncrementMs)); } - EXPECT_TRUE(feedback_.triggered()); + EXPECT_FALSE(feedback_.triggered()); EXPECT_EQ(expected_frame_id, feedback_.last_frame_acked()); // Simulate frame_id being pulled for rendering. frame_id_map_.RemoveOldFrames(frame_id); // We should now leave the slowdown ACK state. ++frame_id; - SetFrameId(frame_id); + SetFrameIds(frame_id, frame_id - 1); InsertPacket(); testing_clock_.Advance( base::TimeDelta::FromMilliseconds(kShortTimeIncrementMs)); diff --git a/chromium/media/cast/framer/frame_buffer.cc b/chromium/media/cast/framer/frame_buffer.cc index ca9f1dedd28..0b6fa8332cd 100644 --- a/chromium/media/cast/framer/frame_buffer.cc +++ b/chromium/media/cast/framer/frame_buffer.cc @@ -28,21 +28,17 @@ void FrameBuffer::InsertPacket(const uint8* payload_data, frame_id_ = rtp_header.frame_id; max_packet_id_ = rtp_header.max_packet_id; is_key_frame_ = rtp_header.is_key_frame; - if (rtp_header.is_reference) { - last_referenced_frame_id_ = rtp_header.reference_frame_id; - } else { - last_referenced_frame_id_ = rtp_header.frame_id - 1; - } - - rtp_timestamp_ = rtp_header.webrtc.header.timestamp; + if (is_key_frame_) + DCHECK_EQ(rtp_header.frame_id, rtp_header.reference_frame_id); + last_referenced_frame_id_ = rtp_header.reference_frame_id; + rtp_timestamp_ = rtp_header.rtp_timestamp; } // Is this the correct frame? - if (rtp_header.frame_id != frame_id_) return; + if (rtp_header.frame_id != frame_id_) + return; // Insert every packet only once. if (packets_.find(rtp_header.packet_id) != packets_.end()) { - VLOG(3) << "Packet already received, ignored: frame " - << frame_id_ << ", packet " << rtp_header.packet_id; return; } @@ -52,8 +48,8 @@ void FrameBuffer::InsertPacket(const uint8* payload_data, // Insert the packet. retval.first->second.resize(payload_size); - std::copy(payload_data, payload_data + payload_size, - retval.first->second.begin()); + std::copy( + payload_data, payload_data + payload_size, retval.first->second.begin()); ++num_packets_received_; total_data_size_ += payload_size; @@ -63,45 +59,27 @@ bool FrameBuffer::Complete() const { return num_packets_received_ - 1 == max_packet_id_; } -bool FrameBuffer::GetEncodedAudioFrame(EncodedAudioFrame* audio_frame, - uint32* rtp_timestamp) const { - if (!Complete()) return false; - - *rtp_timestamp = rtp_timestamp_; - - // Frame is complete -> construct. - audio_frame->frame_id = frame_id_; - - // Build the data vector. - audio_frame->data.clear(); - audio_frame->data.reserve(total_data_size_); - PacketMap::const_iterator it; - for (it = packets_.begin(); it != packets_.end(); ++it) { - audio_frame->data.insert(audio_frame->data.end(), - it->second.begin(), it->second.end()); - } - return true; -} - -bool FrameBuffer::GetEncodedVideoFrame(EncodedVideoFrame* video_frame, - uint32* rtp_timestamp) const { - if (!Complete()) return false; - - *rtp_timestamp = rtp_timestamp_; +bool FrameBuffer::AssembleEncodedFrame(transport::EncodedFrame* frame) const { + if (!Complete()) + return false; // Frame is complete -> construct. - video_frame->key_frame = is_key_frame_; - video_frame->frame_id = frame_id_; - video_frame->last_referenced_frame_id = last_referenced_frame_id_; + if (is_key_frame_) + frame->dependency = transport::EncodedFrame::KEY; + else if (frame_id_ == last_referenced_frame_id_) + frame->dependency = transport::EncodedFrame::INDEPENDENT; + else + frame->dependency = transport::EncodedFrame::DEPENDENT; + frame->frame_id = frame_id_; + frame->referenced_frame_id = last_referenced_frame_id_; + frame->rtp_timestamp = rtp_timestamp_; // Build the data vector. - video_frame->data.clear(); - video_frame->data.reserve(total_data_size_); + frame->data.clear(); + frame->data.reserve(total_data_size_); PacketMap::const_iterator it; - for (it = packets_.begin(); it != packets_.end(); ++it) { - video_frame->data.insert(video_frame->data.end(), - it->second.begin(), it->second.end()); - } + for (it = packets_.begin(); it != packets_.end(); ++it) + frame->data.insert(frame->data.end(), it->second.begin(), it->second.end()); return true; } diff --git a/chromium/media/cast/framer/frame_buffer.h b/chromium/media/cast/framer/frame_buffer.h index b99f2b2582d..d4d5dedbbde 100644 --- a/chromium/media/cast/framer/frame_buffer.h +++ b/chromium/media/cast/framer/frame_buffer.h @@ -25,11 +25,11 @@ class FrameBuffer { const RtpCastHeader& rtp_header); bool Complete() const; - bool GetEncodedAudioFrame(EncodedAudioFrame* audio_frame, - uint32* rtp_timestamp) const; - - bool GetEncodedVideoFrame(EncodedVideoFrame* video_frame, - uint32* rtp_timestamp) const; + // If a frame is complete, sets the frame IDs and RTP timestamp in |frame|, + // and also copies the data from all packets into the data field in |frame|. + // Returns true if the frame was complete; false if incomplete and |frame| + // remains unchanged. + bool AssembleEncodedFrame(transport::EncodedFrame* frame) const; bool is_key_frame() const { return is_key_frame_; } diff --git a/chromium/media/cast/framer/frame_buffer_unittest.cc b/chromium/media/cast/framer/frame_buffer_unittest.cc index fb14da39f7f..d6844f3e952 100644 --- a/chromium/media/cast/framer/frame_buffer_unittest.cc +++ b/chromium/media/cast/framer/frame_buffer_unittest.cc @@ -10,42 +10,45 @@ namespace cast { class FrameBufferTest : public ::testing::Test { protected: - FrameBufferTest() {} + FrameBufferTest() { + payload_.assign(kMaxIpPacketSize, 0); + } virtual ~FrameBufferTest() {} - virtual void SetUp() { - payload_.assign(kIpPacketSize, 0); - - // Build a default one packet frame - populate webrtc header. - rtp_header_.is_key_frame = false; - rtp_header_.frame_id = 0; - rtp_header_.packet_id = 0; - rtp_header_.max_packet_id = 0; - rtp_header_.is_reference = false; - rtp_header_.reference_frame_id = 0; - } - FrameBuffer buffer_; std::vector<uint8> payload_; RtpCastHeader rtp_header_; + + DISALLOW_COPY_AND_ASSIGN(FrameBufferTest); }; +TEST_F(FrameBufferTest, OnePacketInsertSanity) { + rtp_header_.rtp_timestamp = 3000; + rtp_header_.is_key_frame = true; + rtp_header_.frame_id = 5; + rtp_header_.reference_frame_id = 5; + buffer_.InsertPacket(payload_.data(), payload_.size(), rtp_header_); + transport::EncodedFrame frame; + EXPECT_TRUE(buffer_.AssembleEncodedFrame(&frame)); + EXPECT_EQ(transport::EncodedFrame::KEY, frame.dependency); + EXPECT_EQ(5u, frame.frame_id); + EXPECT_EQ(5u, frame.referenced_frame_id); + EXPECT_EQ(3000u, frame.rtp_timestamp); +} + TEST_F(FrameBufferTest, EmptyBuffer) { EXPECT_FALSE(buffer_.Complete()); - EXPECT_FALSE(buffer_.is_key_frame()); - EncodedVideoFrame frame; - uint32 rtp_timestamp; - EXPECT_FALSE(buffer_.GetEncodedVideoFrame(&frame, &rtp_timestamp)); + transport::EncodedFrame frame; + EXPECT_FALSE(buffer_.AssembleEncodedFrame(&frame)); } TEST_F(FrameBufferTest, DefaultOnePacketFrame) { buffer_.InsertPacket(payload_.data(), payload_.size(), rtp_header_); EXPECT_TRUE(buffer_.Complete()); EXPECT_FALSE(buffer_.is_key_frame()); - EncodedVideoFrame frame; - uint32 rtp_timestamp; - EXPECT_TRUE(buffer_.GetEncodedVideoFrame(&frame, &rtp_timestamp)); + transport::EncodedFrame frame; + EXPECT_TRUE(buffer_.AssembleEncodedFrame(&frame)); EXPECT_EQ(payload_.size(), frame.data.size()); } @@ -60,13 +63,12 @@ TEST_F(FrameBufferTest, MultiplePacketFrame) { ++rtp_header_.packet_id; EXPECT_TRUE(buffer_.Complete()); EXPECT_TRUE(buffer_.is_key_frame()); - EncodedVideoFrame frame; - uint32 rtp_timestamp; - EXPECT_TRUE(buffer_.GetEncodedVideoFrame(&frame, &rtp_timestamp)); + transport::EncodedFrame frame; + EXPECT_TRUE(buffer_.AssembleEncodedFrame(&frame)); EXPECT_EQ(3 * payload_.size(), frame.data.size()); } -TEST_F(FrameBufferTest, InCompleteFrame) { +TEST_F(FrameBufferTest, IncompleteFrame) { rtp_header_.max_packet_id = 4; buffer_.InsertPacket(payload_.data(), payload_.size(), rtp_header_); ++rtp_header_.packet_id; diff --git a/chromium/media/cast/framer/frame_id_map.cc b/chromium/media/cast/framer/frame_id_map.cc index bd9b943371c..b4389fd5323 100644 --- a/chromium/media/cast/framer/frame_id_map.cc +++ b/chromium/media/cast/framer/frame_id_map.cc @@ -18,28 +18,27 @@ FrameInfo::FrameInfo(uint32 frame_id, frame_id_(frame_id), referenced_frame_id_(referenced_frame_id), max_received_packet_id_(0) { - if (max_packet_id > 0) { - // Create the set with all packets missing. - for (uint16 i = 0; i <= max_packet_id; i++) { - missing_packets_.insert(i); - } + // Create the set with all packets missing. + for (uint16 i = 0; i <= max_packet_id; i++) { + missing_packets_.insert(i); } } FrameInfo::~FrameInfo() {} -bool FrameInfo::InsertPacket(uint16 packet_id) { +PacketType FrameInfo::InsertPacket(uint16 packet_id) { + if (missing_packets_.find(packet_id) == missing_packets_.end()) { + return kDuplicatePacket; + } // Update the last received packet id. if (IsNewerPacketId(packet_id, max_received_packet_id_)) { max_received_packet_id_ = packet_id; } missing_packets_.erase(packet_id); - return missing_packets_.empty(); + return missing_packets_.empty() ? kNewPacketCompletingFrame : kNewPacket; } -bool FrameInfo::Complete() const { - return missing_packets_.empty(); -} +bool FrameInfo::Complete() const { return missing_packets_.empty(); } void FrameInfo::GetMissingPackets(bool newest_frame, PacketIdSet* missing_packets) const { @@ -53,35 +52,29 @@ void FrameInfo::GetMissingPackets(bool newest_frame, } } - FrameIdMap::FrameIdMap() : waiting_for_key_(true), last_released_frame_(kStartFrameId), - newest_frame_id_(kStartFrameId) { -} + newest_frame_id_(kStartFrameId) {} FrameIdMap::~FrameIdMap() {} -bool FrameIdMap::InsertPacket(const RtpCastHeader& rtp_header, bool* complete) { +PacketType FrameIdMap::InsertPacket(const RtpCastHeader& rtp_header) { uint32 frame_id = rtp_header.frame_id; uint32 reference_frame_id; - if (rtp_header.is_reference) { - reference_frame_id = rtp_header.reference_frame_id; - } else { - reference_frame_id = static_cast<uint32>(frame_id - 1); - } + reference_frame_id = rtp_header.reference_frame_id; if (rtp_header.is_key_frame && waiting_for_key_) { last_released_frame_ = static_cast<uint32>(frame_id - 1); waiting_for_key_ = false; } - VLOG(1) << "InsertPacket frame:" << frame_id + VLOG(3) << "InsertPacket frame:" << frame_id << " packet:" << static_cast<int>(rtp_header.packet_id) << " max packet:" << static_cast<int>(rtp_header.max_packet_id); if (IsOlderFrameId(frame_id, last_released_frame_) && !waiting_for_key_) { - return false; + return kTooOldPacket; } // Update the last received frame id. @@ -91,6 +84,7 @@ bool FrameIdMap::InsertPacket(const RtpCastHeader& rtp_header, bool* complete) { // Does this packet belong to a new frame? FrameMap::iterator it = frame_map_.find(frame_id); + PacketType packet_type; if (it == frame_map_.end()) { // New frame. linked_ptr<FrameInfo> frame_info(new FrameInfo(frame_id, @@ -100,12 +94,12 @@ bool FrameIdMap::InsertPacket(const RtpCastHeader& rtp_header, bool* complete) { std::pair<FrameMap::iterator, bool> retval = frame_map_.insert(std::make_pair(frame_id, frame_info)); - *complete = retval.first->second->InsertPacket(rtp_header.packet_id); + packet_type = retval.first->second->InsertPacket(rtp_header.packet_id); } else { // Insert packet to existing frame. - *complete = it->second->InsertPacket(rtp_header.packet_id); + packet_type = it->second->InsertPacket(rtp_header.packet_id); } - return true; + return packet_type; } void FrameIdMap::RemoveOldFrames(uint32 frame_id) { @@ -129,9 +123,7 @@ void FrameIdMap::Clear() { newest_frame_id_ = kStartFrameId; } -uint32 FrameIdMap::NewestFrameId() const { - return newest_frame_id_; -} +uint32 FrameIdMap::NewestFrameId() const { return newest_frame_id_; } bool FrameIdMap::NextContinuousFrame(uint32* frame_id) const { FrameMap::const_iterator it; @@ -145,6 +137,22 @@ bool FrameIdMap::NextContinuousFrame(uint32* frame_id) const { return false; } +bool FrameIdMap::HaveMultipleDecodableFrames() const { + // Find the oldest decodable frame. + FrameMap::const_iterator it; + bool found_one = false; + for (it = frame_map_.begin(); it != frame_map_.end(); ++it) { + if (it->second->Complete() && DecodableFrame(it->second.get())) { + if (found_one) { + return true; + } else { + found_one = true; + } + } + } + return false; +} + uint32 FrameIdMap::LastContinuousFrame() const { uint32 last_continuous_frame_id = last_released_frame_; uint32 next_expected_frame = last_released_frame_; @@ -154,8 +162,10 @@ uint32 FrameIdMap::LastContinuousFrame() const { do { next_expected_frame++; it = frame_map_.find(next_expected_frame); - if (it == frame_map_.end()) break; - if (!it->second->Complete()) break; + if (it == frame_map_.end()) + break; + if (!it->second->Complete()) + break; // We found the next continuous frame. last_continuous_frame_id = it->first; @@ -163,52 +173,26 @@ uint32 FrameIdMap::LastContinuousFrame() const { return last_continuous_frame_id; } -bool FrameIdMap::NextAudioFrameAllowingMissingFrames(uint32* frame_id) const { - // First check if we have continuous frames. - if (NextContinuousFrame(frame_id)) return true; - - // Find the oldest frame. - FrameMap::const_iterator it_best_match = frame_map_.end(); - FrameMap::const_iterator it; - - // Find first complete frame. - for (it = frame_map_.begin(); it != frame_map_.end(); ++it) { - if (it->second->Complete()) { - it_best_match = it; - break; - } - } - if (it_best_match == frame_map_.end()) return false; // No complete frame. - - ++it; - for (; it != frame_map_.end(); ++it) { - if (it->second->Complete() && - IsOlderFrameId(it->first, it_best_match->first)) { - it_best_match = it; - } - } - *frame_id = it_best_match->first; - return true; -} - -bool FrameIdMap::NextVideoFrameAllowingSkippingFrames(uint32* frame_id) const { +bool FrameIdMap::NextFrameAllowingSkippingFrames(uint32* frame_id) const { // Find the oldest decodable frame. FrameMap::const_iterator it_best_match = frame_map_.end(); FrameMap::const_iterator it; for (it = frame_map_.begin(); it != frame_map_.end(); ++it) { - if (it->second->Complete() && DecodableVideoFrame(it->second.get())) { - it_best_match = it; + if (it->second->Complete() && DecodableFrame(it->second.get())) { + if (it_best_match == frame_map_.end() || + IsOlderFrameId(it->first, it_best_match->first)) { + it_best_match = it; + } } } - if (it_best_match == frame_map_.end()) return false; + if (it_best_match == frame_map_.end()) + return false; *frame_id = it_best_match->first; return true; } -bool FrameIdMap::Empty() const { - return frame_map_.empty(); -} +bool FrameIdMap::Empty() const { return frame_map_.empty(); } int FrameIdMap::NumberOfCompleteFrames() const { int count = 0; @@ -229,20 +213,27 @@ void FrameIdMap::GetMissingPackets(uint32 frame_id, bool last_frame, PacketIdSet* missing_packets) const { FrameMap::const_iterator it = frame_map_.find(frame_id); - if (it == frame_map_.end()) return; + if (it == frame_map_.end()) + return; it->second->GetMissingPackets(last_frame, missing_packets); } bool FrameIdMap::ContinuousFrame(FrameInfo* frame) const { DCHECK(frame); - if (waiting_for_key_ && !frame->is_key_frame()) return false; + if (waiting_for_key_ && !frame->is_key_frame()) + return false; return static_cast<uint32>(last_released_frame_ + 1) == frame->frame_id(); } -bool FrameIdMap::DecodableVideoFrame(FrameInfo* frame) const { - if (frame->is_key_frame()) return true; - if (waiting_for_key_ && !frame->is_key_frame()) return false; +bool FrameIdMap::DecodableFrame(FrameInfo* frame) const { + if (frame->is_key_frame()) + return true; + if (waiting_for_key_ && !frame->is_key_frame()) + return false; + // Self-reference? + if (frame->referenced_frame_id() == frame->frame_id()) + return true; // Current frame is not necessarily referencing the last frame. // Do we have the reference frame? diff --git a/chromium/media/cast/framer/frame_id_map.h b/chromium/media/cast/framer/frame_id_map.h index 40b0a7f3399..66e306f6718 100644 --- a/chromium/media/cast/framer/frame_id_map.h +++ b/chromium/media/cast/framer/frame_id_map.h @@ -25,11 +25,9 @@ class FrameInfo { bool key_frame); ~FrameInfo(); - // Returns true if packet is inserted. - bool InsertPacket(uint16 packet_id); + PacketType InsertPacket(uint16 packet_id); bool Complete() const; - void GetMissingPackets(bool newest_frame, - PacketIdSet* missing_packets) const; + void GetMissingPackets(bool newest_frame, PacketIdSet* missing_packets) const; bool is_key_frame() const { return is_key_frame_; } uint32 frame_id() const { return frame_id_; } @@ -53,8 +51,7 @@ class FrameIdMap { FrameIdMap(); ~FrameIdMap(); - // Returns false if not a valid (old) packet, otherwise returns true. - bool InsertPacket(const RtpCastHeader& rtp_header, bool* complete); + PacketType InsertPacket(const RtpCastHeader& rtp_header); bool Empty() const; bool FrameExists(uint32 frame_id) const; @@ -67,8 +64,8 @@ class FrameIdMap { bool NextContinuousFrame(uint32* frame_id) const; uint32 LastContinuousFrame() const; - bool NextAudioFrameAllowingMissingFrames(uint32* frame_id) const; - bool NextVideoFrameAllowingSkippingFrames(uint32* frame_id) const; + bool NextFrameAllowingSkippingFrames(uint32* frame_id) const; + bool HaveMultipleDecodableFrames() const; int NumberOfCompleteFrames() const; void GetMissingPackets(uint32 frame_id, @@ -77,7 +74,7 @@ class FrameIdMap { private: bool ContinuousFrame(FrameInfo* frame) const; - bool DecodableVideoFrame(FrameInfo* frame) const; + bool DecodableFrame(FrameInfo* frame) const; FrameMap frame_map_; bool waiting_for_key_; diff --git a/chromium/media/cast/framer/framer.cc b/chromium/media/cast/framer/framer.cc index b06e60fd035..de4451a3b4a 100644 --- a/chromium/media/cast/framer/framer.cc +++ b/chromium/media/cast/framer/framer.cc @@ -17,9 +17,13 @@ Framer::Framer(base::TickClock* clock, bool decoder_faster_than_max_frame_rate, int max_unacked_frames) : decoder_faster_than_max_frame_rate_(decoder_faster_than_max_frame_rate), - cast_msg_builder_(new CastMessageBuilder(clock, incoming_payload_feedback, - &frame_id_map_, ssrc, decoder_faster_than_max_frame_rate, - max_unacked_frames)) { + cast_msg_builder_( + new CastMessageBuilder(clock, + incoming_payload_feedback, + &frame_id_map_, + ssrc, + decoder_faster_than_max_frame_rate, + max_unacked_frames)) { DCHECK(incoming_payload_feedback) << "Invalid argument"; } @@ -27,9 +31,20 @@ Framer::~Framer() {} bool Framer::InsertPacket(const uint8* payload_data, size_t payload_size, - const RtpCastHeader& rtp_header) { - bool complete = false; - if (!frame_id_map_.InsertPacket(rtp_header, &complete)) return false; + const RtpCastHeader& rtp_header, + bool* duplicate) { + *duplicate = false; + PacketType packet_type = frame_id_map_.InsertPacket(rtp_header); + if (packet_type == kTooOldPacket) { + return false; + } + if (packet_type == kDuplicatePacket) { + VLOG(3) << "Packet already received, ignored: frame " + << static_cast<int>(rtp_header.frame_id) << ", packet " + << rtp_header.packet_id; + *duplicate = true; + return false; + } // Does this packet belong to a new frame? FrameList::iterator it = frames_.find(rtp_header.frame_id); @@ -43,42 +58,15 @@ bool Framer::InsertPacket(const uint8* payload_data, it->second->InsertPacket(payload_data, payload_size, rtp_header); } - if (complete) { - // ACK as soon as possible. - VLOG(1) << "Complete frame " << static_cast<int>(rtp_header.frame_id); - cast_msg_builder_->CompleteFrameReceived(rtp_header.frame_id, - rtp_header.is_key_frame); - } - return complete; + return packet_type == kNewPacketCompletingFrame; } // This does not release the frame. -bool Framer::GetEncodedAudioFrame(EncodedAudioFrame* audio_frame, - uint32* rtp_timestamp, - bool* next_frame) { - uint32 frame_id; - // Find frame id. - if (frame_id_map_.NextContinuousFrame(&frame_id)) { - // We have our next frame. - *next_frame = true; - } else { - if (!frame_id_map_.NextAudioFrameAllowingMissingFrames(&frame_id)) { - return false; - } - *next_frame = false; - } - - ConstFrameIterator it = frames_.find(frame_id); - DCHECK(it != frames_.end()); - if (it == frames_.end()) return false; - - return it->second->GetEncodedAudioFrame(audio_frame, rtp_timestamp); -} +bool Framer::GetEncodedFrame(transport::EncodedFrame* frame, + bool* next_frame, + bool* have_multiple_decodable_frames) { + *have_multiple_decodable_frames = frame_id_map_.HaveMultipleDecodableFrames(); -// This does not release the frame. -bool Framer::GetEncodedVideoFrame(EncodedVideoFrame* video_frame, - uint32* rtp_timestamp, - bool* next_frame) { uint32 frame_id; // Find frame id. if (frame_id_map_.NextContinuousFrame(&frame_id)) { @@ -86,19 +74,26 @@ bool Framer::GetEncodedVideoFrame(EncodedVideoFrame* video_frame, *next_frame = true; } else { // Check if we can skip frames when our decoder is too slow. - if (!decoder_faster_than_max_frame_rate_) return false; + if (!decoder_faster_than_max_frame_rate_) + return false; - if (!frame_id_map_.NextVideoFrameAllowingSkippingFrames(&frame_id)) { + if (!frame_id_map_.NextFrameAllowingSkippingFrames(&frame_id)) { return false; } *next_frame = false; } + if (*next_frame) { + VLOG(2) << "ACK frame " << frame_id; + cast_msg_builder_->CompleteFrameReceived(frame_id); + } + ConstFrameIterator it = frames_.find(frame_id); DCHECK(it != frames_.end()); - if (it == frames_.end()) return false; + if (it == frames_.end()) + return false; - return it->second->GetEncodedVideoFrame(video_frame, rtp_timestamp); + return it->second->AssembleEncodedFrame(frame); } void Framer::Reset() { @@ -114,7 +109,7 @@ void Framer::ReleaseFrame(uint32 frame_id) { // We have a frame - remove all frames with lower frame id. bool skipped_old_frame = false; FrameList::iterator it; - for (it = frames_.begin(); it != frames_.end(); ) { + for (it = frames_.begin(); it != frames_.end();) { if (IsOlderFrameId(it->first, frame_id)) { frames_.erase(it++); skipped_old_frame = true; @@ -131,9 +126,7 @@ bool Framer::TimeToSendNextCastMessage(base::TimeTicks* time_to_send) { return cast_msg_builder_->TimeToSendNextCastMessage(time_to_send); } -void Framer::SendCastMessage() { - cast_msg_builder_->UpdateCastMessage(); -} +void Framer::SendCastMessage() { cast_msg_builder_->UpdateCastMessage(); } } // namespace cast } // namespace media diff --git a/chromium/media/cast/framer/framer.gyp b/chromium/media/cast/framer/framer.gyp deleted file mode 100644 index 7b124f0c5de..00000000000 --- a/chromium/media/cast/framer/framer.gyp +++ /dev/null @@ -1,27 +0,0 @@ -# Copyright 2013 The Chromium Authors. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - -{ - 'targets': [ - { - 'target_name': 'cast_framer', - 'type': 'static_library', - 'include_dirs': [ - '<(DEPTH)/', - '<(DEPTH)/third_party/', - '<(DEPTH)/third_party/webrtc', - ], - 'sources': [ - 'cast_message_builder.cc', - 'cast_message_builder.h', - 'frame_buffer.cc', - 'frame_buffer.h', - 'frame_id_map.cc', - 'frame_id_map.h', - 'framer.cc', - 'framer.h', - ], - }, - ], # targets -} diff --git a/chromium/media/cast/framer/framer.h b/chromium/media/cast/framer/framer.h index cf72da6c35d..0b7249eff34 100644 --- a/chromium/media/cast/framer/framer.h +++ b/chromium/media/cast/framer/framer.h @@ -33,22 +33,21 @@ class Framer { ~Framer(); // Return true when receiving the last packet in a frame, creating a - // complete frame. + // complete frame. If a duplicate packet for an already complete frame is + // received, the function returns false but sets |duplicate| to true. bool InsertPacket(const uint8* payload_data, size_t payload_size, - const RtpCastHeader& rtp_header); - - // Extracts a complete encoded frame - will only return a complete continuous - // frame. - // Returns false if the frame does not exist or if the frame is not complete - // within the given time frame. - bool GetEncodedVideoFrame(EncodedVideoFrame* video_frame, - uint32* rtp_timestamp, - bool* next_frame); - - bool GetEncodedAudioFrame(EncodedAudioFrame* audio_frame, - uint32* rtp_timestamp, - bool* next_frame); + const RtpCastHeader& rtp_header, + bool* duplicate); + + // Extracts a complete encoded frame - will only return a complete and + // decodable frame. Returns false if no such frames exist. + // |next_frame| will be set to true if the returned frame is the very + // next frame. |have_multiple_complete_frames| will be set to true + // if there are more decodadble frames available. + bool GetEncodedFrame(transport::EncodedFrame* video_frame, + bool* next_frame, + bool* have_multiple_complete_frames); void ReleaseFrame(uint32 frame_id); diff --git a/chromium/media/cast/framer/framer_unittest.cc b/chromium/media/cast/framer/framer_unittest.cc index 871f048af46..ad53ef06eee 100644 --- a/chromium/media/cast/framer/framer_unittest.cc +++ b/chromium/media/cast/framer/framer_unittest.cc @@ -15,317 +15,484 @@ class FramerTest : public ::testing::Test { FramerTest() : mock_rtp_payload_feedback_(), framer_(&testing_clock_, &mock_rtp_payload_feedback_, 0, true, 0) { + payload_.assign(kMaxIpPacketSize, 0); + + EXPECT_CALL(mock_rtp_payload_feedback_, CastFeedback(testing::_)) + .WillRepeatedly(testing::Return()); } virtual ~FramerTest() {} - virtual void SetUp() OVERRIDE { - // Build a default one packet frame - populate webrtc header. - rtp_header_.is_key_frame = false; - rtp_header_.frame_id = 0; - rtp_header_.packet_id = 0; - rtp_header_.max_packet_id = 0; - rtp_header_.is_reference = false; - rtp_header_.reference_frame_id = 0; - payload_.assign(kIpPacketSize, 0); - - EXPECT_CALL(mock_rtp_payload_feedback_, - CastFeedback(testing::_)).WillRepeatedly(testing::Return()); - } - std::vector<uint8> payload_; RtpCastHeader rtp_header_; MockRtpPayloadFeedback mock_rtp_payload_feedback_; Framer framer_; base::SimpleTestTickClock testing_clock_; -}; + DISALLOW_COPY_AND_ASSIGN(FramerTest); +}; TEST_F(FramerTest, EmptyState) { - EncodedVideoFrame frame; - uint32 rtp_timestamp; + transport::EncodedFrame frame; bool next_frame = false; - EXPECT_FALSE(framer_.GetEncodedVideoFrame(&frame, &rtp_timestamp, - &next_frame)); + bool multiple = false; + EXPECT_FALSE(framer_.GetEncodedFrame(&frame, &next_frame, &multiple)); } TEST_F(FramerTest, AlwaysStartWithKey) { - EncodedVideoFrame frame; - uint32 rtp_timestamp; + transport::EncodedFrame frame; bool next_frame = false; + bool complete = false; + bool multiple = false; + bool duplicate = false; // Insert non key first frame. - framer_.InsertPacket(payload_.data(), payload_.size(), rtp_header_); - EXPECT_FALSE(framer_.GetEncodedVideoFrame(&frame, &rtp_timestamp, - &next_frame)); + complete = framer_.InsertPacket( + payload_.data(), payload_.size(), rtp_header_, &duplicate); + EXPECT_TRUE(complete); + EXPECT_FALSE(framer_.GetEncodedFrame(&frame, &next_frame, &multiple)); rtp_header_.frame_id = 1; + rtp_header_.reference_frame_id = 1; rtp_header_.is_key_frame = true; - framer_.InsertPacket(payload_.data(), payload_.size(), rtp_header_); - EXPECT_TRUE(framer_.GetEncodedVideoFrame(&frame, &rtp_timestamp, - &next_frame)); + complete = framer_.InsertPacket( + payload_.data(), payload_.size(), rtp_header_, &duplicate); + EXPECT_TRUE(complete); + EXPECT_TRUE(framer_.GetEncodedFrame(&frame, &next_frame, &multiple)); EXPECT_TRUE(next_frame); + EXPECT_TRUE(multiple); + EXPECT_EQ(transport::EncodedFrame::KEY, frame.dependency); EXPECT_EQ(1u, frame.frame_id); - EXPECT_TRUE(frame.key_frame); + EXPECT_EQ(1u, frame.referenced_frame_id); framer_.ReleaseFrame(frame.frame_id); } TEST_F(FramerTest, CompleteFrame) { - EncodedVideoFrame frame; - uint32 rtp_timestamp; + transport::EncodedFrame frame; bool next_frame = false; + bool complete = false; + bool multiple = false; + bool duplicate = false; - // start with a complete key frame. + // Start with a complete key frame. rtp_header_.is_key_frame = true; - framer_.InsertPacket(payload_.data(), payload_.size(), rtp_header_); - EXPECT_TRUE(framer_.GetEncodedVideoFrame(&frame, &rtp_timestamp, - &next_frame)); + complete = framer_.InsertPacket( + payload_.data(), payload_.size(), rtp_header_, &duplicate); + EXPECT_TRUE(complete); + EXPECT_TRUE(framer_.GetEncodedFrame(&frame, &next_frame, &multiple)); EXPECT_TRUE(next_frame); + EXPECT_FALSE(multiple); + EXPECT_EQ(transport::EncodedFrame::KEY, frame.dependency); EXPECT_EQ(0u, frame.frame_id); - EXPECT_TRUE(frame.key_frame); + EXPECT_EQ(0u, frame.referenced_frame_id); framer_.ReleaseFrame(frame.frame_id); // Incomplete delta. ++rtp_header_.frame_id; + rtp_header_.reference_frame_id = rtp_header_.frame_id - 1; rtp_header_.is_key_frame = false; rtp_header_.max_packet_id = 2; - framer_.InsertPacket(payload_.data(), payload_.size(), rtp_header_); - EXPECT_FALSE(framer_.GetEncodedVideoFrame(&frame, &rtp_timestamp, - &next_frame)); + complete = framer_.InsertPacket( + payload_.data(), payload_.size(), rtp_header_, &duplicate); + EXPECT_FALSE(complete); + EXPECT_FALSE(framer_.GetEncodedFrame(&frame, &next_frame, &multiple)); // Complete delta - can't skip, as incomplete sequence. ++rtp_header_.frame_id; + rtp_header_.reference_frame_id = rtp_header_.frame_id - 1; rtp_header_.max_packet_id = 0; - framer_.InsertPacket(payload_.data(), payload_.size(), rtp_header_); - EXPECT_FALSE(framer_.GetEncodedVideoFrame(&frame, &rtp_timestamp, - &next_frame)); + complete = framer_.InsertPacket( + payload_.data(), payload_.size(), rtp_header_, &duplicate); + EXPECT_TRUE(complete); + EXPECT_FALSE(framer_.GetEncodedFrame(&frame, &next_frame, &multiple)); +} + +TEST_F(FramerTest, DuplicatePackets) { + transport::EncodedFrame frame; + bool next_frame = false; + bool complete = false; + bool multiple = false; + bool duplicate = false; + + // Start with an incomplete key frame. + rtp_header_.is_key_frame = true; + rtp_header_.max_packet_id = 1; + duplicate = true; + complete = framer_.InsertPacket( + payload_.data(), payload_.size(), rtp_header_, &duplicate); + EXPECT_FALSE(complete); + EXPECT_FALSE(duplicate); + EXPECT_FALSE(framer_.GetEncodedFrame(&frame, &next_frame, &multiple)); + + // Add same packet again in incomplete key frame. + duplicate = false; + complete = framer_.InsertPacket( + payload_.data(), payload_.size(), rtp_header_, &duplicate); + EXPECT_FALSE(complete); + EXPECT_TRUE(duplicate); + EXPECT_FALSE(framer_.GetEncodedFrame(&frame, &next_frame, &multiple)); + + // Complete key frame. + rtp_header_.packet_id = 1; + duplicate = true; + complete = framer_.InsertPacket( + payload_.data(), payload_.size(), rtp_header_, &duplicate); + EXPECT_TRUE(complete); + EXPECT_FALSE(duplicate); + EXPECT_TRUE(framer_.GetEncodedFrame(&frame, &next_frame, &multiple)); + EXPECT_EQ(transport::EncodedFrame::KEY, frame.dependency); + EXPECT_FALSE(multiple); + EXPECT_EQ(0u, frame.referenced_frame_id); + + // Add same packet again in complete key frame. + duplicate = false; + complete = framer_.InsertPacket( + payload_.data(), payload_.size(), rtp_header_, &duplicate); + EXPECT_FALSE(complete); + EXPECT_TRUE(duplicate); + EXPECT_TRUE(framer_.GetEncodedFrame(&frame, &next_frame, &multiple)); + EXPECT_EQ(transport::EncodedFrame::KEY, frame.dependency); + EXPECT_EQ(0u, frame.frame_id); + EXPECT_FALSE(multiple); + EXPECT_EQ(0u, frame.referenced_frame_id); + framer_.ReleaseFrame(frame.frame_id); + + // Incomplete delta frame. + ++rtp_header_.frame_id; + rtp_header_.reference_frame_id = rtp_header_.frame_id - 1; + rtp_header_.packet_id = 0; + rtp_header_.is_key_frame = false; + duplicate = true; + complete = framer_.InsertPacket( + payload_.data(), payload_.size(), rtp_header_, &duplicate); + EXPECT_FALSE(complete); + EXPECT_FALSE(duplicate); + EXPECT_FALSE(framer_.GetEncodedFrame(&frame, &next_frame, &multiple)); + + // Add same packet again in incomplete delta frame. + duplicate = false; + complete = framer_.InsertPacket( + payload_.data(), payload_.size(), rtp_header_, &duplicate); + EXPECT_FALSE(complete); + EXPECT_TRUE(duplicate); + EXPECT_FALSE(framer_.GetEncodedFrame(&frame, &next_frame, &multiple)); + + // Complete delta frame. + rtp_header_.packet_id = 1; + duplicate = true; + complete = framer_.InsertPacket( + payload_.data(), payload_.size(), rtp_header_, &duplicate); + EXPECT_TRUE(complete); + EXPECT_FALSE(duplicate); + EXPECT_TRUE(framer_.GetEncodedFrame(&frame, &next_frame, &multiple)); + EXPECT_EQ(transport::EncodedFrame::DEPENDENT, frame.dependency); + EXPECT_EQ(1u, frame.frame_id); + EXPECT_EQ(0u, frame.referenced_frame_id); + EXPECT_FALSE(multiple); + + // Add same packet again in complete delta frame. + duplicate = false; + complete = framer_.InsertPacket( + payload_.data(), payload_.size(), rtp_header_, &duplicate); + EXPECT_FALSE(complete); + EXPECT_TRUE(duplicate); + EXPECT_TRUE(framer_.GetEncodedFrame(&frame, &next_frame, &multiple)); + EXPECT_EQ(transport::EncodedFrame::DEPENDENT, frame.dependency); + EXPECT_EQ(1u, frame.frame_id); + EXPECT_EQ(0u, frame.referenced_frame_id); + EXPECT_FALSE(multiple); } TEST_F(FramerTest, ContinuousSequence) { - EncodedVideoFrame frame; - uint32 rtp_timestamp; + transport::EncodedFrame frame; bool next_frame = false; + bool complete = false; + bool multiple = false; + bool duplicate = false; - // start with a complete key frame. + // Start with a complete key frame. rtp_header_.is_key_frame = true; - framer_.InsertPacket(payload_.data(), payload_.size(), rtp_header_); - EXPECT_TRUE(framer_.GetEncodedVideoFrame(&frame, &rtp_timestamp, - &next_frame)); + complete = framer_.InsertPacket( + payload_.data(), payload_.size(), rtp_header_, &duplicate); + EXPECT_TRUE(complete); + EXPECT_TRUE(framer_.GetEncodedFrame(&frame, &next_frame, &multiple)); EXPECT_TRUE(next_frame); + EXPECT_FALSE(multiple); + EXPECT_EQ(transport::EncodedFrame::KEY, frame.dependency); EXPECT_EQ(0u, frame.frame_id); - EXPECT_TRUE(frame.key_frame); + EXPECT_EQ(0u, frame.referenced_frame_id); framer_.ReleaseFrame(frame.frame_id); // Complete - not continuous. rtp_header_.frame_id = 2; + rtp_header_.reference_frame_id = rtp_header_.frame_id - 1; rtp_header_.is_key_frame = false; - framer_.InsertPacket(payload_.data(), payload_.size(), rtp_header_); - EXPECT_FALSE(framer_.GetEncodedVideoFrame(&frame, &rtp_timestamp, - &next_frame)); + complete = framer_.InsertPacket( + payload_.data(), payload_.size(), rtp_header_, &duplicate); + EXPECT_TRUE(complete); + EXPECT_FALSE(framer_.GetEncodedFrame(&frame, &next_frame, &multiple)); } TEST_F(FramerTest, Wrap) { // Insert key frame, frame_id = 255 (will jump to that) - EncodedVideoFrame frame; - uint32 rtp_timestamp; + transport::EncodedFrame frame; bool next_frame = false; + bool multiple = true; + bool duplicate = false; // Start with a complete key frame. rtp_header_.is_key_frame = true; - rtp_header_.frame_id = 255u; - framer_.InsertPacket(payload_.data(), payload_.size(), rtp_header_); - EXPECT_TRUE(framer_.GetEncodedVideoFrame(&frame, &rtp_timestamp, - &next_frame)); + rtp_header_.frame_id = 255; + rtp_header_.reference_frame_id = 255; + framer_.InsertPacket( + payload_.data(), payload_.size(), rtp_header_, &duplicate); + EXPECT_TRUE(framer_.GetEncodedFrame(&frame, &next_frame, &multiple)); EXPECT_TRUE(next_frame); + EXPECT_FALSE(multiple); + EXPECT_EQ(transport::EncodedFrame::KEY, frame.dependency); EXPECT_EQ(255u, frame.frame_id); + EXPECT_EQ(255u, frame.referenced_frame_id); framer_.ReleaseFrame(frame.frame_id); // Insert wrapped delta frame - should be continuous. rtp_header_.is_key_frame = false; rtp_header_.frame_id = 256; - framer_.InsertPacket(payload_.data(), payload_.size(), rtp_header_); - EXPECT_TRUE(framer_.GetEncodedVideoFrame(&frame, &rtp_timestamp, - &next_frame)); + framer_.InsertPacket( + payload_.data(), payload_.size(), rtp_header_, &duplicate); + EXPECT_TRUE(framer_.GetEncodedFrame(&frame, &next_frame, &multiple)); EXPECT_TRUE(next_frame); + EXPECT_FALSE(multiple); + EXPECT_EQ(transport::EncodedFrame::DEPENDENT, frame.dependency); EXPECT_EQ(256u, frame.frame_id); + EXPECT_EQ(255u, frame.referenced_frame_id); framer_.ReleaseFrame(frame.frame_id); } TEST_F(FramerTest, Reset) { - EncodedVideoFrame frame; - uint32 rtp_timestamp; + transport::EncodedFrame frame; bool next_frame = false; + bool complete = false; + bool multiple = true; + bool duplicate = false; // Start with a complete key frame. rtp_header_.is_key_frame = true; - framer_.InsertPacket(payload_.data(), payload_.size(), rtp_header_); + complete = framer_.InsertPacket( + payload_.data(), payload_.size(), rtp_header_, &duplicate); + EXPECT_TRUE(complete); framer_.Reset(); - EXPECT_FALSE(framer_.GetEncodedVideoFrame(&frame, &rtp_timestamp, - &next_frame)); + EXPECT_FALSE(framer_.GetEncodedFrame(&frame, &next_frame, &multiple)); } TEST_F(FramerTest, RequireKeyAfterReset) { - EncodedVideoFrame frame; - uint32 rtp_timestamp; + transport::EncodedFrame frame; bool next_frame = false; + bool multiple = false; + bool duplicate = false; + framer_.Reset(); // Start with a complete key frame. rtp_header_.is_key_frame = false; - rtp_header_.frame_id = 0u; - framer_.InsertPacket(payload_.data(), payload_.size(), rtp_header_); - EXPECT_FALSE(framer_.GetEncodedVideoFrame(&frame, &rtp_timestamp, - &next_frame)); + rtp_header_.frame_id = 0; + framer_.InsertPacket( + payload_.data(), payload_.size(), rtp_header_, &duplicate); + EXPECT_FALSE(framer_.GetEncodedFrame(&frame, &next_frame, &multiple)); rtp_header_.frame_id = 1; + rtp_header_.reference_frame_id = 1; rtp_header_.is_key_frame = true; - framer_.InsertPacket(payload_.data(), payload_.size(), rtp_header_); - EXPECT_TRUE(framer_.GetEncodedVideoFrame(&frame, &rtp_timestamp, - &next_frame)); + framer_.InsertPacket( + payload_.data(), payload_.size(), rtp_header_, &duplicate); + EXPECT_TRUE(framer_.GetEncodedFrame(&frame, &next_frame, &multiple)); EXPECT_TRUE(next_frame); + EXPECT_TRUE(multiple); } TEST_F(FramerTest, BasicNonLastReferenceId) { - EncodedVideoFrame frame; - uint32 rtp_timestamp; + transport::EncodedFrame frame; bool next_frame = false; + bool multiple = false; + bool duplicate = false; + rtp_header_.is_key_frame = true; rtp_header_.frame_id = 0; - framer_.InsertPacket(payload_.data(), payload_.size(), rtp_header_); + framer_.InsertPacket( + payload_.data(), payload_.size(), rtp_header_, &duplicate); - EXPECT_TRUE(framer_.GetEncodedVideoFrame(&frame, &rtp_timestamp, - &next_frame)); + EXPECT_TRUE(framer_.GetEncodedFrame(&frame, &next_frame, &multiple)); + EXPECT_FALSE(multiple); framer_.ReleaseFrame(frame.frame_id); rtp_header_.is_key_frame = false; - rtp_header_.is_reference = true; rtp_header_.reference_frame_id = 0; - rtp_header_.frame_id = 5u; - framer_.InsertPacket(payload_.data(), payload_.size(), rtp_header_); + rtp_header_.frame_id = 5; + framer_.InsertPacket( + payload_.data(), payload_.size(), rtp_header_, &duplicate); - EXPECT_TRUE(framer_.GetEncodedVideoFrame(&frame, &rtp_timestamp, - &next_frame)); + EXPECT_TRUE(framer_.GetEncodedFrame(&frame, &next_frame, &multiple)); EXPECT_FALSE(next_frame); + EXPECT_FALSE(multiple); } TEST_F(FramerTest, InOrderReferenceFrameSelection) { // Create pattern: 0, 1, 4, 5. - EncodedVideoFrame frame; - uint32 rtp_timestamp; + transport::EncodedFrame frame; bool next_frame = false; + bool multiple = false; + bool duplicate = false; rtp_header_.is_key_frame = true; rtp_header_.frame_id = 0; - framer_.InsertPacket(payload_.data(), payload_.size(), rtp_header_); + framer_.InsertPacket( + payload_.data(), payload_.size(), rtp_header_, &duplicate); rtp_header_.is_key_frame = false; rtp_header_.frame_id = 1; - framer_.InsertPacket(payload_.data(), payload_.size(), rtp_header_); + framer_.InsertPacket( + payload_.data(), payload_.size(), rtp_header_, &duplicate); // Insert frame #2 partially. rtp_header_.frame_id = 2; rtp_header_.max_packet_id = 1; - framer_.InsertPacket(payload_.data(), payload_.size(), rtp_header_); + framer_.InsertPacket( + payload_.data(), payload_.size(), rtp_header_, &duplicate); rtp_header_.frame_id = 4; rtp_header_.max_packet_id = 0; - rtp_header_.is_reference = true; rtp_header_.reference_frame_id = 0; - framer_.InsertPacket(payload_.data(), payload_.size(), rtp_header_); - EXPECT_TRUE(framer_.GetEncodedVideoFrame(&frame, &rtp_timestamp, - &next_frame)); + framer_.InsertPacket( + payload_.data(), payload_.size(), rtp_header_, &duplicate); + EXPECT_TRUE(framer_.GetEncodedFrame(&frame, &next_frame, &multiple)); + EXPECT_EQ(transport::EncodedFrame::KEY, frame.dependency); EXPECT_EQ(0u, frame.frame_id); + EXPECT_EQ(0u, frame.referenced_frame_id); + EXPECT_FALSE(multiple); framer_.ReleaseFrame(frame.frame_id); - EXPECT_TRUE(framer_.GetEncodedVideoFrame(&frame, &rtp_timestamp, - &next_frame)); + EXPECT_TRUE(framer_.GetEncodedFrame(&frame, &next_frame, &multiple)); EXPECT_TRUE(next_frame); + EXPECT_TRUE(multiple); + EXPECT_EQ(transport::EncodedFrame::DEPENDENT, frame.dependency); EXPECT_EQ(1u, frame.frame_id); + EXPECT_EQ(0u, frame.referenced_frame_id); framer_.ReleaseFrame(frame.frame_id); - EXPECT_TRUE(framer_.GetEncodedVideoFrame(&frame, &rtp_timestamp, - &next_frame)); + EXPECT_TRUE(framer_.GetEncodedFrame(&frame, &next_frame, &multiple)); EXPECT_FALSE(next_frame); + EXPECT_FALSE(multiple); + EXPECT_EQ(transport::EncodedFrame::DEPENDENT, frame.dependency); EXPECT_EQ(4u, frame.frame_id); + EXPECT_EQ(0u, frame.referenced_frame_id); framer_.ReleaseFrame(frame.frame_id); // Insert remaining packet of frame #2 - should no be continuous. rtp_header_.frame_id = 2; rtp_header_.packet_id = 1; - framer_.InsertPacket(payload_.data(), payload_.size(), rtp_header_); - EXPECT_FALSE(framer_.GetEncodedVideoFrame(&frame, &rtp_timestamp, - &next_frame)); - rtp_header_.is_reference = false; + framer_.InsertPacket( + payload_.data(), payload_.size(), rtp_header_, &duplicate); + EXPECT_FALSE(framer_.GetEncodedFrame(&frame, &next_frame, &multiple)); rtp_header_.frame_id = 5; + rtp_header_.reference_frame_id = rtp_header_.frame_id - 1; rtp_header_.packet_id = 0; rtp_header_.max_packet_id = 0; - framer_.InsertPacket(payload_.data(), payload_.size(), rtp_header_); - EXPECT_TRUE(framer_.GetEncodedVideoFrame(&frame, &rtp_timestamp, - &next_frame)); + framer_.InsertPacket( + payload_.data(), payload_.size(), rtp_header_, &duplicate); + EXPECT_TRUE(framer_.GetEncodedFrame(&frame, &next_frame, &multiple)); EXPECT_TRUE(next_frame); + EXPECT_FALSE(multiple); + EXPECT_EQ(transport::EncodedFrame::DEPENDENT, frame.dependency); EXPECT_EQ(5u, frame.frame_id); + EXPECT_EQ(4u, frame.referenced_frame_id); } TEST_F(FramerTest, AudioWrap) { // All audio frames are marked as key frames. - EncodedAudioFrame frame; - uint32 rtp_timestamp; + transport::EncodedFrame frame; bool next_frame = false; + bool multiple = false; + bool duplicate = false; + rtp_header_.is_key_frame = true; rtp_header_.frame_id = 254; + rtp_header_.reference_frame_id = 254; - framer_.InsertPacket(payload_.data(), payload_.size(), rtp_header_); - EXPECT_TRUE(framer_.GetEncodedAudioFrame(&frame, &rtp_timestamp, - &next_frame)); + framer_.InsertPacket( + payload_.data(), payload_.size(), rtp_header_, &duplicate); + EXPECT_TRUE(framer_.GetEncodedFrame(&frame, &next_frame, &multiple)); EXPECT_TRUE(next_frame); + EXPECT_FALSE(multiple); + EXPECT_EQ(transport::EncodedFrame::KEY, frame.dependency); EXPECT_EQ(254u, frame.frame_id); + EXPECT_EQ(254u, frame.referenced_frame_id); framer_.ReleaseFrame(frame.frame_id); rtp_header_.frame_id = 255; - framer_.InsertPacket(payload_.data(), payload_.size(), rtp_header_); + rtp_header_.reference_frame_id = 255; + framer_.InsertPacket( + payload_.data(), payload_.size(), rtp_header_, &duplicate); // Insert wrapped frame - should be continuous. rtp_header_.frame_id = 256; - framer_.InsertPacket(payload_.data(), payload_.size(), rtp_header_); + rtp_header_.reference_frame_id = 256; + framer_.InsertPacket( + payload_.data(), payload_.size(), rtp_header_, &duplicate); - EXPECT_TRUE(framer_.GetEncodedAudioFrame(&frame, &rtp_timestamp, - &next_frame)); + EXPECT_TRUE(framer_.GetEncodedFrame(&frame, &next_frame, &multiple)); EXPECT_TRUE(next_frame); + EXPECT_TRUE(multiple); + EXPECT_EQ(transport::EncodedFrame::KEY, frame.dependency); EXPECT_EQ(255u, frame.frame_id); + EXPECT_EQ(255u, frame.referenced_frame_id); framer_.ReleaseFrame(frame.frame_id); - EXPECT_TRUE(framer_.GetEncodedAudioFrame(&frame, &rtp_timestamp, - &next_frame)); + EXPECT_TRUE(framer_.GetEncodedFrame(&frame, &next_frame, &multiple)); EXPECT_TRUE(next_frame); + EXPECT_FALSE(multiple); + EXPECT_EQ(transport::EncodedFrame::KEY, frame.dependency); EXPECT_EQ(256u, frame.frame_id); + EXPECT_EQ(256u, frame.referenced_frame_id); framer_.ReleaseFrame(frame.frame_id); } TEST_F(FramerTest, AudioWrapWithMissingFrame) { // All audio frames are marked as key frames. - EncodedAudioFrame frame; - uint32 rtp_timestamp; + transport::EncodedFrame frame; bool next_frame = false; + bool multiple = true; + bool duplicate = false; // Insert and get first packet. rtp_header_.is_key_frame = true; rtp_header_.frame_id = 253; - framer_.InsertPacket(payload_.data(), payload_.size(), rtp_header_); - EXPECT_TRUE(framer_.GetEncodedAudioFrame(&frame, &rtp_timestamp, - &next_frame)); + rtp_header_.reference_frame_id = 253; + framer_.InsertPacket( + payload_.data(), payload_.size(), rtp_header_, &duplicate); + EXPECT_TRUE(framer_.GetEncodedFrame(&frame, &next_frame, &multiple)); EXPECT_TRUE(next_frame); + EXPECT_FALSE(multiple); + EXPECT_EQ(transport::EncodedFrame::KEY, frame.dependency); EXPECT_EQ(253u, frame.frame_id); + EXPECT_EQ(253u, frame.referenced_frame_id); framer_.ReleaseFrame(frame.frame_id); // Insert third and fourth packets. rtp_header_.frame_id = 255; - framer_.InsertPacket(payload_.data(), payload_.size(), rtp_header_); + rtp_header_.reference_frame_id = 255; + framer_.InsertPacket( + payload_.data(), payload_.size(), rtp_header_, &duplicate); rtp_header_.frame_id = 256; - framer_.InsertPacket(payload_.data(), payload_.size(), rtp_header_); + rtp_header_.reference_frame_id = 256; + framer_.InsertPacket( + payload_.data(), payload_.size(), rtp_header_, &duplicate); // Get third and fourth packets. - EXPECT_TRUE(framer_.GetEncodedAudioFrame(&frame, &rtp_timestamp, - &next_frame)); + EXPECT_TRUE(framer_.GetEncodedFrame(&frame, &next_frame, &multiple)); EXPECT_FALSE(next_frame); + EXPECT_TRUE(multiple); + EXPECT_EQ(transport::EncodedFrame::KEY, frame.dependency); EXPECT_EQ(255u, frame.frame_id); + EXPECT_EQ(255u, frame.referenced_frame_id); framer_.ReleaseFrame(frame.frame_id); - EXPECT_TRUE(framer_.GetEncodedAudioFrame(&frame, &rtp_timestamp, - &next_frame)); + EXPECT_TRUE(framer_.GetEncodedFrame(&frame, &next_frame, &multiple)); EXPECT_TRUE(next_frame); + EXPECT_FALSE(multiple); + EXPECT_EQ(transport::EncodedFrame::KEY, frame.dependency); EXPECT_EQ(256u, frame.frame_id); + EXPECT_EQ(256u, frame.referenced_frame_id); framer_.ReleaseFrame(frame.frame_id); } diff --git a/chromium/media/cast/logging/encoding_event_subscriber.cc b/chromium/media/cast/logging/encoding_event_subscriber.cc new file mode 100644 index 00000000000..48cc911ba80 --- /dev/null +++ b/chromium/media/cast/logging/encoding_event_subscriber.cc @@ -0,0 +1,286 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/cast/logging/encoding_event_subscriber.h" + +#include <cstring> +#include <utility> + +#include "base/logging.h" +#include "media/cast/logging/proto/proto_utils.h" + +using google::protobuf::RepeatedPtrField; +using media::cast::proto::AggregatedFrameEvent; +using media::cast::proto::AggregatedPacketEvent; +using media::cast::proto::BasePacketEvent; +using media::cast::proto::LogMetadata; + +namespace { + +// A size limit on maps to keep lookups fast. +const size_t kMaxMapSize = 200; + +// The smallest (oredered by RTP timestamp) |kNumMapEntriesToTransfer| entries +// will be moved when the map size reaches |kMaxMapSize|. +// Must be smaller than |kMaxMapSize|. +const size_t kNumMapEntriesToTransfer = 100; + +template <typename ProtoPtr> +bool IsRtpTimestampLessThan(const ProtoPtr& lhs, const ProtoPtr& rhs) { + return lhs->relative_rtp_timestamp() < rhs->relative_rtp_timestamp(); +} + +BasePacketEvent* GetNewBasePacketEvent(AggregatedPacketEvent* event_proto, + int packet_id, int size) { + BasePacketEvent* base = event_proto->add_base_packet_event(); + base->set_packet_id(packet_id); + base->set_size(size); + return base; +} + +} + +namespace media { +namespace cast { + +EncodingEventSubscriber::EncodingEventSubscriber( + EventMediaType event_media_type, + size_t max_frames) + : event_media_type_(event_media_type), + max_frames_(max_frames), + frame_event_storage_index_(0), + packet_event_storage_index_(0), + seen_first_rtp_timestamp_(false), + first_rtp_timestamp_(0u) {} + +EncodingEventSubscriber::~EncodingEventSubscriber() { + DCHECK(thread_checker_.CalledOnValidThread()); +} + +void EncodingEventSubscriber::OnReceiveFrameEvent( + const FrameEvent& frame_event) { + DCHECK(thread_checker_.CalledOnValidThread()); + + if (event_media_type_ != frame_event.media_type) + return; + + RtpTimestamp relative_rtp_timestamp = + GetRelativeRtpTimestamp(frame_event.rtp_timestamp); + FrameEventMap::iterator it = frame_event_map_.find(relative_rtp_timestamp); + linked_ptr<AggregatedFrameEvent> event_proto; + + // Look up existing entry. If not found, create a new entry and add to map. + if (it == frame_event_map_.end()) { + event_proto.reset(new AggregatedFrameEvent); + event_proto->set_relative_rtp_timestamp(relative_rtp_timestamp); + frame_event_map_.insert( + std::make_pair(relative_rtp_timestamp, event_proto)); + } else { + event_proto = it->second; + if (event_proto->event_type_size() >= kMaxEventsPerProto) { + DVLOG(2) << "Too many events in frame " << frame_event.rtp_timestamp + << ". Using new frame event proto."; + AddFrameEventToStorage(event_proto); + event_proto.reset(new AggregatedFrameEvent); + event_proto->set_relative_rtp_timestamp(relative_rtp_timestamp); + it->second = event_proto; + } + } + + event_proto->add_event_type(ToProtoEventType(frame_event.type)); + event_proto->add_event_timestamp_ms( + (frame_event.timestamp - base::TimeTicks()).InMilliseconds()); + + if (frame_event.type == FRAME_ENCODED) { + event_proto->set_encoded_frame_size(frame_event.size); + if (frame_event.media_type == VIDEO_EVENT) { + event_proto->set_encoded_frame_size(frame_event.size); + event_proto->set_key_frame(frame_event.key_frame); + event_proto->set_target_bitrate(frame_event.target_bitrate); + } + } else if (frame_event.type == FRAME_PLAYOUT) { + event_proto->set_delay_millis(frame_event.delay_delta.InMilliseconds()); + } + + if (frame_event_map_.size() > kMaxMapSize) + TransferFrameEvents(kNumMapEntriesToTransfer); + + DCHECK(frame_event_map_.size() <= kMaxMapSize); + DCHECK(frame_event_storage_.size() <= max_frames_); +} + +void EncodingEventSubscriber::OnReceivePacketEvent( + const PacketEvent& packet_event) { + DCHECK(thread_checker_.CalledOnValidThread()); + + if (event_media_type_ != packet_event.media_type) + return; + + RtpTimestamp relative_rtp_timestamp = + GetRelativeRtpTimestamp(packet_event.rtp_timestamp); + PacketEventMap::iterator it = + packet_event_map_.find(relative_rtp_timestamp); + linked_ptr<AggregatedPacketEvent> event_proto; + BasePacketEvent* base_packet_event_proto = NULL; + + // Look up existing entry. If not found, create a new entry and add to map. + if (it == packet_event_map_.end()) { + event_proto.reset(new AggregatedPacketEvent); + event_proto->set_relative_rtp_timestamp(relative_rtp_timestamp); + packet_event_map_.insert( + std::make_pair(relative_rtp_timestamp, event_proto)); + base_packet_event_proto = GetNewBasePacketEvent( + event_proto.get(), packet_event.packet_id, packet_event.size); + } else { + // Found existing entry, now look up existing BasePacketEvent using packet + // ID. If not found, create a new entry and add to proto. + event_proto = it->second; + RepeatedPtrField<BasePacketEvent>* field = + event_proto->mutable_base_packet_event(); + for (RepeatedPtrField<BasePacketEvent>::pointer_iterator base_it = + field->pointer_begin(); + base_it != field->pointer_end(); + ++base_it) { + if ((*base_it)->packet_id() == packet_event.packet_id) { + base_packet_event_proto = *base_it; + break; + } + } + if (!base_packet_event_proto) { + if (event_proto->base_packet_event_size() >= kMaxPacketsPerFrame) { + DVLOG(3) << "Too many packets in AggregatedPacketEvent " + << packet_event.rtp_timestamp << ". " + << "Using new packet event proto."; + AddPacketEventToStorage(event_proto); + event_proto.reset(new AggregatedPacketEvent); + event_proto->set_relative_rtp_timestamp(relative_rtp_timestamp); + it->second = event_proto; + } + + base_packet_event_proto = GetNewBasePacketEvent( + event_proto.get(), packet_event.packet_id, packet_event.size); + } else if (base_packet_event_proto->event_type_size() >= + kMaxEventsPerProto) { + DVLOG(3) << "Too many events in packet " + << packet_event.rtp_timestamp << ", " + << packet_event.packet_id << ". Using new packet event proto."; + AddPacketEventToStorage(event_proto); + event_proto.reset(new AggregatedPacketEvent); + event_proto->set_relative_rtp_timestamp(relative_rtp_timestamp); + it->second = event_proto; + base_packet_event_proto = GetNewBasePacketEvent( + event_proto.get(), packet_event.packet_id, packet_event.size); + } + } + + base_packet_event_proto->add_event_type( + ToProtoEventType(packet_event.type)); + base_packet_event_proto->add_event_timestamp_ms( + (packet_event.timestamp - base::TimeTicks()).InMilliseconds()); + + // |base_packet_event_proto| could have been created with a receiver event + // which does not have the packet size and we would need to overwrite it when + // we see a sender event, which does have the packet size. + if (packet_event.size > 0) { + base_packet_event_proto->set_size(packet_event.size); + } + + if (packet_event_map_.size() > kMaxMapSize) + TransferPacketEvents(kNumMapEntriesToTransfer); + + DCHECK(packet_event_map_.size() <= kMaxMapSize); + DCHECK(packet_event_storage_.size() <= max_frames_); +} + +void EncodingEventSubscriber::GetEventsAndReset(LogMetadata* metadata, + FrameEventList* frame_events, PacketEventList* packet_events) { + DCHECK(thread_checker_.CalledOnValidThread()); + + // Flush all events. + TransferFrameEvents(frame_event_map_.size()); + TransferPacketEvents(packet_event_map_.size()); + std::sort(frame_event_storage_.begin(), frame_event_storage_.end(), + &IsRtpTimestampLessThan<linked_ptr<AggregatedFrameEvent> >); + std::sort(packet_event_storage_.begin(), packet_event_storage_.end(), + &IsRtpTimestampLessThan<linked_ptr<AggregatedPacketEvent> >); + + metadata->set_is_audio(event_media_type_ == AUDIO_EVENT); + metadata->set_first_rtp_timestamp(first_rtp_timestamp_); + metadata->set_num_frame_events(frame_event_storage_.size()); + metadata->set_num_packet_events(packet_event_storage_.size()); + metadata->set_reference_timestamp_ms_at_unix_epoch( + (base::TimeTicks::UnixEpoch() - base::TimeTicks()).InMilliseconds()); + frame_events->swap(frame_event_storage_); + packet_events->swap(packet_event_storage_); + Reset(); +} + +void EncodingEventSubscriber::TransferFrameEvents(size_t max_num_entries) { + DCHECK(frame_event_map_.size() >= max_num_entries); + + FrameEventMap::iterator it = frame_event_map_.begin(); + for (size_t i = 0; + i < max_num_entries && it != frame_event_map_.end(); + i++, ++it) { + AddFrameEventToStorage(it->second); + } + + frame_event_map_.erase(frame_event_map_.begin(), it); +} + +void EncodingEventSubscriber::TransferPacketEvents(size_t max_num_entries) { + PacketEventMap::iterator it = packet_event_map_.begin(); + for (size_t i = 0; + i < max_num_entries && it != packet_event_map_.end(); + i++, ++it) { + AddPacketEventToStorage(it->second); + } + + packet_event_map_.erase(packet_event_map_.begin(), it); +} + +void EncodingEventSubscriber::AddFrameEventToStorage( + const linked_ptr<AggregatedFrameEvent>& frame_event_proto) { + if (frame_event_storage_.size() >= max_frames_) { + frame_event_storage_[frame_event_storage_index_] = frame_event_proto; + } else { + frame_event_storage_.push_back(frame_event_proto); + } + + frame_event_storage_index_ = (frame_event_storage_index_ + 1) % max_frames_; +} + +void EncodingEventSubscriber::AddPacketEventToStorage( + const linked_ptr<AggregatedPacketEvent>& packet_event_proto) { + if (packet_event_storage_.size() >= max_frames_) + packet_event_storage_[packet_event_storage_index_] = packet_event_proto; + else + packet_event_storage_.push_back(packet_event_proto); + + packet_event_storage_index_ = (packet_event_storage_index_ + 1) % max_frames_; +} + +RtpTimestamp EncodingEventSubscriber::GetRelativeRtpTimestamp( + RtpTimestamp rtp_timestamp) { + if (!seen_first_rtp_timestamp_) { + seen_first_rtp_timestamp_ = true; + first_rtp_timestamp_ = rtp_timestamp; + } + + return rtp_timestamp - first_rtp_timestamp_; +} + +void EncodingEventSubscriber::Reset() { + frame_event_map_.clear(); + frame_event_storage_.clear(); + frame_event_storage_index_ = 0; + packet_event_map_.clear(); + packet_event_storage_.clear(); + packet_event_storage_index_ = 0; + seen_first_rtp_timestamp_ = false; + first_rtp_timestamp_ = 0u; +} + +} // namespace cast +} // namespace media diff --git a/chromium/media/cast/logging/encoding_event_subscriber.h b/chromium/media/cast/logging/encoding_event_subscriber.h new file mode 100644 index 00000000000..ca2cccb5f74 --- /dev/null +++ b/chromium/media/cast/logging/encoding_event_subscriber.h @@ -0,0 +1,122 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_CAST_LOGGING_ENCODING_EVENT_SUBSCRIBER_H_ +#define MEDIA_CAST_LOGGING_ENCODING_EVENT_SUBSCRIBER_H_ + +#include <map> + +#include "base/memory/linked_ptr.h" +#include "base/threading/thread_checker.h" +#include "media/cast/logging/logging_defines.h" +#include "media/cast/logging/proto/raw_events.pb.h" +#include "media/cast/logging/raw_event_subscriber.h" + +namespace media { +namespace cast { + +// Number of packets per frame recorded by the subscriber. +// Once the max number of packets has been reached, a new aggregated proto +// will be created. +static const int kMaxPacketsPerFrame = 64; +// Number of events per proto recorded by the subscriber. +// Once the max number of events has been reached, a new aggregated proto +// will be created. +static const int kMaxEventsPerProto = 16; + +typedef std::vector<linked_ptr<media::cast::proto::AggregatedFrameEvent> > + FrameEventList; +typedef std::vector<linked_ptr<media::cast::proto::AggregatedPacketEvent> > + PacketEventList; + +// A RawEventSubscriber implementation that subscribes to events, +// encodes them in protocol buffer format, and aggregates them into a more +// compact structure. Aggregation is per-frame, and uses a map with RTP +// timestamp as key. Periodically, old entries in the map will be transferred +// to a storage vector. This helps keep the size of the map small and +// lookup times fast. The storage itself is a circular buffer that will +// overwrite old entries once it has reached the size configured by user. +class EncodingEventSubscriber : public RawEventSubscriber { + public: + // |event_media_type|: The subscriber will only process events that + // corresponds to this type. + // |max_frames|: How many events to keep in the frame / packet storage. + // This helps keep memory usage bounded. + // Every time one of |OnReceive[Frame,Packet]Event()| is + // called, it will check if the respective map size has exceeded |max_frames|. + // If so, it will remove the oldest aggregated entry (ordered by RTP + // timestamp). + EncodingEventSubscriber(EventMediaType event_media_type, size_t max_frames); + + virtual ~EncodingEventSubscriber(); + + // RawReventSubscriber implementations. + virtual void OnReceiveFrameEvent(const FrameEvent& frame_event) OVERRIDE; + virtual void OnReceivePacketEvent(const PacketEvent& packet_event) OVERRIDE; + + // Assigns frame events and packet events received so far to |frame_events| + // and |packet_events| and resets the internal state. + // In addition, assign metadata associated with these events to |metadata|. + // The protos in |frame_events| and |packets_events| are sorted in + // ascending RTP timestamp order. + void GetEventsAndReset(media::cast::proto::LogMetadata* metadata, + FrameEventList* frame_events, + PacketEventList* packet_events); + + private: + typedef std::map<RtpTimestamp, + linked_ptr<media::cast::proto::AggregatedFrameEvent> > + FrameEventMap; + typedef std::map<RtpTimestamp, + linked_ptr<media::cast::proto::AggregatedPacketEvent> > + PacketEventMap; + + // Transfer up to |max_num_entries| smallest entries from |frame_event_map_| + // to |frame_event_storage_|. This helps keep size of |frame_event_map_| small + // and lookup speed fast. + void TransferFrameEvents(size_t max_num_entries); + // See above. + void TransferPacketEvents(size_t max_num_entries); + + void AddFrameEventToStorage( + const linked_ptr<media::cast::proto::AggregatedFrameEvent>& + frame_event_proto); + void AddPacketEventToStorage( + const linked_ptr<media::cast::proto::AggregatedPacketEvent>& + packet_event_proto); + + // Returns the difference between |rtp_timestamp| and |first_rtp_timestamp_|. + // Sets |first_rtp_timestamp_| if it is not already set. + RtpTimestamp GetRelativeRtpTimestamp(RtpTimestamp rtp_timestamp); + + // Clears the maps and first RTP timestamp seen. + void Reset(); + + const EventMediaType event_media_type_; + const size_t max_frames_; + + FrameEventMap frame_event_map_; + FrameEventList frame_event_storage_; + int frame_event_storage_index_; + + PacketEventMap packet_event_map_; + PacketEventList packet_event_storage_; + int packet_event_storage_index_; + + // All functions must be called on the main thread. + base::ThreadChecker thread_checker_; + + // Set to true on first event encountered after a |Reset()|. + bool seen_first_rtp_timestamp_; + + // Set to RTP timestamp of first event encountered after a |Reset()|. + RtpTimestamp first_rtp_timestamp_; + + DISALLOW_COPY_AND_ASSIGN(EncodingEventSubscriber); +}; + +} // namespace cast +} // namespace media + +#endif // MEDIA_CAST_LOGGING_ENCODING_EVENT_SUBSCRIBER_H_ diff --git a/chromium/media/cast/logging/encoding_event_subscriber_unittest.cc b/chromium/media/cast/logging/encoding_event_subscriber_unittest.cc new file mode 100644 index 00000000000..3d77a621b78 --- /dev/null +++ b/chromium/media/cast/logging/encoding_event_subscriber_unittest.cc @@ -0,0 +1,668 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/test/simple_test_tick_clock.h" +#include "base/time/tick_clock.h" +#include "media/cast/cast_environment.h" +#include "media/cast/logging/encoding_event_subscriber.h" +#include "media/cast/logging/logging_defines.h" +#include "media/cast/test/fake_single_thread_task_runner.h" +#include "testing/gtest/include/gtest/gtest.h" + +using media::cast::proto::AggregatedFrameEvent; +using media::cast::proto::AggregatedPacketEvent; +using media::cast::proto::BasePacketEvent; +using media::cast::proto::LogMetadata; + +namespace { + +int64 InMilliseconds(base::TimeTicks event_time) { + return (event_time - base::TimeTicks()).InMilliseconds(); +} + +} + +namespace media { +namespace cast { + +class EncodingEventSubscriberTest : public ::testing::Test { + protected: + EncodingEventSubscriberTest() + : testing_clock_(new base::SimpleTestTickClock()), + task_runner_(new test::FakeSingleThreadTaskRunner(testing_clock_)), + cast_environment_(new CastEnvironment( + scoped_ptr<base::TickClock>(testing_clock_).Pass(), + task_runner_, + task_runner_, + task_runner_)), + first_rtp_timestamp_(0) {} + + void Init(EventMediaType event_media_type) { + DCHECK(!event_subscriber_); + event_subscriber_.reset(new EncodingEventSubscriber(event_media_type, 10)); + cast_environment_->Logging()->AddRawEventSubscriber( + event_subscriber_.get()); + } + + virtual ~EncodingEventSubscriberTest() { + if (event_subscriber_) { + cast_environment_->Logging()->RemoveRawEventSubscriber( + event_subscriber_.get()); + } + } + + void GetEventsAndReset() { + event_subscriber_->GetEventsAndReset( + &metadata_, &frame_events_, &packet_events_); + first_rtp_timestamp_ = metadata_.first_rtp_timestamp(); + } + + base::SimpleTestTickClock* testing_clock_; // Owned by CastEnvironment. + scoped_refptr<test::FakeSingleThreadTaskRunner> task_runner_; + scoped_refptr<CastEnvironment> cast_environment_; + scoped_ptr<EncodingEventSubscriber> event_subscriber_; + FrameEventList frame_events_; + PacketEventList packet_events_; + LogMetadata metadata_; + RtpTimestamp first_rtp_timestamp_; +}; + +TEST_F(EncodingEventSubscriberTest, FrameEventTruncating) { + Init(VIDEO_EVENT); + + base::TimeTicks now(testing_clock_->NowTicks()); + + // Entry with RTP timestamp 0 should get dropped. + for (int i = 0; i < 11; i++) { + cast_environment_->Logging()->InsertFrameEvent(now, + FRAME_CAPTURE_BEGIN, + VIDEO_EVENT, + i * 100, + /*frame_id*/ 0); + cast_environment_->Logging()->InsertFrameEvent(now, + FRAME_DECODED, + VIDEO_EVENT, + i * 100, + /*frame_id*/ 0); + } + + GetEventsAndReset(); + + ASSERT_EQ(10u, frame_events_.size()); + EXPECT_EQ(100u, frame_events_.front()->relative_rtp_timestamp()); + EXPECT_EQ(1000u, frame_events_.back()->relative_rtp_timestamp()); +} + +TEST_F(EncodingEventSubscriberTest, PacketEventTruncating) { + Init(AUDIO_EVENT); + + base::TimeTicks now(testing_clock_->NowTicks()); + + // Entry with RTP timestamp 0 should get dropped. + for (int i = 0; i < 11; i++) { + cast_environment_->Logging()->InsertPacketEvent(now, + PACKET_RECEIVED, + AUDIO_EVENT, + /*rtp_timestamp*/ i * 100, + /*frame_id*/ 0, + /*packet_id*/ i, + /*max_packet_id*/ 10, + /*size*/ 123); + } + + GetEventsAndReset(); + + ASSERT_EQ(10u, packet_events_.size()); + EXPECT_EQ(100u, packet_events_.front()->relative_rtp_timestamp()); + EXPECT_EQ(1000u, packet_events_.back()->relative_rtp_timestamp()); +} + +TEST_F(EncodingEventSubscriberTest, EventFiltering) { + Init(VIDEO_EVENT); + + base::TimeTicks now(testing_clock_->NowTicks()); + RtpTimestamp rtp_timestamp = 100; + cast_environment_->Logging()->InsertFrameEvent(now, + FRAME_DECODED, + VIDEO_EVENT, + rtp_timestamp, + /*frame_id*/ 0); + + // This is an AUDIO_EVENT and shouldn't be processed by the subscriber. + cast_environment_->Logging()->InsertFrameEvent(now, + FRAME_DECODED, + AUDIO_EVENT, + rtp_timestamp, + /*frame_id*/ 0); + + GetEventsAndReset(); + + ASSERT_EQ(1u, frame_events_.size()); + FrameEventList::iterator it = frame_events_.begin(); + + linked_ptr<AggregatedFrameEvent> frame_event = *it; + + ASSERT_EQ(1, frame_event->event_type_size()); + EXPECT_EQ(media::cast::proto::FRAME_DECODED, + frame_event->event_type(0)); + + GetEventsAndReset(); + + EXPECT_TRUE(packet_events_.empty()); +} + +TEST_F(EncodingEventSubscriberTest, FrameEvent) { + Init(VIDEO_EVENT); + base::TimeTicks now(testing_clock_->NowTicks()); + RtpTimestamp rtp_timestamp = 100; + cast_environment_->Logging()->InsertFrameEvent(now, FRAME_DECODED, + VIDEO_EVENT, + rtp_timestamp, + /*frame_id*/ 0); + + GetEventsAndReset(); + + ASSERT_EQ(1u, frame_events_.size()); + + RtpTimestamp relative_rtp_timestamp = rtp_timestamp - first_rtp_timestamp_; + FrameEventList::iterator it = frame_events_.begin(); + + linked_ptr<AggregatedFrameEvent> event = *it; + + EXPECT_EQ(relative_rtp_timestamp, event->relative_rtp_timestamp()); + + ASSERT_EQ(1, event->event_type_size()); + EXPECT_EQ(media::cast::proto::FRAME_DECODED, event->event_type(0)); + ASSERT_EQ(1, event->event_timestamp_ms_size()); + EXPECT_EQ(InMilliseconds(now), event->event_timestamp_ms(0)); + + EXPECT_EQ(0, event->encoded_frame_size()); + EXPECT_EQ(0, event->delay_millis()); + + GetEventsAndReset(); + EXPECT_TRUE(frame_events_.empty()); +} + +TEST_F(EncodingEventSubscriberTest, FrameEventDelay) { + Init(AUDIO_EVENT); + base::TimeTicks now(testing_clock_->NowTicks()); + RtpTimestamp rtp_timestamp = 100; + int delay_ms = 100; + cast_environment_->Logging()->InsertFrameEventWithDelay( + now, FRAME_PLAYOUT, AUDIO_EVENT, rtp_timestamp, + /*frame_id*/ 0, base::TimeDelta::FromMilliseconds(delay_ms)); + + GetEventsAndReset(); + + ASSERT_EQ(1u, frame_events_.size()); + + RtpTimestamp relative_rtp_timestamp = rtp_timestamp - first_rtp_timestamp_; + FrameEventList::iterator it = frame_events_.begin(); + + linked_ptr<AggregatedFrameEvent> event = *it; + + EXPECT_EQ(relative_rtp_timestamp, event->relative_rtp_timestamp()); + + ASSERT_EQ(1, event->event_type_size()); + EXPECT_EQ(media::cast::proto::FRAME_PLAYOUT, event->event_type(0)); + ASSERT_EQ(1, event->event_timestamp_ms_size()); + EXPECT_EQ(InMilliseconds(now), event->event_timestamp_ms(0)); + + EXPECT_EQ(0, event->encoded_frame_size()); + EXPECT_EQ(100, event->delay_millis()); + EXPECT_FALSE(event->has_key_frame()); +} + +TEST_F(EncodingEventSubscriberTest, FrameEventSize) { + Init(VIDEO_EVENT); + base::TimeTicks now(testing_clock_->NowTicks()); + RtpTimestamp rtp_timestamp = 100; + int size = 123; + bool key_frame = true; + int target_bitrate = 1024; + cast_environment_->Logging()->InsertEncodedFrameEvent( + now, FRAME_ENCODED, VIDEO_EVENT, rtp_timestamp, + /*frame_id*/ 0, size, key_frame, target_bitrate); + + GetEventsAndReset(); + + ASSERT_EQ(1u, frame_events_.size()); + + RtpTimestamp relative_rtp_timestamp = rtp_timestamp - first_rtp_timestamp_; + FrameEventList::iterator it = frame_events_.begin(); + + linked_ptr<AggregatedFrameEvent> event = *it; + + EXPECT_EQ(relative_rtp_timestamp, event->relative_rtp_timestamp()); + + ASSERT_EQ(1, event->event_type_size()); + EXPECT_EQ(media::cast::proto::FRAME_ENCODED, event->event_type(0)); + ASSERT_EQ(1, event->event_timestamp_ms_size()); + EXPECT_EQ(InMilliseconds(now), event->event_timestamp_ms(0)); + + EXPECT_EQ(size, event->encoded_frame_size()); + EXPECT_EQ(0, event->delay_millis()); + EXPECT_TRUE(event->has_key_frame()); + EXPECT_EQ(key_frame, event->key_frame()); + EXPECT_EQ(target_bitrate, event->target_bitrate()); +} + +TEST_F(EncodingEventSubscriberTest, MultipleFrameEvents) { + Init(AUDIO_EVENT); + RtpTimestamp rtp_timestamp1 = 100; + RtpTimestamp rtp_timestamp2 = 200; + base::TimeTicks now1(testing_clock_->NowTicks()); + cast_environment_->Logging()->InsertFrameEventWithDelay( + now1, FRAME_PLAYOUT, AUDIO_EVENT, rtp_timestamp1, + /*frame_id*/ 0, /*delay*/ base::TimeDelta::FromMilliseconds(100)); + + testing_clock_->Advance(base::TimeDelta::FromMilliseconds(20)); + base::TimeTicks now2(testing_clock_->NowTicks()); + cast_environment_->Logging()->InsertEncodedFrameEvent( + now2, FRAME_ENCODED, AUDIO_EVENT, rtp_timestamp2, + /*frame_id*/ 0, /*size*/ 123, /* key_frame - unused */ false, + /*target_bitrate - unused*/ 0); + + testing_clock_->Advance(base::TimeDelta::FromMilliseconds(20)); + base::TimeTicks now3(testing_clock_->NowTicks()); + cast_environment_->Logging()->InsertFrameEvent( + now3, FRAME_DECODED, AUDIO_EVENT, rtp_timestamp1, /*frame_id*/ 0); + + GetEventsAndReset(); + + ASSERT_EQ(2u, frame_events_.size()); + + RtpTimestamp relative_rtp_timestamp = rtp_timestamp1 - first_rtp_timestamp_; + FrameEventList::iterator it = frame_events_.begin(); + + linked_ptr<AggregatedFrameEvent> event = *it; + + EXPECT_EQ(relative_rtp_timestamp, event->relative_rtp_timestamp()); + + ASSERT_EQ(2, event->event_type_size()); + EXPECT_EQ(media::cast::proto::FRAME_PLAYOUT, event->event_type(0)); + EXPECT_EQ(media::cast::proto::FRAME_DECODED, event->event_type(1)); + + ASSERT_EQ(2, event->event_timestamp_ms_size()); + EXPECT_EQ(InMilliseconds(now1), event->event_timestamp_ms(0)); + EXPECT_EQ(InMilliseconds(now3), event->event_timestamp_ms(1)); + + EXPECT_FALSE(event->has_key_frame()); + + relative_rtp_timestamp = rtp_timestamp2 - first_rtp_timestamp_; + ++it; + + event = *it; + + EXPECT_EQ(relative_rtp_timestamp, event->relative_rtp_timestamp()); + + ASSERT_EQ(1, event->event_type_size()); + EXPECT_EQ(media::cast::proto::FRAME_ENCODED, event->event_type(0)); + + ASSERT_EQ(1, event->event_timestamp_ms_size()); + EXPECT_EQ(InMilliseconds(now2), event->event_timestamp_ms(0)); + + EXPECT_FALSE(event->has_key_frame()); +} + +TEST_F(EncodingEventSubscriberTest, PacketEvent) { + Init(AUDIO_EVENT); + base::TimeTicks now(testing_clock_->NowTicks()); + RtpTimestamp rtp_timestamp = 100; + int packet_id = 2; + int size = 100; + cast_environment_->Logging()->InsertPacketEvent( + now, PACKET_RECEIVED, AUDIO_EVENT, + rtp_timestamp, /*frame_id*/ 0, packet_id, + /*max_packet_id*/ 10, size); + + GetEventsAndReset(); + + ASSERT_EQ(1u, packet_events_.size()); + + RtpTimestamp relative_rtp_timestamp = rtp_timestamp - first_rtp_timestamp_; + PacketEventList::iterator it = packet_events_.begin(); + + linked_ptr<AggregatedPacketEvent> event = *it; + + EXPECT_EQ(relative_rtp_timestamp, event->relative_rtp_timestamp()); + + ASSERT_EQ(1, event->base_packet_event_size()); + const BasePacketEvent& base_event = event->base_packet_event(0); + EXPECT_EQ(packet_id, base_event.packet_id()); + ASSERT_EQ(1, base_event.event_type_size()); + EXPECT_EQ(media::cast::proto::PACKET_RECEIVED, + base_event.event_type(0)); + ASSERT_EQ(1, base_event.event_timestamp_ms_size()); + EXPECT_EQ(InMilliseconds(now), base_event.event_timestamp_ms(0)); + EXPECT_EQ(size, base_event.size()); + + GetEventsAndReset(); + EXPECT_TRUE(packet_events_.empty()); +} + +TEST_F(EncodingEventSubscriberTest, MultiplePacketEventsForPacket) { + Init(VIDEO_EVENT); + base::TimeTicks now1(testing_clock_->NowTicks()); + RtpTimestamp rtp_timestamp = 100; + int packet_id = 2; + int size = 100; + cast_environment_->Logging()->InsertPacketEvent(now1, + PACKET_SENT_TO_NETWORK, + VIDEO_EVENT, + rtp_timestamp, + /*frame_id*/ 0, + packet_id, + /*max_packet_id*/ 10, + size); + + testing_clock_->Advance(base::TimeDelta::FromMilliseconds(20)); + base::TimeTicks now2(testing_clock_->NowTicks()); + cast_environment_->Logging()->InsertPacketEvent(now2, + PACKET_RETRANSMITTED, + VIDEO_EVENT, + rtp_timestamp, + /*frame_id*/ 0, + packet_id, + /*max_packet_id*/ 10, + size); + + GetEventsAndReset(); + + ASSERT_EQ(1u, packet_events_.size()); + + RtpTimestamp relative_rtp_timestamp = rtp_timestamp - first_rtp_timestamp_; + PacketEventList::iterator it = packet_events_.begin(); + + linked_ptr<AggregatedPacketEvent> event = *it; + + EXPECT_EQ(relative_rtp_timestamp, event->relative_rtp_timestamp()); + + ASSERT_EQ(1, event->base_packet_event_size()); + const BasePacketEvent& base_event = event->base_packet_event(0); + EXPECT_EQ(packet_id, base_event.packet_id()); + ASSERT_EQ(2, base_event.event_type_size()); + EXPECT_EQ(media::cast::proto::PACKET_SENT_TO_NETWORK, + base_event.event_type(0)); + EXPECT_EQ(media::cast::proto::PACKET_RETRANSMITTED, + base_event.event_type(1)); + ASSERT_EQ(2, base_event.event_timestamp_ms_size()); + EXPECT_EQ(InMilliseconds(now1), base_event.event_timestamp_ms(0)); + EXPECT_EQ(InMilliseconds(now2), base_event.event_timestamp_ms(1)); +} + +TEST_F(EncodingEventSubscriberTest, MultiplePacketEventsForFrame) { + Init(VIDEO_EVENT); + base::TimeTicks now1(testing_clock_->NowTicks()); + RtpTimestamp rtp_timestamp = 100; + int packet_id_1 = 2; + int packet_id_2 = 3; + int size = 100; + cast_environment_->Logging()->InsertPacketEvent(now1, + PACKET_SENT_TO_NETWORK, + VIDEO_EVENT, + rtp_timestamp, + /*frame_id*/ 0, + packet_id_1, + /*max_packet_id*/ 10, + size); + + testing_clock_->Advance(base::TimeDelta::FromMilliseconds(20)); + base::TimeTicks now2(testing_clock_->NowTicks()); + cast_environment_->Logging()->InsertPacketEvent(now2, + PACKET_RETRANSMITTED, + VIDEO_EVENT, + rtp_timestamp, + /*frame_id*/ 0, + packet_id_2, + /*max_packet_id*/ 10, + size); + + GetEventsAndReset(); + + ASSERT_EQ(1u, packet_events_.size()); + + RtpTimestamp relative_rtp_timestamp = rtp_timestamp - first_rtp_timestamp_; + PacketEventList::iterator it = packet_events_.begin(); + + linked_ptr<AggregatedPacketEvent> event = *it; + + EXPECT_EQ(relative_rtp_timestamp, event->relative_rtp_timestamp()); + + ASSERT_EQ(2, event->base_packet_event_size()); + const BasePacketEvent& base_event = event->base_packet_event(0); + EXPECT_EQ(packet_id_1, base_event.packet_id()); + ASSERT_EQ(1, base_event.event_type_size()); + EXPECT_EQ(media::cast::proto::PACKET_SENT_TO_NETWORK, + base_event.event_type(0)); + ASSERT_EQ(1, base_event.event_timestamp_ms_size()); + EXPECT_EQ(InMilliseconds(now1), base_event.event_timestamp_ms(0)); + + const BasePacketEvent& base_event_2 = event->base_packet_event(1); + EXPECT_EQ(packet_id_2, base_event_2.packet_id()); + ASSERT_EQ(1, base_event_2.event_type_size()); + EXPECT_EQ(media::cast::proto::PACKET_RETRANSMITTED, + base_event_2.event_type(0)); + ASSERT_EQ(1, base_event_2.event_timestamp_ms_size()); + EXPECT_EQ(InMilliseconds(now2), base_event_2.event_timestamp_ms(0)); +} + +TEST_F(EncodingEventSubscriberTest, MultiplePacketEvents) { + Init(VIDEO_EVENT); + base::TimeTicks now1(testing_clock_->NowTicks()); + RtpTimestamp rtp_timestamp_1 = 100; + RtpTimestamp rtp_timestamp_2 = 200; + int packet_id_1 = 2; + int packet_id_2 = 3; + int size = 100; + cast_environment_->Logging()->InsertPacketEvent(now1, + PACKET_SENT_TO_NETWORK, + VIDEO_EVENT, + rtp_timestamp_1, + /*frame_id*/ 0, + packet_id_1, + /*max_packet_id*/ 10, + size); + + testing_clock_->Advance(base::TimeDelta::FromMilliseconds(20)); + base::TimeTicks now2(testing_clock_->NowTicks()); + cast_environment_->Logging()->InsertPacketEvent(now2, + PACKET_RETRANSMITTED, + VIDEO_EVENT, + rtp_timestamp_2, + /*frame_id*/ 0, + packet_id_2, + /*max_packet_id*/ 10, + size); + + GetEventsAndReset(); + + ASSERT_EQ(2u, packet_events_.size()); + + RtpTimestamp relative_rtp_timestamp = rtp_timestamp_1 - first_rtp_timestamp_; + PacketEventList::iterator it = packet_events_.begin(); + + linked_ptr<AggregatedPacketEvent> event = *it; + + EXPECT_EQ(relative_rtp_timestamp, event->relative_rtp_timestamp()); + + ASSERT_EQ(1, event->base_packet_event_size()); + const BasePacketEvent& base_event = event->base_packet_event(0); + EXPECT_EQ(packet_id_1, base_event.packet_id()); + ASSERT_EQ(1, base_event.event_type_size()); + EXPECT_EQ(media::cast::proto::PACKET_SENT_TO_NETWORK, + base_event.event_type(0)); + ASSERT_EQ(1, base_event.event_timestamp_ms_size()); + EXPECT_EQ(InMilliseconds(now1), base_event.event_timestamp_ms(0)); + + relative_rtp_timestamp = rtp_timestamp_2 - first_rtp_timestamp_; + ++it; + ASSERT_TRUE(it != packet_events_.end()); + + event = *it; + EXPECT_EQ(relative_rtp_timestamp, event->relative_rtp_timestamp()); + + ASSERT_EQ(1, event->base_packet_event_size()); + const BasePacketEvent& base_event_2 = event->base_packet_event(0); + EXPECT_EQ(packet_id_2, base_event_2.packet_id()); + ASSERT_EQ(1, base_event_2.event_type_size()); + EXPECT_EQ(media::cast::proto::PACKET_RETRANSMITTED, + base_event_2.event_type(0)); + ASSERT_EQ(1, base_event_2.event_timestamp_ms_size()); + EXPECT_EQ(InMilliseconds(now2), base_event_2.event_timestamp_ms(0)); +} + +TEST_F(EncodingEventSubscriberTest, FirstRtpTimestamp) { + Init(VIDEO_EVENT); + RtpTimestamp rtp_timestamp = 12345; + base::TimeTicks now(testing_clock_->NowTicks()); + + cast_environment_->Logging()->InsertFrameEvent(now, + FRAME_CAPTURE_BEGIN, + VIDEO_EVENT, + rtp_timestamp, + /*frame_id*/ 0); + + cast_environment_->Logging()->InsertFrameEvent(now, + FRAME_CAPTURE_END, + VIDEO_EVENT, + rtp_timestamp + 30, + /*frame_id*/ 1); + + GetEventsAndReset(); + + EXPECT_EQ(rtp_timestamp, first_rtp_timestamp_); + FrameEventList::iterator it = frame_events_.begin(); + ASSERT_NE(frame_events_.end(), it); + EXPECT_EQ(0u, (*it)->relative_rtp_timestamp()); + + ++it; + ASSERT_NE(frame_events_.end(), it); + EXPECT_EQ(30u, (*it)->relative_rtp_timestamp()); + + rtp_timestamp = 67890; + + cast_environment_->Logging()->InsertFrameEvent(now, + FRAME_CAPTURE_BEGIN, + VIDEO_EVENT, + rtp_timestamp, + /*frame_id*/ 0); + GetEventsAndReset(); + + EXPECT_EQ(rtp_timestamp, first_rtp_timestamp_); +} + +TEST_F(EncodingEventSubscriberTest, RelativeRtpTimestampWrapAround) { + Init(VIDEO_EVENT); + RtpTimestamp rtp_timestamp = 0xffffffff - 20; + base::TimeTicks now(testing_clock_->NowTicks()); + + cast_environment_->Logging()->InsertFrameEvent(now, + FRAME_CAPTURE_BEGIN, + VIDEO_EVENT, + rtp_timestamp, + /*frame_id*/ 0); + + // RtpTimestamp has now wrapped around. + cast_environment_->Logging()->InsertFrameEvent(now, + FRAME_CAPTURE_END, + VIDEO_EVENT, + rtp_timestamp + 30, + /*frame_id*/ 1); + + GetEventsAndReset(); + + FrameEventList::iterator it = frame_events_.begin(); + ASSERT_NE(frame_events_.end(), it); + EXPECT_EQ(0u, (*it)->relative_rtp_timestamp()); + + ++it; + ASSERT_NE(frame_events_.end(), it); + EXPECT_EQ(30u, (*it)->relative_rtp_timestamp()); +} + +TEST_F(EncodingEventSubscriberTest, MaxEventsPerProto) { + Init(VIDEO_EVENT); + RtpTimestamp rtp_timestamp = 100; + for (int i = 0; i < kMaxEventsPerProto + 1; i++) { + cast_environment_->Logging()->InsertFrameEvent(testing_clock_->NowTicks(), + FRAME_ACK_RECEIVED, + VIDEO_EVENT, + rtp_timestamp, + /*frame_id*/ 0); + testing_clock_->Advance(base::TimeDelta::FromMilliseconds(30)); + } + + GetEventsAndReset(); + + ASSERT_EQ(2u, frame_events_.size()); + FrameEventList::iterator frame_it = frame_events_.begin(); + ASSERT_TRUE(frame_it != frame_events_.end()); + + linked_ptr<AggregatedFrameEvent> frame_event = *frame_it; + + EXPECT_EQ(kMaxEventsPerProto, frame_event->event_type_size()); + + for (int i = 0; i < kMaxPacketsPerFrame + 1; i++) { + cast_environment_->Logging()->InsertPacketEvent( + testing_clock_->NowTicks(), + PACKET_SENT_TO_NETWORK, + VIDEO_EVENT, + rtp_timestamp, + /*frame_id*/ 0, + i, + kMaxPacketsPerFrame, + 123); + testing_clock_->Advance(base::TimeDelta::FromMilliseconds(30)); + } + + GetEventsAndReset(); + + EXPECT_EQ(2u, packet_events_.size()); + + PacketEventList::iterator packet_it = packet_events_.begin(); + ASSERT_TRUE(packet_it != packet_events_.end()); + + linked_ptr<AggregatedPacketEvent> packet_event = *packet_it; + + EXPECT_EQ(kMaxPacketsPerFrame, + packet_event->base_packet_event_size()); + + ++packet_it; + packet_event = *packet_it; + EXPECT_EQ(1, packet_event->base_packet_event_size()); + + for (int j = 0; j < kMaxEventsPerProto + 1; j++) { + cast_environment_->Logging()->InsertPacketEvent( + testing_clock_->NowTicks(), + PACKET_SENT_TO_NETWORK, + VIDEO_EVENT, + rtp_timestamp, + /*frame_id*/ 0, + 0, + 0, + 123); + testing_clock_->Advance(base::TimeDelta::FromMilliseconds(30)); + } + + GetEventsAndReset(); + + EXPECT_EQ(2u, packet_events_.size()); + packet_it = packet_events_.begin(); + ASSERT_TRUE(packet_it != packet_events_.end()); + + packet_event = *packet_it; + + EXPECT_EQ(kMaxEventsPerProto, + packet_event->base_packet_event(0).event_type_size()); + + ++packet_it; + packet_event = *packet_it; + EXPECT_EQ(1, packet_event->base_packet_event(0).event_type_size()); +} + +} // namespace cast +} // namespace media diff --git a/chromium/media/cast/logging/log_deserializer.cc b/chromium/media/cast/logging/log_deserializer.cc new file mode 100644 index 00000000000..1c6dd572240 --- /dev/null +++ b/chromium/media/cast/logging/log_deserializer.cc @@ -0,0 +1,252 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/cast/logging/log_deserializer.h" + +#include <map> +#include <utility> + +#include "base/big_endian.h" +#include "base/memory/scoped_ptr.h" +#include "third_party/zlib/zlib.h" + +using media::cast::FrameEventMap; +using media::cast::PacketEventMap; +using media::cast::RtpTimestamp; +using media::cast::proto::AggregatedFrameEvent; +using media::cast::proto::AggregatedPacketEvent; +using media::cast::proto::BasePacketEvent; +using media::cast::proto::LogMetadata; + +namespace { + +// Use 60MB of temp buffer to hold uncompressed data if |compress| is true. +// This is double the size of temp buffer used during compression (30MB) +// since the there are two streams in the blob. +// Keep in sync with media/cast/logging/log_serializer.cc. +const int kMaxUncompressedBytes = 60 * 1000 * 1000; + +void MergePacketEvent(const AggregatedPacketEvent& from, + linked_ptr<AggregatedPacketEvent> to) { + for (int i = 0; i < from.base_packet_event_size(); i++) { + const BasePacketEvent& from_base_event = from.base_packet_event(i); + bool merged = false; + for (int j = 0; j < to->base_packet_event_size(); j++) { + BasePacketEvent* to_base_event = to->mutable_base_packet_event(j); + if (from_base_event.packet_id() == to_base_event->packet_id()) { + int packet_size = std::max( + from_base_event.size(), to_base_event->size()); + // Need special merge logic here because we need to prevent a valid + // packet size (> 0) from being overwritten with an invalid one (= 0). + to_base_event->MergeFrom(from_base_event); + to_base_event->set_size(packet_size); + merged = true; + break; + } + } + if (!merged) { + BasePacketEvent* to_base_event = to->add_base_packet_event(); + to_base_event->CopyFrom(from_base_event); + } + } +} + +void MergeFrameEvent(const AggregatedFrameEvent& from, + linked_ptr<AggregatedFrameEvent> to) { + to->mutable_event_type()->MergeFrom(from.event_type()); + to->mutable_event_timestamp_ms()->MergeFrom(from.event_timestamp_ms()); + if (!to->has_encoded_frame_size() && from.has_encoded_frame_size()) + to->set_encoded_frame_size(from.encoded_frame_size()); + if (!to->has_delay_millis() && from.has_delay_millis()) + to->set_delay_millis(from.delay_millis()); + if (!to->has_key_frame() && from.has_key_frame()) + to->set_key_frame(from.key_frame()); + if (!to->has_target_bitrate() && from.has_target_bitrate()) + to->set_target_bitrate(from.target_bitrate()); +} + +bool PopulateDeserializedLog(base::BigEndianReader* reader, + media::cast::DeserializedLog* log) { + FrameEventMap frame_event_map; + PacketEventMap packet_event_map; + + int num_frame_events = log->metadata.num_frame_events(); + RtpTimestamp relative_rtp_timestamp = 0; + uint16 proto_size = 0; + for (int i = 0; i < num_frame_events; i++) { + if (!reader->ReadU16(&proto_size)) + return false; + + linked_ptr<AggregatedFrameEvent> frame_event(new AggregatedFrameEvent); + if (!frame_event->ParseFromArray(reader->ptr(), proto_size)) + return false; + if (!reader->Skip(proto_size)) + return false; + + // During serialization the RTP timestamp in proto is relative to previous + // frame. + // Adjust RTP timestamp back to value relative to first RTP timestamp. + frame_event->set_relative_rtp_timestamp( + frame_event->relative_rtp_timestamp() + relative_rtp_timestamp); + relative_rtp_timestamp = frame_event->relative_rtp_timestamp(); + + FrameEventMap::iterator it = frame_event_map.find( + frame_event->relative_rtp_timestamp()); + if (it == frame_event_map.end()) { + frame_event_map.insert( + std::make_pair(frame_event->relative_rtp_timestamp(), frame_event)); + } else { + // Events for the same frame might have been split into more than one + // proto. Merge them. + MergeFrameEvent(*frame_event, it->second); + } + } + + log->frame_events.swap(frame_event_map); + + int num_packet_events = log->metadata.num_packet_events(); + relative_rtp_timestamp = 0; + for (int i = 0; i < num_packet_events; i++) { + if (!reader->ReadU16(&proto_size)) + return false; + + linked_ptr<AggregatedPacketEvent> packet_event(new AggregatedPacketEvent); + if (!packet_event->ParseFromArray(reader->ptr(), proto_size)) + return false; + if (!reader->Skip(proto_size)) + return false; + + packet_event->set_relative_rtp_timestamp( + packet_event->relative_rtp_timestamp() + relative_rtp_timestamp); + relative_rtp_timestamp = packet_event->relative_rtp_timestamp(); + + PacketEventMap::iterator it = packet_event_map.find( + packet_event->relative_rtp_timestamp()); + if (it == packet_event_map.end()) { + packet_event_map.insert( + std::make_pair(packet_event->relative_rtp_timestamp(), packet_event)); + } else { + // Events for the same frame might have been split into more than one + // proto. Merge them. + MergePacketEvent(*packet_event, it->second); + } + } + + log->packet_events.swap(packet_event_map); + + return true; +} + +bool DoDeserializeEvents(const char* data, + int data_bytes, + media::cast::DeserializedLog* audio_log, + media::cast::DeserializedLog* video_log) { + bool got_audio = false; + bool got_video = false; + base::BigEndianReader reader(data, data_bytes); + + LogMetadata metadata; + uint16 proto_size = 0; + while (reader.remaining() > 0) { + if (!reader.ReadU16(&proto_size)) + return false; + if (!metadata.ParseFromArray(reader.ptr(), proto_size)) + return false; + reader.Skip(proto_size); + + if (metadata.is_audio()) { + if (got_audio) { + VLOG(1) << "Got audio data twice."; + return false; + } + + got_audio = true; + audio_log->metadata = metadata; + if (!PopulateDeserializedLog(&reader, audio_log)) + return false; + } else { + if (got_video) { + VLOG(1) << "Got duplicate video log."; + return false; + } + + got_video = true; + video_log->metadata = metadata; + if (!PopulateDeserializedLog(&reader, video_log)) + return false; + } + } + return true; +} + +bool Uncompress(const char* data, + int data_bytes, + int max_uncompressed_bytes, + char* uncompressed, + int* uncompressed_bytes) { + z_stream stream = {0}; + + stream.next_in = reinterpret_cast<uint8*>(const_cast<char*>(data)); + stream.avail_in = data_bytes; + stream.next_out = reinterpret_cast<uint8*>(uncompressed); + stream.avail_out = max_uncompressed_bytes; + + bool success = false; + while (stream.avail_in > 0 && stream.avail_out > 0) { + // 16 is added to read in gzip format. + int result = inflateInit2(&stream, MAX_WBITS + 16); + DCHECK_EQ(Z_OK, result); + + result = inflate(&stream, Z_FINISH); + success = (result == Z_STREAM_END); + if (!success) { + DVLOG(2) << "inflate() failed. Result: " << result; + break; + } + + result = inflateEnd(&stream); + DCHECK(result == Z_OK); + } + + if (stream.avail_in == 0) { + success = true; + *uncompressed_bytes = max_uncompressed_bytes - stream.avail_out; + } + return success; +} + +} // namespace + +namespace media { +namespace cast { + +bool DeserializeEvents(const char* data, + int data_bytes, + bool compressed, + DeserializedLog* audio_log, + DeserializedLog* video_log) { + DCHECK_GT(data_bytes, 0); + + if (compressed) { + scoped_ptr<char[]> uncompressed(new char[kMaxUncompressedBytes]); + int uncompressed_bytes = 0; + if (!Uncompress(data, + data_bytes, + kMaxUncompressedBytes, + uncompressed.get(), + &uncompressed_bytes)) + return false; + + return DoDeserializeEvents( + uncompressed.get(), uncompressed_bytes, audio_log, video_log); + } else { + return DoDeserializeEvents(data, data_bytes, audio_log, video_log); + } +} + +DeserializedLog::DeserializedLog() {} +DeserializedLog::~DeserializedLog() {} + +} // namespace cast +} // namespace media diff --git a/chromium/media/cast/logging/log_deserializer.h b/chromium/media/cast/logging/log_deserializer.h new file mode 100644 index 00000000000..01b6db7dd12 --- /dev/null +++ b/chromium/media/cast/logging/log_deserializer.h @@ -0,0 +1,51 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_CAST_LOGGING_LOG_DESERIALIZER_H_ +#define MEDIA_CAST_LOGGING_LOG_DESERIALIZER_H_ + +#include <map> +#include <string> + +#include "base/memory/linked_ptr.h" +#include "media/cast/logging/logging_defines.h" +#include "media/cast/logging/proto/raw_events.pb.h" + +namespace media { +namespace cast { + +typedef std::map<RtpTimestamp, + linked_ptr<media::cast::proto::AggregatedFrameEvent> > + FrameEventMap; +typedef std::map<RtpTimestamp, + linked_ptr<media::cast::proto::AggregatedPacketEvent> > + PacketEventMap; + +// Represents deserialized raw event logs for a particular stream. +struct DeserializedLog { + DeserializedLog(); + ~DeserializedLog(); + proto::LogMetadata metadata; + FrameEventMap frame_events; + PacketEventMap packet_events; +}; + +// This function takes the output of LogSerializer and deserializes it into +// its original format. Returns true if deserialization is successful. All +// output arguments are valid if this function returns true. +// |data|: Serialized event logs with length |data_bytes|. +// |compressed|: true if |data| is compressed in gzip format. +// |log_metadata|: This will be populated with deserialized LogMetadata proto. +// |audio_log|, |video_log|: These will be populated with deserialized +// log data for audio and video streams, respectively. +bool DeserializeEvents(const char* data, + int data_bytes, + bool compressed, + DeserializedLog* audio_log, + DeserializedLog* video_log); + +} // namespace cast +} // namespace media + +#endif // MEDIA_CAST_LOGGING_LOG_DESERIALIZER_H_ diff --git a/chromium/media/cast/logging/log_serializer.cc b/chromium/media/cast/logging/log_serializer.cc new file mode 100644 index 00000000000..afcf77013f3 --- /dev/null +++ b/chromium/media/cast/logging/log_serializer.cc @@ -0,0 +1,190 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// The serialization format is as follows: +// 16-bit integer describing the following LogMetadata proto size in bytes. +// The LogMetadata proto. +// 32-bit integer describing number of frame events. +// (The following repeated for number of frame events): +// 16-bit integer describing the following AggregatedFrameEvent proto size +// in bytes. +// The AggregatedFrameEvent proto. +// 32-bit integer describing number of packet events. +// (The following repeated for number of packet events): +// 16-bit integer describing the following AggregatedPacketEvent proto +// size in bytes. +// The AggregatedPacketEvent proto. + +#include "media/cast/logging/log_serializer.h" + +#include "base/big_endian.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "third_party/zlib/zlib.h" + +namespace media { +namespace cast { + +namespace { + +using media::cast::proto::AggregatedFrameEvent; +using media::cast::proto::AggregatedPacketEvent; +using media::cast::proto::LogMetadata; + +// Use 30MB of temp buffer to hold uncompressed data if |compress| is true. +const int kMaxUncompressedBytes = 30 * 1000 * 1000; + +// The maximum allowed size per serialized proto. +const int kMaxSerializedProtoBytes = (1 << 16) - 1; +bool DoSerializeEvents(const LogMetadata& metadata, + const FrameEventList& frame_events, + const PacketEventList& packet_events, + const int max_output_bytes, + char* output, + int* output_bytes) { + base::BigEndianWriter writer(output, max_output_bytes); + + int proto_size = metadata.ByteSize(); + DCHECK(proto_size <= kMaxSerializedProtoBytes); + if (!writer.WriteU16(proto_size)) + return false; + if (!metadata.SerializeToArray(writer.ptr(), writer.remaining())) + return false; + if (!writer.Skip(proto_size)) + return false; + + RtpTimestamp prev_rtp_timestamp = 0; + for (media::cast::FrameEventList::const_iterator it = frame_events.begin(); + it != frame_events.end(); + ++it) { + media::cast::proto::AggregatedFrameEvent frame_event(**it); + + // Adjust relative RTP timestamp so that it is relative to previous frame, + // rather than relative to first RTP timestamp. + // This is done to improve encoding size. + RtpTimestamp old_relative_rtp_timestamp = + frame_event.relative_rtp_timestamp(); + frame_event.set_relative_rtp_timestamp( + old_relative_rtp_timestamp - prev_rtp_timestamp); + prev_rtp_timestamp = old_relative_rtp_timestamp; + + proto_size = frame_event.ByteSize(); + DCHECK(proto_size <= kMaxSerializedProtoBytes); + + // Write size of the proto, then write the proto. + if (!writer.WriteU16(proto_size)) + return false; + if (!frame_event.SerializeToArray(writer.ptr(), writer.remaining())) + return false; + if (!writer.Skip(proto_size)) + return false; + } + + // Write packet events. + prev_rtp_timestamp = 0; + for (media::cast::PacketEventList::const_iterator it = packet_events.begin(); + it != packet_events.end(); + ++it) { + media::cast::proto::AggregatedPacketEvent packet_event(**it); + RtpTimestamp old_relative_rtp_timestamp = + packet_event.relative_rtp_timestamp(); + packet_event.set_relative_rtp_timestamp( + old_relative_rtp_timestamp - prev_rtp_timestamp); + prev_rtp_timestamp = old_relative_rtp_timestamp; + + proto_size = packet_event.ByteSize(); + DCHECK(proto_size <= kMaxSerializedProtoBytes); + + // Write size of the proto, then write the proto. + if (!writer.WriteU16(proto_size)) + return false; + if (!packet_event.SerializeToArray(writer.ptr(), writer.remaining())) + return false; + if (!writer.Skip(proto_size)) + return false; + } + + *output_bytes = max_output_bytes - writer.remaining(); + return true; +} + +bool Compress(char* uncompressed_buffer, + int uncompressed_bytes, + int max_output_bytes, + char* output, + int* output_bytes) { + z_stream stream = {0}; + int result = deflateInit2(&stream, + Z_DEFAULT_COMPRESSION, + Z_DEFLATED, + // 16 is added to produce a gzip header + trailer. + MAX_WBITS + 16, + 8, // memLevel = 8 is default. + Z_DEFAULT_STRATEGY); + DCHECK_EQ(Z_OK, result); + + stream.next_in = reinterpret_cast<uint8*>(uncompressed_buffer); + stream.avail_in = uncompressed_bytes; + stream.next_out = reinterpret_cast<uint8*>(output); + stream.avail_out = max_output_bytes; + + // Do a one-shot compression. This will return Z_STREAM_END only if |output| + // is large enough to hold all compressed data. + result = deflate(&stream, Z_FINISH); + bool success = (result == Z_STREAM_END); + + if (!success) + DVLOG(2) << "deflate() failed. Result: " << result; + + result = deflateEnd(&stream); + DCHECK(result == Z_OK || result == Z_DATA_ERROR); + + if (success) + *output_bytes = max_output_bytes - stream.avail_out; + + return success; +} + +} // namespace + +bool SerializeEvents(const LogMetadata& log_metadata, + const FrameEventList& frame_events, + const PacketEventList& packet_events, + bool compress, + int max_output_bytes, + char* output, + int* output_bytes) { + DCHECK_GT(max_output_bytes, 0); + DCHECK(output); + DCHECK(output_bytes); + + if (compress) { + // Allocate a reasonably large temp buffer to hold uncompressed data. + scoped_ptr<char[]> uncompressed_buffer(new char[kMaxUncompressedBytes]); + int uncompressed_bytes; + bool success = DoSerializeEvents(log_metadata, + frame_events, + packet_events, + kMaxUncompressedBytes, + uncompressed_buffer.get(), + &uncompressed_bytes); + if (!success) + return false; + return Compress(uncompressed_buffer.get(), + uncompressed_bytes, + max_output_bytes, + output, + output_bytes); + } else { + return DoSerializeEvents(log_metadata, + frame_events, + packet_events, + max_output_bytes, + output, + output_bytes); + } +} + +} // namespace cast +} // namespace media diff --git a/chromium/media/cast/logging/log_serializer.h b/chromium/media/cast/logging/log_serializer.h new file mode 100644 index 00000000000..8aff54fc95d --- /dev/null +++ b/chromium/media/cast/logging/log_serializer.h @@ -0,0 +1,37 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_CAST_LOGGING_LOG_SERIALIZER_H_ +#define MEDIA_CAST_LOGGING_LOG_SERIALIZER_H_ + +#include <string> + +#include "media/cast/logging/encoding_event_subscriber.h" + +namespace media { +namespace cast { + +// Serialize |frame_events|, |packet_events|, |log_metadata| +// returned from EncodingEventSubscriber. +// Result is written to |output|, which can hold |max_output_bytes| of data. +// If |compress| is true, |output| will be set with data compresssed in +// gzip format. +// |output_bytes| will be set to number of bytes written. +// +// Returns |true| if serialization is successful. This function +// returns |false| if the serialized string will exceed |max_output_bytes|. +// +// See .cc file for format specification. +bool SerializeEvents(const media::cast::proto::LogMetadata& log_metadata, + const FrameEventList& frame_events, + const PacketEventList& packet_events, + bool compress, + int max_output_bytes, + char* output, + int* output_bytes); + +} // namespace cast +} // namespace media + +#endif // MEDIA_CAST_LOGGING_LOG_SERIALIZER_H_ diff --git a/chromium/media/cast/logging/logging_defines.cc b/chromium/media/cast/logging/logging_defines.cc index 85abe7c5d45..05ceeb9521f 100644 --- a/chromium/media/cast/logging/logging_defines.cc +++ b/chromium/media/cast/logging/logging_defines.cc @@ -6,96 +6,46 @@ #include "base/logging.h" +#define ENUM_TO_STRING(enum) \ + case enum: \ + return #enum + namespace media { namespace cast { -CastLoggingConfig::CastLoggingConfig() - : enable_data_collection(false), - enable_uma_stats(false), - enable_tracing(false) {} - -CastLoggingConfig::~CastLoggingConfig() {} - -CastLoggingConfig GetDefaultCastLoggingConfig() { - CastLoggingConfig config; - return config; -} - -std::string CastLoggingToString(CastLoggingEvent event) { +const char* CastLoggingToString(CastLoggingEvent event) { switch (event) { - case(kUnknown): - // Can happen if the sender and receiver of RTCP log messages are not - // aligned. - return "Unknown"; - case(kRttMs): - return "RttMs"; - case(kPacketLoss): - return "PacketLoss"; - case(kJitterMs): - return "JitterMs"; - case(kAckReceived): - return "AckReceived"; - case(kRembBitrate): - return "RembBitrate"; - case(kAckSent): - return "AckSent"; - case(kLastEvent): - return "LastEvent"; - case(kAudioFrameReceived): - return "AudioFrameReceived"; - case(kAudioFrameCaptured): - return "AudioFrameCaptured"; - case(kAudioFrameEncoded): - return "AudioFrameEncoded"; - case(kAudioPlayoutDelay): - return "AudioPlayoutDelay"; - case(kAudioFrameDecoded): - return "AudioFrameDecoded"; - case(kVideoFrameCaptured): - return "VideoFrameCaptured"; - case(kVideoFrameReceived): - return "VideoFrameReceived"; - case(kVideoFrameSentToEncoder): - return "VideoFrameSentToEncoder"; - case(kVideoFrameEncoded): - return "VideoFrameEncoded"; - case(kVideoFrameDecoded): - return "VideoFrameDecoded"; - case(kVideoRenderDelay): - return "VideoRenderDelay"; - case(kPacketSentToPacer): - return "PacketSentToPacer"; - case(kPacketSentToNetwork): - return "PacketSentToNetwork"; - case(kPacketRetransmited): - return "PacketRetransmited"; - case(kPacketReceived): - return "PacketReceived"; - default: - NOTREACHED(); - return ""; + ENUM_TO_STRING(UNKNOWN); + ENUM_TO_STRING(FRAME_CAPTURE_BEGIN); + ENUM_TO_STRING(FRAME_CAPTURE_END); + ENUM_TO_STRING(FRAME_ENCODED); + ENUM_TO_STRING(FRAME_ACK_RECEIVED); + ENUM_TO_STRING(FRAME_ACK_SENT); + ENUM_TO_STRING(FRAME_DECODED); + ENUM_TO_STRING(FRAME_PLAYOUT); + ENUM_TO_STRING(PACKET_SENT_TO_NETWORK); + ENUM_TO_STRING(PACKET_RETRANSMITTED); + ENUM_TO_STRING(PACKET_RTX_REJECTED); + ENUM_TO_STRING(PACKET_RECEIVED); } + NOTREACHED(); + return ""; } -FrameEvent::FrameEvent() {} +FrameEvent::FrameEvent() + : rtp_timestamp(0u), frame_id(kFrameIdUnknown), size(0u), type(UNKNOWN), + media_type(UNKNOWN_EVENT), key_frame(false), target_bitrate(0) {} FrameEvent::~FrameEvent() {} -BasePacketInfo::BasePacketInfo() {} -BasePacketInfo::~BasePacketInfo() {} - -PacketEvent::PacketEvent() {} +PacketEvent::PacketEvent() + : rtp_timestamp(0), + frame_id(kFrameIdUnknown), + max_packet_id(0), + packet_id(0), + size(0), + type(UNKNOWN), + media_type(UNKNOWN_EVENT) {} PacketEvent::~PacketEvent() {} -GenericEvent::GenericEvent() {} -GenericEvent::~GenericEvent() {} - -FrameLogStats::FrameLogStats() - : framerate_fps(0), - bitrate_kbps(0), - max_delay_ms(0), - min_delay_ms(0), - avg_delay_ms(0) {} -FrameLogStats::~FrameLogStats() {} - } // namespace cast } // namespace media diff --git a/chromium/media/cast/logging/logging_defines.h b/chromium/media/cast/logging/logging_defines.h index 5a7bca1500f..021a3c99a7a 100644 --- a/chromium/media/cast/logging/logging_defines.h +++ b/chromium/media/cast/logging/logging_defines.h @@ -9,120 +9,88 @@ #include <string> #include <vector> -#include "base/memory/linked_ptr.h" #include "base/time/time.h" namespace media { namespace cast { -static const uint32 kFrameIdUnknown = 0xFFFF; +static const uint32 kFrameIdUnknown = 0xFFFFFFFF; -struct CastLoggingConfig { - CastLoggingConfig(); - ~CastLoggingConfig(); +typedef uint32 RtpTimestamp; - bool enable_data_collection; - bool enable_uma_stats; - bool enable_tracing; +enum CastLoggingEvent { + UNKNOWN, + // Sender side frame events. + FRAME_CAPTURE_BEGIN, + FRAME_CAPTURE_END, + FRAME_ENCODED, + FRAME_ACK_RECEIVED, + // Receiver side frame events. + FRAME_ACK_SENT, + FRAME_DECODED, + FRAME_PLAYOUT, + // Sender side packet events. + PACKET_SENT_TO_NETWORK, + PACKET_RETRANSMITTED, + PACKET_RTX_REJECTED, + // Receiver side packet events. + PACKET_RECEIVED, + kNumOfLoggingEvents = PACKET_RECEIVED }; -// By default, enable raw and stats data collection. Disable tracing and UMA. -CastLoggingConfig GetDefaultCastLoggingConfig(); +const char* CastLoggingToString(CastLoggingEvent event); -enum CastLoggingEvent { - // Generic events. - kUnknown, - kRttMs, - kPacketLoss, - kJitterMs, - kAckReceived, - kRembBitrate, - kAckSent, - kLastEvent, - // Audio sender. - kAudioFrameReceived, - kAudioFrameCaptured, - kAudioFrameEncoded, - // Audio receiver. - kAudioPlayoutDelay, - kAudioFrameDecoded, - // Video sender. - kVideoFrameCaptured, - kVideoFrameReceived, - kVideoFrameSentToEncoder, - kVideoFrameEncoded, - // Video receiver. - kVideoFrameDecoded, - kVideoRenderDelay, - // Send-side packet events. - kPacketSentToPacer, - kPacketSentToNetwork, - kPacketRetransmited, - // Receive-side packet events. - kPacketReceived, - - kNumOfLoggingEvents, +// CastLoggingEvent are classified into one of three following types. +enum EventMediaType { + AUDIO_EVENT, + VIDEO_EVENT, + UNKNOWN_EVENT, + EVENT_MEDIA_TYPE_LAST = UNKNOWN_EVENT }; -std::string CastLoggingToString(CastLoggingEvent event); - struct FrameEvent { FrameEvent(); ~FrameEvent(); + RtpTimestamp rtp_timestamp; uint32 frame_id; - size_t size; // Encoded size only. - std::vector<base::TimeTicks> timestamp; - std::vector<CastLoggingEvent> type; - base::TimeDelta delay_delta; // Render/playout delay. -}; - -// Internal map sorted by packet id. -struct BasePacketInfo { - BasePacketInfo(); - ~BasePacketInfo(); + // Size of encoded frame. Only set for FRAME_ENCODED event. size_t size; - std::vector<base::TimeTicks> timestamp; - std::vector<CastLoggingEvent> type; -}; -typedef std::map<uint16, BasePacketInfo> BasePacketMap; + // Time of event logged. + base::TimeTicks timestamp; -struct PacketEvent { - PacketEvent(); - ~PacketEvent(); - uint32 frame_id; - int max_packet_id; - BasePacketMap packet_map; -}; + CastLoggingEvent type; -struct GenericEvent { - GenericEvent(); - ~GenericEvent(); - std::vector<int> value; - std::vector<base::TimeTicks> timestamp; -}; + EventMediaType media_type; + + // Render / playout delay. Only set for FRAME_PLAYOUT events. + base::TimeDelta delay_delta; -struct FrameLogStats { - FrameLogStats(); - ~FrameLogStats(); + // Whether the frame is a key frame. Only set for video FRAME_ENCODED event. + bool key_frame; - double framerate_fps; - double bitrate_kbps; - int max_delay_ms; - int min_delay_ms; - int avg_delay_ms; + // The requested target bitrate of the encoder at the time the frame is + // encoded. Only set for video FRAME_ENCODED event. + int target_bitrate; }; -// Store all log types in a map based on the event. -typedef std::map<uint32, FrameEvent> FrameRawMap; -typedef std::map<uint32, PacketEvent> PacketRawMap; -typedef std::map<CastLoggingEvent, GenericEvent> GenericRawMap; +struct PacketEvent { + PacketEvent(); + ~PacketEvent(); + + RtpTimestamp rtp_timestamp; + uint32 frame_id; + uint16 max_packet_id; + uint16 packet_id; + size_t size; -typedef std::map<CastLoggingEvent, linked_ptr<FrameLogStats > > FrameStatsMap; -typedef std::map<CastLoggingEvent, double> PacketStatsMap; -typedef std::map<CastLoggingEvent, double> GenericStatsMap; + // Time of event logged. + base::TimeTicks timestamp; + CastLoggingEvent type; + EventMediaType media_type; +}; } // namespace cast } // namespace media diff --git a/chromium/media/cast/logging/logging_impl.cc b/chromium/media/cast/logging/logging_impl.cc index ea96b94b610..1143d1be217 100644 --- a/chromium/media/cast/logging/logging_impl.cc +++ b/chromium/media/cast/logging/logging_impl.cc @@ -2,223 +2,113 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "base/big_endian.h" #include "base/debug/trace_event.h" -#include "base/metrics/histogram.h" #include "media/cast/logging/logging_impl.h" -#include "net/base/big_endian.h" namespace media { namespace cast { -LoggingImpl::LoggingImpl(base::TickClock* clock, - scoped_refptr<base::TaskRunner> main_thread_proxy, - const CastLoggingConfig& config) - : main_thread_proxy_(main_thread_proxy), - config_(config), - raw_(clock), - stats_(clock) {} +// TODO(imcheng): Collapse LoggingRaw onto LoggingImpl. +LoggingImpl::LoggingImpl() { + // LoggingImpl can be constructed on any thread, but its methods should all be + // called on the same thread. + thread_checker_.DetachFromThread(); +} LoggingImpl::~LoggingImpl() {} -void LoggingImpl::InsertFrameEvent(CastLoggingEvent event, +void LoggingImpl::InsertFrameEvent(const base::TimeTicks& time_of_event, + CastLoggingEvent event, + EventMediaType event_media_type, uint32 rtp_timestamp, uint32 frame_id) { - DCHECK(main_thread_proxy_->RunsTasksOnCurrentThread()); - if (config_.enable_data_collection) { - raw_.InsertFrameEvent(event, rtp_timestamp, frame_id); - stats_.InsertFrameEvent(event, rtp_timestamp, frame_id); - } - if (config_.enable_tracing) { - std::string event_string = CastLoggingToString(event); - TRACE_EVENT_INSTANT2(event_string.c_str(), "FE", - TRACE_EVENT_SCOPE_THREAD, "rtp_timestamp", rtp_timestamp, "frame_id", - frame_id); - } -} - -void LoggingImpl::InsertFrameEventWithSize(CastLoggingEvent event, - uint32 rtp_timestamp, - uint32 frame_id, - int frame_size) { - DCHECK(main_thread_proxy_->RunsTasksOnCurrentThread()); - if (config_.enable_data_collection) { - raw_.InsertFrameEventWithSize(event, rtp_timestamp, frame_id, frame_size); - stats_.InsertFrameEventWithSize(event, rtp_timestamp, frame_id, frame_size); - } - if (config_.enable_uma_stats) { - UMA_HISTOGRAM_COUNTS(CastLoggingToString(event), frame_size); - } - if (config_.enable_tracing) { - std::string event_string = CastLoggingToString(event); - TRACE_EVENT_INSTANT2(event_string.c_str(), "FES", - TRACE_EVENT_SCOPE_THREAD, "rtp_timestamp", rtp_timestamp, "frame_size", - frame_size); - } + DCHECK(thread_checker_.CalledOnValidThread()); + raw_.InsertFrameEvent(time_of_event, event, event_media_type, + rtp_timestamp, frame_id); } -void LoggingImpl::InsertFrameEventWithDelay(CastLoggingEvent event, - uint32 rtp_timestamp, - uint32 frame_id, - base::TimeDelta delay) { - DCHECK(main_thread_proxy_->RunsTasksOnCurrentThread()); - if (config_.enable_data_collection) { - raw_.InsertFrameEventWithDelay(event, rtp_timestamp, frame_id, delay); - stats_.InsertFrameEventWithDelay(event, rtp_timestamp, frame_id, delay); - } - if (config_.enable_uma_stats) { - UMA_HISTOGRAM_TIMES(CastLoggingToString(event), delay); - } - if (config_.enable_tracing) { - std::string event_string = CastLoggingToString(event); - TRACE_EVENT_INSTANT2(event_string.c_str(), "FED", - TRACE_EVENT_SCOPE_THREAD, "rtp_timestamp", rtp_timestamp, "delay", - delay.InMilliseconds()); - } +void LoggingImpl::InsertEncodedFrameEvent(const base::TimeTicks& time_of_event, + CastLoggingEvent event, + EventMediaType event_media_type, + uint32 rtp_timestamp, + uint32 frame_id, int frame_size, + bool key_frame, + int target_bitrate) { + DCHECK(thread_checker_.CalledOnValidThread()); + raw_.InsertEncodedFrameEvent(time_of_event, event, event_media_type, + rtp_timestamp, frame_id, frame_size, key_frame, target_bitrate); } -void LoggingImpl::InsertPacketListEvent(CastLoggingEvent event, - const PacketList& packets) { - DCHECK(main_thread_proxy_->RunsTasksOnCurrentThread()); - for (unsigned int i = 0; i < packets.size(); ++i) { - const Packet& packet = packets[i]; - // Parse basic properties. - uint32 rtp_timestamp; - uint16 packet_id, max_packet_id; - const uint8* packet_data = &packet[0]; - net::BigEndianReader big_endian_reader(packet_data + 4, 4); - big_endian_reader.ReadU32(&rtp_timestamp); - net::BigEndianReader cast_big_endian_reader(packet_data + 12 + 2, 4); - cast_big_endian_reader.ReadU16(&packet_id); - cast_big_endian_reader.ReadU16(&max_packet_id); - // rtp_timestamp is enough - no need for frame_id as well. - InsertPacketEvent(event, rtp_timestamp, kFrameIdUnknown, packet_id, - max_packet_id, packet.size()); - } +void LoggingImpl::InsertFrameEventWithDelay( + const base::TimeTicks& time_of_event, CastLoggingEvent event, + EventMediaType event_media_type, uint32 rtp_timestamp, uint32 frame_id, + base::TimeDelta delay) { + DCHECK(thread_checker_.CalledOnValidThread()); + raw_.InsertFrameEventWithDelay(time_of_event, event, event_media_type, + rtp_timestamp, frame_id, delay); } -void LoggingImpl::InsertPacketEvent(CastLoggingEvent event, - uint32 rtp_timestamp, - uint32 frame_id, - uint16 packet_id, - uint16 max_packet_id, - size_t size) { - DCHECK(main_thread_proxy_->RunsTasksOnCurrentThread()); - if (config_.enable_data_collection) { - raw_.InsertPacketEvent(event, rtp_timestamp, frame_id, packet_id, - max_packet_id, size); - stats_.InsertPacketEvent(event, rtp_timestamp, frame_id, packet_id, - max_packet_id, size); - } - if (config_.enable_tracing) { - std::string event_string = CastLoggingToString(event); - TRACE_EVENT_INSTANT2(event_string.c_str(), "PE", - TRACE_EVENT_SCOPE_THREAD, "rtp_timestamp", rtp_timestamp, - "packet_id", packet_id); - } +void LoggingImpl::InsertSinglePacketEvent(const base::TimeTicks& time_of_event, + CastLoggingEvent event, + EventMediaType event_media_type, + const Packet& packet) { + DCHECK(thread_checker_.CalledOnValidThread()); + + // Parse basic properties. + uint32 rtp_timestamp; + uint16 packet_id, max_packet_id; + const uint8* packet_data = &packet[0]; + base::BigEndianReader big_endian_reader( + reinterpret_cast<const char*>(packet_data + 4), 4); + big_endian_reader.ReadU32(&rtp_timestamp); + base::BigEndianReader cast_big_endian_reader( + reinterpret_cast<const char*>(packet_data + 12 + 2), 4); + cast_big_endian_reader.ReadU16(&packet_id); + cast_big_endian_reader.ReadU16(&max_packet_id); + + // rtp_timestamp is enough - no need for frame_id as well. + InsertPacketEvent(time_of_event, + event, + event_media_type, + rtp_timestamp, + kFrameIdUnknown, + packet_id, + max_packet_id, + packet.size()); } -void LoggingImpl::InsertGenericEvent(CastLoggingEvent event, int value) { - DCHECK(main_thread_proxy_->RunsTasksOnCurrentThread()); - if (config_.enable_data_collection) { - raw_.InsertGenericEvent(event, value); - stats_.InsertGenericEvent(event, value); - } - if (config_.enable_uma_stats) { - UMA_HISTOGRAM_COUNTS(CastLoggingToString(event), value); - } - if (config_.enable_tracing) { - std::string event_string = CastLoggingToString(event); - TRACE_EVENT_INSTANT1(event_string.c_str(), "GE", - TRACE_EVENT_SCOPE_THREAD, "value", value); - } -} - -// should just get the entire class, would be much easier. -FrameRawMap LoggingImpl::GetFrameRawData() { - DCHECK(main_thread_proxy_->RunsTasksOnCurrentThread()); - return raw_.GetFrameData(); -} - -PacketRawMap LoggingImpl::GetPacketRawData() { - DCHECK(main_thread_proxy_->RunsTasksOnCurrentThread()); - return raw_.GetPacketData(); -} - -GenericRawMap LoggingImpl::GetGenericRawData() { - DCHECK(main_thread_proxy_->RunsTasksOnCurrentThread()); - return raw_.GetGenericData(); -} - -const FrameStatsMap* LoggingImpl::GetFrameStatsData() { - DCHECK(main_thread_proxy_->RunsTasksOnCurrentThread()); - // Get stats data. - const FrameStatsMap* stats = stats_.GetFrameStatsData(); - if (config_.enable_uma_stats) { - FrameStatsMap::const_iterator it; - for (it = stats->begin(); it != stats->end(); ++it) { - // Check for an active event. - if (it->second->framerate_fps > 0) { - std::string event_string = CastLoggingToString(it->first); - UMA_HISTOGRAM_COUNTS(event_string.append("_framerate_fps"), - it->second->framerate_fps); - } else { - // All active frame events trigger framerate computation. - continue; - } - if (it->second->bitrate_kbps > 0) { - std::string evnt_string = CastLoggingToString(it->first); - UMA_HISTOGRAM_COUNTS(evnt_string.append("_bitrate_kbps"), - it->second->framerate_fps); - } - if (it->second->avg_delay_ms > 0) { - std::string event_string = CastLoggingToString(it->first); - UMA_HISTOGRAM_COUNTS(event_string.append("_avg_delay_ms"), - it->second->avg_delay_ms); - UMA_HISTOGRAM_COUNTS(event_string.append("_min_delay_ms"), - it->second->min_delay_ms); - UMA_HISTOGRAM_COUNTS(event_string.append("_max_delay_ms"), - it->second->max_delay_ms); - } - } +void LoggingImpl::InsertPacketListEvent(const base::TimeTicks& time_of_event, + CastLoggingEvent event, + EventMediaType event_media_type, + const PacketList& packets) { + DCHECK(thread_checker_.CalledOnValidThread()); + for (PacketList::const_iterator it = packets.begin(); it != packets.end(); + ++it) { + InsertSinglePacketEvent(time_of_event, event, event_media_type, + (*it)->data); } - return stats; } -const PacketStatsMap* LoggingImpl::GetPacketStatsData() { - DCHECK(main_thread_proxy_->RunsTasksOnCurrentThread()); - // Get stats data. - const PacketStatsMap* stats = stats_.GetPacketStatsData(); - if (config_.enable_uma_stats) { - PacketStatsMap::const_iterator it; - for (it = stats->begin(); it != stats->end(); ++it) { - if (it->second > 0) { - std::string event_string = CastLoggingToString(it->first); - UMA_HISTOGRAM_COUNTS(event_string.append("_bitrate_kbps"), it->second); - } - } - } - return stats; +void LoggingImpl::InsertPacketEvent(const base::TimeTicks& time_of_event, + CastLoggingEvent event, + EventMediaType event_media_type, + uint32 rtp_timestamp, uint32 frame_id, + uint16 packet_id, uint16 max_packet_id, + size_t size) { + DCHECK(thread_checker_.CalledOnValidThread()); + raw_.InsertPacketEvent(time_of_event, event, event_media_type, + rtp_timestamp, frame_id, packet_id, max_packet_id, size); } -const GenericStatsMap* LoggingImpl::GetGenericStatsData() { - DCHECK(main_thread_proxy_->RunsTasksOnCurrentThread()); - // Get stats data. - const GenericStatsMap* stats = stats_.GetGenericStatsData(); - if (config_.enable_uma_stats) { - GenericStatsMap::const_iterator it; - for (it = stats->begin(); it != stats->end(); ++it) { - if (it->second > 0) { - UMA_HISTOGRAM_COUNTS(CastLoggingToString(it->first), it->second); - } - } - } - return stats; +void LoggingImpl::AddRawEventSubscriber(RawEventSubscriber* subscriber) { + DCHECK(thread_checker_.CalledOnValidThread()); + raw_.AddSubscriber(subscriber); } -void LoggingImpl::Reset() { - DCHECK(main_thread_proxy_->RunsTasksOnCurrentThread()); - raw_.Reset(); - stats_.Reset(); +void LoggingImpl::RemoveRawEventSubscriber(RawEventSubscriber* subscriber) { + DCHECK(thread_checker_.CalledOnValidThread()); + raw_.RemoveSubscriber(subscriber); } } // namespace cast diff --git a/chromium/media/cast/logging/logging_impl.h b/chromium/media/cast/logging/logging_impl.h index 34021b7d03c..ba453c8c8ee 100644 --- a/chromium/media/cast/logging/logging_impl.h +++ b/chromium/media/cast/logging/logging_impl.h @@ -7,67 +7,66 @@ // Generic class that handles event logging for the cast library. // Logging has three possible optional forms: // 1. Raw data and stats accessible by the application. -// 2. UMA stats. -// 3. Tracing of raw events. +// 2. Tracing of raw events. #include "base/memory/ref_counted.h" -#include "base/task_runner.h" +#include "base/threading/thread_checker.h" #include "media/cast/cast_config.h" #include "media/cast/logging/logging_defines.h" #include "media/cast/logging/logging_raw.h" -#include "media/cast/logging/logging_stats.h" namespace media { namespace cast { -// Should only be called from the main thread. -class LoggingImpl : public base::NonThreadSafe { +class LoggingImpl { public: - LoggingImpl(base::TickClock* clock, - scoped_refptr<base::TaskRunner> main_thread_proxy, - const CastLoggingConfig& config); - + LoggingImpl(); ~LoggingImpl(); - // TODO(pwestin): Add argument to API to send in time of event instead of - // grabbing now. - void InsertFrameEvent(CastLoggingEvent event, - uint32 rtp_timestamp, - uint32 frame_id); - void InsertFrameEventWithSize(CastLoggingEvent event, - uint32 rtp_timestamp, - uint32 frame_id, - int frame_size); - void InsertFrameEventWithDelay(CastLoggingEvent event, - uint32 rtp_timestamp, - uint32 frame_id, + // Note: All methods below should be called from the same thread. + + void InsertFrameEvent(const base::TimeTicks& time_of_event, + CastLoggingEvent event, EventMediaType event_media_type, + uint32 rtp_timestamp, uint32 frame_id); + + void InsertEncodedFrameEvent(const base::TimeTicks& time_of_event, + CastLoggingEvent event, + EventMediaType event_media_type, + uint32 rtp_timestamp, uint32 frame_id, + int frame_size, bool key_frame, + int target_bitrate); + + void InsertFrameEventWithDelay(const base::TimeTicks& time_of_event, + CastLoggingEvent event, + EventMediaType event_media_type, + uint32 rtp_timestamp, uint32 frame_id, base::TimeDelta delay); - void InsertPacketListEvent(CastLoggingEvent event, const PacketList& packets); - - void InsertPacketEvent(CastLoggingEvent event, - uint32 rtp_timestamp, - uint32 frame_id, - uint16 packet_id, - uint16 max_packet_id, - size_t size); - void InsertGenericEvent(CastLoggingEvent event, int value); - - // Get raw data. - FrameRawMap GetFrameRawData(); - PacketRawMap GetPacketRawData(); - GenericRawMap GetGenericRawData(); - // Get stats only (computed when called). Triggers UMA stats when enabled. - const FrameStatsMap* GetFrameStatsData(); - const PacketStatsMap* GetPacketStatsData(); - const GenericStatsMap* GetGenericStatsData(); - - void Reset(); + + void InsertSinglePacketEvent(const base::TimeTicks& time_of_event, + CastLoggingEvent event, + EventMediaType event_media_type, + const Packet& packet); + + void InsertPacketListEvent(const base::TimeTicks& time_of_event, + CastLoggingEvent event, + EventMediaType event_media_type, + const PacketList& packets); + + void InsertPacketEvent(const base::TimeTicks& time_of_event, + CastLoggingEvent event, + EventMediaType event_media_type, uint32 rtp_timestamp, + uint32 frame_id, uint16 packet_id, + uint16 max_packet_id, size_t size); + + // Delegates to |LoggingRaw::AddRawEventSubscriber()|. + void AddRawEventSubscriber(RawEventSubscriber* subscriber); + + // Delegates to |LoggingRaw::RemoveRawEventSubscriber()|. + void RemoveRawEventSubscriber(RawEventSubscriber* subscriber); private: - scoped_refptr<base::TaskRunner> main_thread_proxy_; - const CastLoggingConfig config_; + base::ThreadChecker thread_checker_; LoggingRaw raw_; - LoggingStats stats_; DISALLOW_COPY_AND_ASSIGN(LoggingImpl); }; diff --git a/chromium/media/cast/logging/logging_impl_unittest.cc b/chromium/media/cast/logging/logging_impl_unittest.cc new file mode 100644 index 00000000000..712d76bae60 --- /dev/null +++ b/chromium/media/cast/logging/logging_impl_unittest.cc @@ -0,0 +1,234 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <stdint.h> + +#include <vector> + +#include "base/rand_util.h" +#include "base/test/simple_test_tick_clock.h" +#include "base/time/tick_clock.h" +#include "base/time/time.h" +#include "media/cast/logging/logging_defines.h" +#include "media/cast/logging/logging_impl.h" +#include "media/cast/logging/simple_event_subscriber.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace media { +namespace cast { + +// Insert frame duration- one second. +const int64 kIntervalTime1S = 1; +// Test frame rate goal - 30fps. +const int kFrameIntervalMs = 33; + +static const int64 kStartMillisecond = INT64_C(12345678900000); + +class LoggingImplTest : public ::testing::Test { + protected: + LoggingImplTest() { + testing_clock_.Advance( + base::TimeDelta::FromMilliseconds(kStartMillisecond)); + logging_.AddRawEventSubscriber(&event_subscriber_); + } + + virtual ~LoggingImplTest() { + logging_.RemoveRawEventSubscriber(&event_subscriber_); + } + + LoggingImpl logging_; + base::SimpleTestTickClock testing_clock_; + SimpleEventSubscriber event_subscriber_; + + DISALLOW_COPY_AND_ASSIGN(LoggingImplTest); +}; + +TEST_F(LoggingImplTest, BasicFrameLogging) { + base::TimeTicks start_time = testing_clock_.NowTicks(); + base::TimeDelta time_interval = testing_clock_.NowTicks() - start_time; + uint32 rtp_timestamp = 0; + uint32 frame_id = 0; + base::TimeTicks now; + do { + now = testing_clock_.NowTicks(); + logging_.InsertFrameEvent( + now, FRAME_CAPTURE_BEGIN, VIDEO_EVENT, rtp_timestamp, frame_id); + testing_clock_.Advance( + base::TimeDelta::FromMilliseconds(kFrameIntervalMs)); + rtp_timestamp += kFrameIntervalMs * 90; + ++frame_id; + time_interval = now - start_time; + } while (time_interval.InSeconds() < kIntervalTime1S); + + // Get logging data. + std::vector<FrameEvent> frame_events; + event_subscriber_.GetFrameEventsAndReset(&frame_events); + // Size of vector should be equal to the number of events logged, + // which equals to number of frames in this case. + EXPECT_EQ(frame_id, frame_events.size()); +} + +TEST_F(LoggingImplTest, FrameLoggingWithSize) { + // Average packet size. + const int kBaseFrameSizeBytes = 25000; + const int kRandomSizeInterval = 100; + base::TimeTicks start_time = testing_clock_.NowTicks(); + base::TimeDelta time_interval = testing_clock_.NowTicks() - start_time; + uint32 rtp_timestamp = 0; + uint32 frame_id = 0; + size_t sum_size = 0; + int target_bitrate = 1234; + do { + int size = kBaseFrameSizeBytes + + base::RandInt(-kRandomSizeInterval, kRandomSizeInterval); + sum_size += static_cast<size_t>(size); + logging_.InsertEncodedFrameEvent(testing_clock_.NowTicks(), + FRAME_ENCODED, VIDEO_EVENT, rtp_timestamp, + frame_id, size, true, target_bitrate); + testing_clock_.Advance(base::TimeDelta::FromMilliseconds(kFrameIntervalMs)); + rtp_timestamp += kFrameIntervalMs * 90; + ++frame_id; + time_interval = testing_clock_.NowTicks() - start_time; + } while (time_interval.InSeconds() < kIntervalTime1S); + // Get logging data. + std::vector<FrameEvent> frame_events; + event_subscriber_.GetFrameEventsAndReset(&frame_events); + // Size of vector should be equal to the number of events logged, which + // equals to number of frames in this case. + EXPECT_EQ(frame_id, frame_events.size()); +} + +TEST_F(LoggingImplTest, FrameLoggingWithDelay) { + // Average packet size. + const int kPlayoutDelayMs = 50; + const int kRandomSizeInterval = 20; + base::TimeTicks start_time = testing_clock_.NowTicks(); + base::TimeDelta time_interval = testing_clock_.NowTicks() - start_time; + uint32 rtp_timestamp = 0; + uint32 frame_id = 0; + do { + int delay = kPlayoutDelayMs + + base::RandInt(-kRandomSizeInterval, kRandomSizeInterval); + logging_.InsertFrameEventWithDelay( + testing_clock_.NowTicks(), + FRAME_CAPTURE_BEGIN, + VIDEO_EVENT, + rtp_timestamp, + frame_id, + base::TimeDelta::FromMilliseconds(delay)); + testing_clock_.Advance(base::TimeDelta::FromMilliseconds(kFrameIntervalMs)); + rtp_timestamp += kFrameIntervalMs * 90; + ++frame_id; + time_interval = testing_clock_.NowTicks() - start_time; + } while (time_interval.InSeconds() < kIntervalTime1S); + // Get logging data. + std::vector<FrameEvent> frame_events; + event_subscriber_.GetFrameEventsAndReset(&frame_events); + // Size of vector should be equal to the number of frames logged. + EXPECT_EQ(frame_id, frame_events.size()); +} + +TEST_F(LoggingImplTest, MultipleEventFrameLogging) { + base::TimeTicks start_time = testing_clock_.NowTicks(); + base::TimeDelta time_interval = testing_clock_.NowTicks() - start_time; + uint32 rtp_timestamp = 0u; + uint32 frame_id = 0u; + uint32 num_events = 0u; + do { + logging_.InsertFrameEvent(testing_clock_.NowTicks(), + FRAME_CAPTURE_END, + VIDEO_EVENT, + rtp_timestamp, + frame_id); + ++num_events; + if (frame_id % 2) { + logging_.InsertEncodedFrameEvent(testing_clock_.NowTicks(), + FRAME_ENCODED, AUDIO_EVENT, + rtp_timestamp, + frame_id, 1500, true, 0); + } else if (frame_id % 3) { + logging_.InsertFrameEvent(testing_clock_.NowTicks(), FRAME_DECODED, + VIDEO_EVENT, rtp_timestamp, frame_id); + } else { + logging_.InsertFrameEventWithDelay( + testing_clock_.NowTicks(), FRAME_PLAYOUT, VIDEO_EVENT, + rtp_timestamp, frame_id, base::TimeDelta::FromMilliseconds(20)); + } + ++num_events; + + testing_clock_.Advance(base::TimeDelta::FromMilliseconds(kFrameIntervalMs)); + rtp_timestamp += kFrameIntervalMs * 90; + ++frame_id; + time_interval = testing_clock_.NowTicks() - start_time; + } while (time_interval.InSeconds() < kIntervalTime1S); + // Get logging data. + std::vector<FrameEvent> frame_events; + event_subscriber_.GetFrameEventsAndReset(&frame_events); + // Size of vector should be equal to the number of frames logged. + EXPECT_EQ(num_events, frame_events.size()); + // Multiple events captured per frame. +} + +TEST_F(LoggingImplTest, PacketLogging) { + const int kNumPacketsPerFrame = 10; + const int kBaseSize = 2500; + const int kSizeInterval = 100; + base::TimeTicks start_time = testing_clock_.NowTicks(); + base::TimeTicks latest_time; + base::TimeDelta time_interval = testing_clock_.NowTicks() - start_time; + RtpTimestamp rtp_timestamp = 0; + int frame_id = 0; + int num_packets = 0; + int sum_size = 0u; + do { + for (int i = 0; i < kNumPacketsPerFrame; ++i) { + int size = kBaseSize + base::RandInt(-kSizeInterval, kSizeInterval); + sum_size += size; + latest_time = testing_clock_.NowTicks(); + ++num_packets; + logging_.InsertPacketEvent(latest_time, + PACKET_RECEIVED, + VIDEO_EVENT, + rtp_timestamp, + frame_id, + i, + kNumPacketsPerFrame, + size); + } + testing_clock_.Advance(base::TimeDelta::FromMilliseconds(kFrameIntervalMs)); + rtp_timestamp += kFrameIntervalMs * 90; + ++frame_id; + time_interval = testing_clock_.NowTicks() - start_time; + } while (time_interval.InSeconds() < kIntervalTime1S); + // Get logging data. + std::vector<PacketEvent> packet_events; + event_subscriber_.GetPacketEventsAndReset(&packet_events); + // Size of vector should be equal to the number of packets logged. + EXPECT_EQ(num_packets, static_cast<int>(packet_events.size())); +} + +TEST_F(LoggingImplTest, MultipleRawEventSubscribers) { + SimpleEventSubscriber event_subscriber_2; + + // Now logging_ has two subscribers. + logging_.AddRawEventSubscriber(&event_subscriber_2); + + logging_.InsertFrameEvent(testing_clock_.NowTicks(), + FRAME_CAPTURE_BEGIN, + VIDEO_EVENT, + /*rtp_timestamp*/ 0u, + /*frame_id*/ 0u); + + std::vector<FrameEvent> frame_events; + event_subscriber_.GetFrameEventsAndReset(&frame_events); + EXPECT_EQ(1u, frame_events.size()); + frame_events.clear(); + event_subscriber_2.GetFrameEventsAndReset(&frame_events); + EXPECT_EQ(1u, frame_events.size()); + + logging_.RemoveRawEventSubscriber(&event_subscriber_2); +} + +} // namespace cast +} // namespace media diff --git a/chromium/media/cast/logging/logging_internal.cc b/chromium/media/cast/logging/logging_internal.cc deleted file mode 100644 index ce2249ee4e0..00000000000 --- a/chromium/media/cast/logging/logging_internal.cc +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "media/cast/logging/logging_internal.h" - -namespace media { -namespace cast { - -FrameLogData::FrameLogData(base::TickClock* clock) - : clock_(clock), - frame_map_() {} - -FrameLogData::~FrameLogData() {} - -void FrameLogData::Insert(uint32 rtp_timestamp, uint32 frame_id) { - FrameEvent info; - InsertBase(rtp_timestamp, frame_id, info); -} - -void FrameLogData::InsertWithSize( - uint32 rtp_timestamp, uint32 frame_id, int size) { - FrameEvent info; - info.size = size; - InsertBase(rtp_timestamp, frame_id, info); -} - -void FrameLogData::InsertWithDelay( - uint32 rtp_timestamp, uint32 frame_id, base::TimeDelta delay) { - FrameEvent info; - info.delay_delta = delay; - InsertBase(rtp_timestamp, frame_id, info); -} - -void FrameLogData::InsertBase( - uint32 rtp_timestamp, uint32 frame_id, FrameEvent info) { - info.timestamp = clock_->NowTicks(); - info.frame_id = frame_id; - frame_map_.insert(std::make_pair(rtp_timestamp, info)); -} - -PacketLogData::PacketLogData(base::TickClock* clock) - : clock_(clock), - packet_map_() {} - -PacketLogData::~PacketLogData() {} - -void PacketLogData::Insert(uint32 rtp_timestamp, - uint32 frame_id, uint16 packet_id, uint16 max_packet_id, int size) { - PacketEvent info; - info.size = size; - info.max_packet_id = max_packet_id; - info.frame_id = frame_id; - info.timestamp = clock_->NowTicks(); - // Is this a new frame? - PacketMap::iterator it = packet_map_.find(rtp_timestamp); - if (it == packet_map_.end()) { - // New rtp_timestamp id - create base packet map. - BasePacketMap base_map; - base_map.insert(std::make_pair(packet_id, info)); - packet_map_.insert(std::make_pair(rtp_timestamp, base_map)); - } else { - // Existing rtp_timestamp. - it->second.insert(std::make_pair(packet_id, info)); - } -} - -GenericLogData::GenericLogData(base::TickClock* clock) - : clock_(clock) {} - -GenericLogData::~GenericLogData() {} - -void GenericLogData::Insert(int data) { - data_.push_back(data); - timestamp_.push_back(clock_->NowTicks()); -} - -} // namespace cast -} // namespace media diff --git a/chromium/media/cast/logging/logging_internal.h b/chromium/media/cast/logging/logging_internal.h deleted file mode 100644 index 6f028b925fe..00000000000 --- a/chromium/media/cast/logging/logging_internal.h +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef MEDIA_CAST_LOGGING_LOGGING_INTERNAL_H_ -#define MEDIA_CAST_LOGGING_LOGGING_INTERNAL_H_ - -#include <map> -#include <string> -#include <vector> - -#include "base/basictypes.h" -#include "base/time/tick_clock.h" -#include "base/time/time.h" - -namespace media { -namespace cast { - -// TODO(mikhal): Consider storing only the delta time and not absolute time. -struct FrameEvent { - uint32 frame_id; - int size; - base::TimeTicks timestamp; - base::TimeDelta delay_delta; // render/playout delay. -}; - -struct PacketEvent { - uint32 frame_id; - int max_packet_id; - size_t size; - base::TimeTicks timestamp; -}; - -// Frame and packet maps are sorted based on the rtp_timestamp. -typedef std::map<uint32, FrameEvent> FrameMap; -typedef std::map<uint16, PacketEvent> BasePacketMap; -typedef std::map<uint32, BasePacketMap> PacketMap; - -class FrameLogData { - public: - explicit FrameLogData(base::TickClock* clock); - ~FrameLogData(); - void Insert(uint32 rtp_timestamp, uint32 frame_id); - // Include size for encoded images (compute bitrate), - void InsertWithSize(uint32 rtp_timestamp, uint32 frame_id, int size); - // Include playout/render delay info. - void InsertWithDelay( - uint32 rtp_timestamp, uint32 frame_id, base::TimeDelta delay); - void Reset(); - - private: - void InsertBase(uint32 rtp_timestamp, uint32 frame_id, FrameEvent info); - - base::TickClock* const clock_; // Not owned by this class. - FrameMap frame_map_; - - DISALLOW_COPY_AND_ASSIGN(FrameLogData); -}; - -// TODO(mikhal): Should be able to handle packet bursts. -class PacketLogData { - public: - explicit PacketLogData(base::TickClock* clock); - ~PacketLogData(); - void Insert(uint32 rtp_timestamp, uint32 frame_id, uint16 packet_id, - uint16 max_packet_id, int size); - void Reset(); - - private: - base::TickClock* const clock_; // Not owned by this class. - PacketMap packet_map_; - - DISALLOW_COPY_AND_ASSIGN(PacketLogData); -}; - -class GenericLogData { - public: - explicit GenericLogData(base::TickClock* clock); - ~GenericLogData(); - void Insert(int value); - void Reset(); - - private: - base::TickClock* const clock_; // Not owned by this class. - std::vector<int> data_; - std::vector<base::TimeTicks> timestamp_; - - DISALLOW_COPY_AND_ASSIGN(GenericLogData); -}; - - -} // namespace cast -} // namespace media - -#endif // MEDIA_CAST_LOGGING_LOGGING_INTERNAL_H_ diff --git a/chromium/media/cast/logging/logging_raw.cc b/chromium/media/cast/logging/logging_raw.cc index 6a389617f62..229064d7b69 100644 --- a/chromium/media/cast/logging/logging_raw.cc +++ b/chromium/media/cast/logging/logging_raw.cc @@ -4,139 +4,110 @@ #include "media/cast/logging/logging_raw.h" +#include <algorithm> + #include "base/logging.h" -#include "base/metrics/histogram.h" #include "base/time/time.h" namespace media { namespace cast { -LoggingRaw::LoggingRaw(base::TickClock* clock) - : clock_(clock), - frame_map_(), - packet_map_(), - generic_map_(), - weak_factory_(this) {} +LoggingRaw::LoggingRaw() {} LoggingRaw::~LoggingRaw() {} -void LoggingRaw::InsertFrameEvent(CastLoggingEvent event, +void LoggingRaw::InsertFrameEvent(const base::TimeTicks& time_of_event, + CastLoggingEvent event, + EventMediaType event_media_type, uint32 rtp_timestamp, uint32 frame_id) { - InsertBaseFrameEvent(event, frame_id, rtp_timestamp); + InsertBaseFrameEvent(time_of_event, event, event_media_type, frame_id, + rtp_timestamp, base::TimeDelta(), 0, false, 0); } -void LoggingRaw::InsertFrameEventWithSize(CastLoggingEvent event, - uint32 rtp_timestamp, - uint32 frame_id, - int size) { - InsertBaseFrameEvent(event, frame_id, rtp_timestamp); - // Now insert size. - FrameRawMap::iterator it = frame_map_.find(rtp_timestamp); - DCHECK(it != frame_map_.end()); - it->second.size = size; +void LoggingRaw::InsertEncodedFrameEvent(const base::TimeTicks& time_of_event, + CastLoggingEvent event, + EventMediaType event_media_type, + uint32 rtp_timestamp, uint32 frame_id, + int size, bool key_frame, + int target_bitrate) { + InsertBaseFrameEvent(time_of_event, event, event_media_type, + frame_id, rtp_timestamp, base::TimeDelta(), size, + key_frame, target_bitrate); } -void LoggingRaw::InsertFrameEventWithDelay(CastLoggingEvent event, +void LoggingRaw::InsertFrameEventWithDelay(const base::TimeTicks& time_of_event, + CastLoggingEvent event, + EventMediaType event_media_type, uint32 rtp_timestamp, uint32 frame_id, base::TimeDelta delay) { - InsertBaseFrameEvent(event, frame_id, rtp_timestamp); - // Now insert delay. - FrameRawMap::iterator it = frame_map_.find(rtp_timestamp); - DCHECK(it != frame_map_.end()); - it->second.delay_delta = delay; + InsertBaseFrameEvent(time_of_event, event, event_media_type, frame_id, + rtp_timestamp, delay, 0, false, 0); } -void LoggingRaw::InsertBaseFrameEvent(CastLoggingEvent event, +void LoggingRaw::InsertBaseFrameEvent(const base::TimeTicks& time_of_event, + CastLoggingEvent event, + EventMediaType event_media_type, uint32 frame_id, - uint32 rtp_timestamp) { - // Is this a new event? - FrameRawMap::iterator it = frame_map_.find(rtp_timestamp); - if (it == frame_map_.end()) { - // Create a new map entry. - FrameEvent info; - info.frame_id = frame_id; - info.timestamp.push_back(clock_->NowTicks()); - info.type.push_back(event); - frame_map_.insert(std::make_pair(rtp_timestamp, info)); - } else { - // Insert to an existing entry. - it->second.timestamp.push_back(clock_->NowTicks()); - it->second.type.push_back(event); - // Do we have a valid frame_id? - // Not all events have a valid frame id. - if (it->second.frame_id == kFrameIdUnknown && frame_id != kFrameIdUnknown) - it->second.frame_id = frame_id; + uint32 rtp_timestamp, + base::TimeDelta delay, int size, + bool key_frame, int target_bitrate) { + FrameEvent frame_event; + frame_event.rtp_timestamp = rtp_timestamp; + frame_event.frame_id = frame_id; + frame_event.size = size; + frame_event.timestamp = time_of_event; + frame_event.type = event; + frame_event.media_type = event_media_type; + frame_event.delay_delta = delay; + frame_event.key_frame = key_frame; + frame_event.target_bitrate = target_bitrate; + for (std::vector<RawEventSubscriber*>::const_iterator it = + subscribers_.begin(); + it != subscribers_.end(); ++it) { + (*it)->OnReceiveFrameEvent(frame_event); } } -void LoggingRaw::InsertPacketEvent(CastLoggingEvent event, +void LoggingRaw::InsertPacketEvent(const base::TimeTicks& time_of_event, + CastLoggingEvent event, + EventMediaType event_media_type, uint32 rtp_timestamp, - uint32 frame_id, - uint16 packet_id, - uint16 max_packet_id, - size_t size) { - // Is this packet belonging to a new frame? - PacketRawMap::iterator it = packet_map_.find(rtp_timestamp); - if (it == packet_map_.end()) { - // Create a new entry - start with base packet map. - PacketEvent info; - info.frame_id = frame_id; - info.max_packet_id = max_packet_id; - BasePacketInfo base_info; - base_info.size = size; - base_info.timestamp.push_back(clock_->NowTicks()); - base_info.type.push_back(event); - packet_map_.insert(std::make_pair(rtp_timestamp, info)); - } else { - // Is this a new packet? - BasePacketMap::iterator packet_it = it->second.packet_map.find(packet_id); - if (packet_it == it->second.packet_map.end()) { - BasePacketInfo base_info; - base_info.size = size; - base_info.timestamp.push_back(clock_->NowTicks()); - base_info.type.push_back(event); - it->second.packet_map.insert(std::make_pair(packet_id, base_info)); - } else { - packet_it->second.timestamp.push_back(clock_->NowTicks()); - packet_it->second.type.push_back(event); - } - } -} - -void LoggingRaw::InsertGenericEvent(CastLoggingEvent event, int value) { - GenericEvent event_data; - event_data.value.push_back(value); - event_data.timestamp.push_back(clock_->NowTicks()); - // Is this a new event? - GenericRawMap::iterator it = generic_map_.find(event); - if (it == generic_map_.end()) { - // Create new entry. - generic_map_.insert(std::make_pair(event, event_data)); - } else { - // Insert to existing entry. - it->second.value.push_back(value); - it->second.timestamp.push_back(clock_->NowTicks()); + uint32 frame_id, uint16 packet_id, + uint16 max_packet_id, size_t size) { + PacketEvent packet_event; + packet_event.rtp_timestamp = rtp_timestamp; + packet_event.frame_id = frame_id; + packet_event.max_packet_id = max_packet_id; + packet_event.packet_id = packet_id; + packet_event.size = size; + packet_event.timestamp = time_of_event; + packet_event.type = event; + packet_event.media_type = event_media_type; + for (std::vector<RawEventSubscriber*>::const_iterator it = + subscribers_.begin(); + it != subscribers_.end(); ++it) { + (*it)->OnReceivePacketEvent(packet_event); } } -FrameRawMap LoggingRaw::GetFrameData() const { - return frame_map_; -} +void LoggingRaw::AddSubscriber(RawEventSubscriber* subscriber) { + DCHECK(subscriber); + DCHECK(std::find(subscribers_.begin(), subscribers_.end(), subscriber) == + subscribers_.end()); -PacketRawMap LoggingRaw::GetPacketData() const { - return packet_map_; + subscribers_.push_back(subscriber); } -GenericRawMap LoggingRaw::GetGenericData() const { - return generic_map_; -} +void LoggingRaw::RemoveSubscriber(RawEventSubscriber* subscriber) { + DCHECK(subscriber); + DCHECK(std::find(subscribers_.begin(), subscribers_.end(), subscriber) != + subscribers_.end()); -void LoggingRaw::Reset() { - frame_map_.clear(); - packet_map_.clear(); - generic_map_.clear(); + subscribers_.erase( + std::remove(subscribers_.begin(), subscribers_.end(), subscriber), + subscribers_.end()); } } // namespace cast diff --git a/chromium/media/cast/logging/logging_raw.h b/chromium/media/cast/logging/logging_raw.h index 4ac8d0fb7ad..8ed4a599602 100644 --- a/chromium/media/cast/logging/logging_raw.h +++ b/chromium/media/cast/logging/logging_raw.h @@ -5,75 +5,80 @@ #ifndef MEDIA_CAST_LOGGING_LOGGING_RAW_H_ #define MEDIA_CAST_LOGGING_LOGGING_RAW_H_ -#include <map> -#include <string> #include <vector> #include "base/basictypes.h" #include "base/memory/linked_ptr.h" -#include "base/memory/weak_ptr.h" #include "base/threading/non_thread_safe.h" #include "base/time/tick_clock.h" #include "media/cast/logging/logging_defines.h" +#include "media/cast/logging/raw_event_subscriber.h" namespace media { namespace cast { // This class is not thread safe, and should only be called from the main // thread. -class LoggingRaw : public base::NonThreadSafe, - public base::SupportsWeakPtr<LoggingRaw> { +class LoggingRaw : public base::NonThreadSafe { public: - explicit LoggingRaw(base::TickClock* clock); + LoggingRaw(); ~LoggingRaw(); - // Inform of new event: three types of events: frame, packets and generic. + // Inform of new event: two types of events: frame and packet. // Frame events can be inserted with different parameters. - void InsertFrameEvent(CastLoggingEvent event, - uint32 rtp_timestamp, - uint32 frame_id); - - // Size - Inserting the size implies that this is an encoded frame. - void InsertFrameEventWithSize(CastLoggingEvent event, - uint32 rtp_timestamp, - uint32 frame_id, - int frame_size); + void InsertFrameEvent(const base::TimeTicks& time_of_event, + CastLoggingEvent event, EventMediaType event_media_type, + uint32 rtp_timestamp, uint32 frame_id); + + // This function is only applicable for FRAME_ENCODED event. + // |size| - Size of encoded frame. + // |key_frame| - Whether the frame is a key frame. This field is only + // applicable for video event. + // |target_bitrate| - The target bitrate of the encoder the time the frame + // was encoded. Only applicable for video event. + void InsertEncodedFrameEvent(const base::TimeTicks& time_of_event, + CastLoggingEvent event, + EventMediaType event_media_type, + uint32 rtp_timestamp, uint32 frame_id, + int size, bool key_frame, + int target_bitrate); // Render/playout delay - void InsertFrameEventWithDelay(CastLoggingEvent event, + // This function is only applicable for FRAME_PLAYOUT event. + void InsertFrameEventWithDelay(const base::TimeTicks& time_of_event, + CastLoggingEvent event, + EventMediaType event_media_type, uint32 rtp_timestamp, - uint32 frame_id, - base::TimeDelta delay); + uint32 frame_id, base::TimeDelta delay); // Insert a packet event. - void InsertPacketEvent(CastLoggingEvent event, - uint32 rtp_timestamp, - uint32 frame_id, - uint16 packet_id, - uint16 max_packet_id, - size_t size); - - void InsertGenericEvent(CastLoggingEvent event, int value); - - // Get raw log data. - FrameRawMap GetFrameData() const; - PacketRawMap GetPacketData() const; - GenericRawMap GetGenericData() const; - - - // Reset all log data. - void Reset(); + void InsertPacketEvent(const base::TimeTicks& time_of_event, + CastLoggingEvent event, + EventMediaType event_media_type, uint32 rtp_timestamp, + uint32 frame_id, uint16 packet_id, + uint16 max_packet_id, size_t size); + + // Adds |subscriber| so that it will start receiving events on main thread. + // Note that this class does not own |subscriber|. + // It is a no-op to add a subscriber that already exists. + void AddSubscriber(RawEventSubscriber* subscriber); + + // Removes |subscriber| so that it will stop receiving events. + // Note that this class does NOT own the subscribers. This function MUST be + // called before |subscriber| is destroyed if it was previously added. + // It is a no-op to remove a subscriber that doesn't exist. + void RemoveSubscriber(RawEventSubscriber* subscriber); private: - void InsertBaseFrameEvent(CastLoggingEvent event, - uint32 frame_id, - uint32 rtp_timestamp); + void InsertBaseFrameEvent(const base::TimeTicks& time_of_event, + CastLoggingEvent event, + EventMediaType event_media_type, + uint32 frame_id, uint32 rtp_timestamp, + base::TimeDelta delay, int size, bool key_frame, + int target_bitrate); - base::TickClock* const clock_; // Not owned by this class. - FrameRawMap frame_map_; - PacketRawMap packet_map_; - GenericRawMap generic_map_; - base::WeakPtrFactory<LoggingRaw> weak_factory_; + // List of subscriber pointers. This class does not own the subscribers. + std::vector<RawEventSubscriber*> subscribers_; DISALLOW_COPY_AND_ASSIGN(LoggingRaw); }; @@ -82,4 +87,3 @@ class LoggingRaw : public base::NonThreadSafe, } // namespace media #endif // MEDIA_CAST_LOGGING_LOGGING_RAW_H_ - diff --git a/chromium/media/cast/logging/logging_raw_unittest.cc b/chromium/media/cast/logging/logging_raw_unittest.cc new file mode 100644 index 00000000000..0b7c05aaac1 --- /dev/null +++ b/chromium/media/cast/logging/logging_raw_unittest.cc @@ -0,0 +1,196 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/cast/logging/logging_defines.h" +#include "media/cast/logging/logging_raw.h" +#include "media/cast/logging/simple_event_subscriber.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace media { +namespace cast { + +class LoggingRawTest : public ::testing::Test { + protected: + LoggingRawTest() { + raw_.AddSubscriber(&event_subscriber_); + } + + virtual ~LoggingRawTest() { raw_.RemoveSubscriber(&event_subscriber_); } + + LoggingRaw raw_; + SimpleEventSubscriber event_subscriber_; + std::vector<FrameEvent> frame_events_; + std::vector<PacketEvent> packet_events_; +}; + +TEST_F(LoggingRawTest, FrameEvent) { + CastLoggingEvent event_type = FRAME_DECODED; + EventMediaType media_type = VIDEO_EVENT; + uint32 frame_id = 456u; + RtpTimestamp rtp_timestamp = 123u; + base::TimeTicks timestamp = base::TimeTicks(); + raw_.InsertFrameEvent(timestamp, event_type, media_type, + rtp_timestamp, frame_id); + + event_subscriber_.GetPacketEventsAndReset(&packet_events_); + EXPECT_TRUE(packet_events_.empty()); + + event_subscriber_.GetFrameEventsAndReset(&frame_events_); + ASSERT_EQ(1u, frame_events_.size()); + EXPECT_EQ(rtp_timestamp, frame_events_[0].rtp_timestamp); + EXPECT_EQ(frame_id, frame_events_[0].frame_id); + EXPECT_EQ(0u, frame_events_[0].size); + EXPECT_EQ(timestamp, frame_events_[0].timestamp); + EXPECT_EQ(event_type, frame_events_[0].type); + EXPECT_EQ(media_type, frame_events_[0].media_type); + EXPECT_EQ(base::TimeDelta(), frame_events_[0].delay_delta); +} + +TEST_F(LoggingRawTest, EncodedFrameEvent) { + CastLoggingEvent event_type = FRAME_ENCODED; + EventMediaType media_type = VIDEO_EVENT; + uint32 frame_id = 456u; + RtpTimestamp rtp_timestamp = 123u; + base::TimeTicks timestamp = base::TimeTicks(); + int size = 1024; + bool key_frame = true; + int target_bitrate = 4096; + raw_.InsertEncodedFrameEvent(timestamp, event_type, media_type, + rtp_timestamp, frame_id, size, key_frame, target_bitrate); + + event_subscriber_.GetPacketEventsAndReset(&packet_events_); + EXPECT_TRUE(packet_events_.empty()); + + event_subscriber_.GetFrameEventsAndReset(&frame_events_); + ASSERT_EQ(1u, frame_events_.size()); + EXPECT_EQ(rtp_timestamp, frame_events_[0].rtp_timestamp); + EXPECT_EQ(frame_id, frame_events_[0].frame_id); + EXPECT_EQ(size, static_cast<int>(frame_events_[0].size)); + EXPECT_EQ(timestamp, frame_events_[0].timestamp); + EXPECT_EQ(event_type, frame_events_[0].type); + EXPECT_EQ(media_type, frame_events_[0].media_type); + EXPECT_EQ(base::TimeDelta(), frame_events_[0].delay_delta); + EXPECT_EQ(key_frame, frame_events_[0].key_frame); + EXPECT_EQ(target_bitrate, frame_events_[0].target_bitrate); +} + +TEST_F(LoggingRawTest, FrameEventWithDelay) { + CastLoggingEvent event_type = FRAME_PLAYOUT; + EventMediaType media_type = VIDEO_EVENT; + uint32 frame_id = 456u; + RtpTimestamp rtp_timestamp = 123u; + base::TimeTicks timestamp = base::TimeTicks(); + base::TimeDelta delay = base::TimeDelta::FromMilliseconds(20); + raw_.InsertFrameEventWithDelay(timestamp, event_type, media_type, + rtp_timestamp, frame_id, delay); + + event_subscriber_.GetPacketEventsAndReset(&packet_events_); + EXPECT_TRUE(packet_events_.empty()); + + event_subscriber_.GetFrameEventsAndReset(&frame_events_); + ASSERT_EQ(1u, frame_events_.size()); + EXPECT_EQ(rtp_timestamp, frame_events_[0].rtp_timestamp); + EXPECT_EQ(frame_id, frame_events_[0].frame_id); + EXPECT_EQ(0u, frame_events_[0].size); + EXPECT_EQ(timestamp, frame_events_[0].timestamp); + EXPECT_EQ(event_type, frame_events_[0].type); + EXPECT_EQ(media_type, frame_events_[0].media_type); + EXPECT_EQ(delay, frame_events_[0].delay_delta); +} + +TEST_F(LoggingRawTest, PacketEvent) { + CastLoggingEvent event_type = PACKET_RECEIVED; + EventMediaType media_type = VIDEO_EVENT; + uint32 frame_id = 456u; + uint16 packet_id = 1u; + uint16 max_packet_id = 10u; + RtpTimestamp rtp_timestamp = 123u; + base::TimeTicks timestamp = base::TimeTicks(); + size_t size = 1024u; + raw_.InsertPacketEvent(timestamp, event_type, media_type, + rtp_timestamp, frame_id, packet_id, max_packet_id, size); + + event_subscriber_.GetFrameEventsAndReset(&frame_events_); + EXPECT_TRUE(frame_events_.empty()); + + event_subscriber_.GetPacketEventsAndReset(&packet_events_); + ASSERT_EQ(1u, packet_events_.size()); + + EXPECT_EQ(rtp_timestamp, packet_events_[0].rtp_timestamp); + EXPECT_EQ(frame_id, packet_events_[0].frame_id); + EXPECT_EQ(max_packet_id, packet_events_[0].max_packet_id); + EXPECT_EQ(packet_id, packet_events_[0].packet_id); + EXPECT_EQ(size, packet_events_[0].size); + EXPECT_EQ(timestamp, packet_events_[0].timestamp); + EXPECT_EQ(event_type, packet_events_[0].type); + EXPECT_EQ(media_type, packet_events_[0].media_type); +} + +TEST_F(LoggingRawTest, MultipleSubscribers) { + SimpleEventSubscriber event_subscriber_2; + + // Now raw_ has two subscribers. + raw_.AddSubscriber(&event_subscriber_2); + + CastLoggingEvent event_type = FRAME_DECODED; + EventMediaType media_type = VIDEO_EVENT; + uint32 frame_id = 456u; + RtpTimestamp rtp_timestamp = 123u; + base::TimeTicks timestamp = base::TimeTicks(); + raw_.InsertFrameEvent(timestamp, event_type, media_type, + rtp_timestamp, frame_id); + + event_subscriber_.GetPacketEventsAndReset(&packet_events_); + EXPECT_TRUE(packet_events_.empty()); + + event_subscriber_.GetFrameEventsAndReset(&frame_events_); + ASSERT_EQ(1u, frame_events_.size()); + EXPECT_EQ(rtp_timestamp, frame_events_[0].rtp_timestamp); + EXPECT_EQ(frame_id, frame_events_[0].frame_id); + EXPECT_EQ(0u, frame_events_[0].size); + EXPECT_EQ(timestamp, frame_events_[0].timestamp); + EXPECT_EQ(event_type, frame_events_[0].type); + EXPECT_EQ(media_type, frame_events_[0].media_type); + EXPECT_EQ(base::TimeDelta(), frame_events_[0].delay_delta); + + event_subscriber_2.GetPacketEventsAndReset(&packet_events_); + EXPECT_TRUE(packet_events_.empty()); + + event_subscriber_2.GetFrameEventsAndReset(&frame_events_); + ASSERT_EQ(1u, frame_events_.size()); + EXPECT_EQ(rtp_timestamp, frame_events_[0].rtp_timestamp); + EXPECT_EQ(frame_id, frame_events_[0].frame_id); + EXPECT_EQ(0u, frame_events_[0].size); + EXPECT_EQ(timestamp, frame_events_[0].timestamp); + EXPECT_EQ(event_type, frame_events_[0].type); + EXPECT_EQ(media_type, frame_events_[0].media_type); + EXPECT_EQ(base::TimeDelta(), frame_events_[0].delay_delta); + + // Remove event_subscriber_2, so it shouldn't receive events after this. + raw_.RemoveSubscriber(&event_subscriber_2); + + media_type = AUDIO_EVENT; + frame_id = 789; + rtp_timestamp = 456; + timestamp = base::TimeTicks(); + raw_.InsertFrameEvent(timestamp, event_type, media_type, + rtp_timestamp, frame_id); + + // |event_subscriber_| should still receive events. + event_subscriber_.GetFrameEventsAndReset(&frame_events_); + ASSERT_EQ(1u, frame_events_.size()); + EXPECT_EQ(rtp_timestamp, frame_events_[0].rtp_timestamp); + EXPECT_EQ(frame_id, frame_events_[0].frame_id); + EXPECT_EQ(0u, frame_events_[0].size); + EXPECT_EQ(timestamp, frame_events_[0].timestamp); + EXPECT_EQ(event_type, frame_events_[0].type); + EXPECT_EQ(media_type, frame_events_[0].media_type); + EXPECT_EQ(base::TimeDelta(), frame_events_[0].delay_delta); + + event_subscriber_2.GetFrameEventsAndReset(&frame_events_); + EXPECT_TRUE(frame_events_.empty()); +} + +} // namespace cast +} // namespace media diff --git a/chromium/media/cast/logging/logging_stats.cc b/chromium/media/cast/logging/logging_stats.cc deleted file mode 100644 index 84fdbf7a615..00000000000 --- a/chromium/media/cast/logging/logging_stats.cc +++ /dev/null @@ -1,150 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "base/memory/linked_ptr.h" -#include "base/memory/scoped_ptr.h" -#include "media/cast/logging/logging_stats.h" - -namespace media { -namespace cast { - -LoggingStats::LoggingStats(base::TickClock* clock) - : frame_stats_(), - packet_stats_(), - generic_stats_(), - start_time_(), - clock_(clock) { - memset(counts_, 0, sizeof(counts_)); - memset(start_time_, 0, sizeof(start_time_)); -} - -LoggingStats::~LoggingStats() {} - -void LoggingStats::Reset() { - frame_stats_.clear(); - packet_stats_.clear(); - generic_stats_.clear(); - memset(counts_, 0, sizeof(counts_)); -} - -void LoggingStats::InsertFrameEvent(CastLoggingEvent event, - uint32 rtp_timestamp, - uint32 frame_id) { - InsertBaseFrameEvent(event, frame_id, rtp_timestamp); -} - -void LoggingStats::InsertFrameEventWithSize(CastLoggingEvent event, - uint32 rtp_timestamp, - uint32 frame_id, - int frame_size) { - InsertBaseFrameEvent(event, frame_id, rtp_timestamp); - // Update size. - FrameStatsMap::iterator it = frame_stats_.find(event); - DCHECK(it != frame_stats_.end()); - it->second->bitrate_kbps += frame_size; -} - -void LoggingStats::InsertFrameEventWithDelay(CastLoggingEvent event, - uint32 rtp_timestamp, - uint32 frame_id, - base::TimeDelta delay) { - InsertBaseFrameEvent(event, frame_id, rtp_timestamp); - // Update size. - FrameStatsMap::iterator it = frame_stats_.find(event); - DCHECK(it != frame_stats_.end()); - // Using the average delay as a counter, will divide by the counter when - // triggered. - it->second->avg_delay_ms += delay.InMilliseconds(); - if (delay.InMilliseconds() > it->second->max_delay_ms) - it->second->max_delay_ms = delay.InMilliseconds(); - if ((delay.InMilliseconds() < it->second->min_delay_ms) || - (counts_[event] == 1) ) - it->second->min_delay_ms = delay.InMilliseconds(); -} - -void LoggingStats::InsertBaseFrameEvent(CastLoggingEvent event, - uint32 frame_id, - uint32 rtp_timestamp) { - // Does this belong to an existing event? - FrameStatsMap::iterator it = frame_stats_.find(event); - if (it == frame_stats_.end()) { - // New event. - start_time_[event] = clock_->NowTicks(); - linked_ptr<FrameLogStats> stats(new FrameLogStats()); - frame_stats_.insert(std::make_pair(event, stats)); - } - - ++counts_[event]; -} - -void LoggingStats::InsertPacketEvent(CastLoggingEvent event, - uint32 rtp_timestamp, - uint32 frame_id, - uint16 packet_id, - uint16 max_packet_id, - size_t size) { - // Does this packet belong to an existing event? - PacketStatsMap::iterator it = packet_stats_.find(event); - if (it == packet_stats_.end()) { - // New event. - start_time_[event] = clock_->NowTicks(); - packet_stats_.insert(std::make_pair(event, size)); - } else { - // Add to existing. - it->second += size; - } - ++counts_[event]; -} - -void LoggingStats::InsertGenericEvent(CastLoggingEvent event, int value) { - // Does this event belong to an existing event? - GenericStatsMap::iterator it = generic_stats_.find(event); - if (it == generic_stats_.end()) { - // New event. - start_time_[event] = clock_->NowTicks(); - generic_stats_.insert(std::make_pair(event, value)); - } else { - // Add to existing (will be used to compute average). - it->second += value; - } - ++counts_[event]; -} - -const FrameStatsMap* LoggingStats::GetFrameStatsData() { - // Compute framerate and bitrate (when available). - FrameStatsMap::iterator it; - for (it = frame_stats_.begin(); it != frame_stats_.end(); ++it) { - base::TimeDelta time_diff = clock_->NowTicks() - start_time_[it->first]; - it->second->framerate_fps = counts_[it->first] / time_diff.InSecondsF(); - if (it->second->bitrate_kbps > 0) { - it->second->bitrate_kbps = (8 / 1000) * - it->second->bitrate_kbps / time_diff.InSecondsF(); - } - if (it->second->avg_delay_ms > 0) - it->second->avg_delay_ms /= counts_[it->first]; - } - return &frame_stats_; -} - -const PacketStatsMap* LoggingStats::GetPacketStatsData() { - PacketStatsMap::iterator it; - for (it = packet_stats_.begin(); it != packet_stats_.end(); ++it) { - if (counts_[it->first] == 0) continue; - base::TimeDelta time_diff = clock_->NowTicks() - start_time_[it->first]; - it->second = (8 / 1000) * it->second / time_diff.InSecondsF(); - } - return &packet_stats_; -} - -const GenericStatsMap* LoggingStats::GetGenericStatsData() { - // Compute averages. - GenericStatsMap::iterator it; - for (it = generic_stats_.begin(); it != generic_stats_.end(); ++it) { - it->second /= counts_[ it->first]; - } - return &generic_stats_; -} - -} // namespace cast -} // namespace media diff --git a/chromium/media/cast/logging/logging_stats.h b/chromium/media/cast/logging/logging_stats.h deleted file mode 100644 index f08649cc777..00000000000 --- a/chromium/media/cast/logging/logging_stats.h +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef MEDIA_CAST_LOGGING_LOGGING_STATS_H_ -#define MEDIA_CAST_LOGGING_LOGGING_STATS_H_ - -#include "base/basictypes.h" -#include "base/time/tick_clock.h" -#include "base/time/time.h" -#include "media/cast/logging/logging_defines.h" - -namespace media { -namespace cast { - -class LoggingStats { - public: - explicit LoggingStats(base::TickClock* clock); - - ~LoggingStats(); - - void Reset(); - - void InsertFrameEvent(CastLoggingEvent event, - uint32 rtp_timestamp, - uint32 frame_id); - - void InsertFrameEventWithSize(CastLoggingEvent event, - uint32 rtp_timestamp, - uint32 frame_id, - int frame_size); - - void InsertFrameEventWithDelay(CastLoggingEvent event, - uint32 rtp_timestamp, - uint32 frame_id, - base::TimeDelta delay); - - void InsertPacketEvent(CastLoggingEvent event, - uint32 rtp_timestamp, - uint32 frame_id, - uint16 packet_id, - uint16 max_packet_id, - size_t size); - - void InsertGenericEvent(CastLoggingEvent event, int value); - - // Get log stats: some of the values, such as frame rate and bit rates are - // computed at the time of the call. - const FrameStatsMap* GetFrameStatsData(); - - const PacketStatsMap* GetPacketStatsData(); - - const GenericStatsMap* GetGenericStatsData(); - - private: - void InsertBaseFrameEvent(CastLoggingEvent event, - uint32 frame_id, - uint32 rtp_timestamp); - FrameStatsMap frame_stats_; - PacketStatsMap packet_stats_; - GenericStatsMap generic_stats_; - // Every event has an individual start time - base::TimeTicks start_time_[kNumOfLoggingEvents]; - // Keep track of event counts. - int counts_[kNumOfLoggingEvents]; - base::TickClock* const clock_; // Not owned by this class. - - DISALLOW_COPY_AND_ASSIGN(LoggingStats); - }; - -} // namespace cast -} // namespace media - -#endif // MEDIA_CAST_LOGGING_LOGGING_STATS_H_ - diff --git a/chromium/media/cast/logging/logging_unittest.cc b/chromium/media/cast/logging/logging_unittest.cc deleted file mode 100644 index 5ce760ec4c7..00000000000 --- a/chromium/media/cast/logging/logging_unittest.cc +++ /dev/null @@ -1,248 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include <gtest/gtest.h> - -#include "base/rand_util.h" -#include "base/test/simple_test_tick_clock.h" -#include "base/time/tick_clock.h" -#include "base/time/time.h" -#include "media/cast/logging/logging_impl.h" - - -namespace media { -namespace cast { - - // Insert frame duration- one second. -const int64 kIntervalTime1S = 1; -// Test frame rate goal - 30fps. -const int kFrameIntervalMs = 33; - -static const int64 kStartMillisecond = GG_INT64_C(12345678900000); - -class TestLogging : public ::testing::Test { - protected: - TestLogging() - // Enable logging, disable tracing and uma. - : logging_(&testing_clock_, true, false, false) { - testing_clock_.Advance( - base::TimeDelta::FromMilliseconds(kStartMillisecond)); - } - - virtual ~TestLogging() {} - - LoggingImpl logging_; - base::SimpleTestTickClock testing_clock_; -}; - -TEST_F(TestLogging, BasicFrameLogging) { - base::TimeTicks start_time = testing_clock_.NowTicks(); - base::TimeDelta time_interval = testing_clock_.NowTicks() - start_time; - uint32 rtp_timestamp = 0; - uint32 frame_id = 0; - do { - logging_.InsertFrameEvent(kAudioFrameCaptured, rtp_timestamp, frame_id); - testing_clock_.Advance( - base::TimeDelta::FromMilliseconds(kFrameIntervalMs)); - rtp_timestamp += kFrameIntervalMs * 90; - ++frame_id; - time_interval = testing_clock_.NowTicks() - start_time; - } while (time_interval.InSeconds() < kIntervalTime1S); - // Get logging data. - FrameRawMap frame_map = logging_.GetFrameRawData(); - // Size of map should be equal to the number of frames logged. - EXPECT_EQ(frame_id, frame_map.size()); - // Verify stats. - const FrameStatsMap* frame_stats = logging_.GetFrameStatsData(); - // Size of stats equals the number of events. - EXPECT_EQ(1u, frame_stats->size()); - FrameStatsMap::const_iterator it = frame_stats->find(kAudioFrameCaptured); - EXPECT_TRUE(it != frame_stats->end()); - EXPECT_NEAR(30.3, it->second->framerate_fps, 0.1); - EXPECT_EQ(0, it->second->bitrate_kbps); - EXPECT_EQ(0, it->second->max_delay_ms); - EXPECT_EQ(0, it->second->min_delay_ms); - EXPECT_EQ(0, it->second->avg_delay_ms); -} - -TEST_F(TestLogging, FrameLoggingWithSize) { - // Average packet size. - const int kBaseFrameSizeBytes = 25000; - const int kRandomSizeInterval = 100; - base::TimeTicks start_time = testing_clock_.NowTicks(); - base::TimeDelta time_interval = testing_clock_.NowTicks() - start_time; - uint32 rtp_timestamp = 0; - uint32 frame_id = 0; - do { - int size = kBaseFrameSizeBytes + - base::RandInt(-kRandomSizeInterval, kRandomSizeInterval); - logging_.InsertFrameEventWithSize( - kAudioFrameCaptured, rtp_timestamp, frame_id, size); - testing_clock_.Advance( - base::TimeDelta::FromMilliseconds(kFrameIntervalMs)); - rtp_timestamp += kFrameIntervalMs * 90; - ++frame_id; - time_interval = testing_clock_.NowTicks() - start_time; - } while (time_interval.InSeconds() < kIntervalTime1S); - // Get logging data. - FrameRawMap frame_map = logging_.GetFrameRawData(); - // Size of map should be equal to the number of frames logged. - EXPECT_EQ(frame_id, frame_map.size()); - // Verify stats. - const FrameStatsMap* frame_stats = logging_.GetFrameStatsData(); - // Size of stats equals the number of events. - EXPECT_EQ(1u, frame_stats->size()); - FrameStatsMap::const_iterator it = frame_stats->find(kAudioFrameCaptured); - EXPECT_TRUE(it != frame_stats->end()); - EXPECT_NEAR(30.3, it->second->framerate_fps, 0.1); - EXPECT_NEAR(8 * kBaseFrameSizeBytes / (kFrameIntervalMs * 1000), - it->second->bitrate_kbps, kRandomSizeInterval); - EXPECT_EQ(0, it->second->max_delay_ms); - EXPECT_EQ(0, it->second->min_delay_ms); - EXPECT_EQ(0, it->second->avg_delay_ms); -} - -TEST_F(TestLogging, FrameLoggingWithDelay) { - // Average packet size. - const int kPlayoutDelayMs = 50; - const int kRandomSizeInterval = 20; - base::TimeTicks start_time = testing_clock_.NowTicks(); - base::TimeDelta time_interval = testing_clock_.NowTicks() - start_time; - uint32 rtp_timestamp = 0; - uint32 frame_id = 0; - do { - int delay = kPlayoutDelayMs + - base::RandInt(-kRandomSizeInterval, kRandomSizeInterval); - logging_.InsertFrameEventWithDelay( - kAudioFrameCaptured, rtp_timestamp, frame_id, - base::TimeDelta::FromMilliseconds(delay)); - testing_clock_.Advance( - base::TimeDelta::FromMilliseconds(kFrameIntervalMs)); - rtp_timestamp += kFrameIntervalMs * 90; - ++frame_id; - time_interval = testing_clock_.NowTicks() - start_time; - } while (time_interval.InSeconds() < kIntervalTime1S); - // Get logging data. - FrameRawMap frame_map = logging_.GetFrameRawData(); - // Size of map should be equal to the number of frames logged. - EXPECT_EQ(frame_id, frame_map.size()); - // Verify stats. - const FrameStatsMap* frame_stats = logging_.GetFrameStatsData(); - // Size of stats equals the number of events. - EXPECT_EQ(1u, frame_stats->size()); - FrameStatsMap::const_iterator it = frame_stats->find(kAudioFrameCaptured); - EXPECT_TRUE(it != frame_stats->end()); - EXPECT_NEAR(30.3, it->second->framerate_fps, 0.1); - EXPECT_EQ(0, it->second->bitrate_kbps); - EXPECT_GE(kPlayoutDelayMs + kRandomSizeInterval, it->second->max_delay_ms); - EXPECT_LE(kPlayoutDelayMs - kRandomSizeInterval, it->second->min_delay_ms); - EXPECT_NEAR(kPlayoutDelayMs, it->second->avg_delay_ms, - 0.2 * kRandomSizeInterval); -} - -TEST_F(TestLogging, MultipleEventFrameLogging) { - base::TimeTicks start_time = testing_clock_.NowTicks(); - base::TimeDelta time_interval = testing_clock_.NowTicks() - start_time; - uint32 rtp_timestamp = 0; - uint32 frame_id = 0; - do { - logging_.InsertFrameEvent(kAudioFrameCaptured, rtp_timestamp, frame_id); - if (frame_id % 2) { - logging_.InsertFrameEventWithSize( - kAudioFrameEncoded, rtp_timestamp, frame_id, 1500); - } else if (frame_id % 3) { - logging_.InsertFrameEvent(kVideoFrameDecoded, rtp_timestamp, frame_id); - } else { - logging_.InsertFrameEventWithDelay( - kVideoRenderDelay, rtp_timestamp, frame_id, - base::TimeDelta::FromMilliseconds(20)); - } - testing_clock_.Advance( - base::TimeDelta::FromMilliseconds(kFrameIntervalMs)); - rtp_timestamp += kFrameIntervalMs * 90; - ++frame_id; - time_interval = testing_clock_.NowTicks() - start_time; - } while (time_interval.InSeconds() < kIntervalTime1S); - // Get logging data. - FrameRawMap frame_map = logging_.GetFrameRawData(); - // Size of map should be equal to the number of frames logged. - EXPECT_EQ(frame_id, frame_map.size()); - // Multiple events captured per frame. -} - -TEST_F(TestLogging, PacketLogging) { - const int kNumPacketsPerFrame = 10; - const int kBaseSize = 2500; - const int kSizeInterval = 100; - base::TimeTicks start_time = testing_clock_.NowTicks(); - base::TimeDelta time_interval = testing_clock_.NowTicks() - start_time; - uint32 rtp_timestamp = 0; - uint32 frame_id = 0; - do { - for (int i = 0; i < kNumPacketsPerFrame; ++i) { - int size = kBaseSize + base::RandInt(-kSizeInterval, kSizeInterval); - logging_.InsertPacketEvent(kPacketSentToPacer, rtp_timestamp, frame_id, - i, kNumPacketsPerFrame, size); - } - testing_clock_.Advance( - base::TimeDelta::FromMilliseconds(kFrameIntervalMs)); - rtp_timestamp += kFrameIntervalMs * 90; - ++frame_id; - time_interval = testing_clock_.NowTicks() - start_time; - } while (time_interval.InSeconds() < kIntervalTime1S); - // Get logging data. - PacketRawMap raw_map = logging_.GetPacketRawData(); - // Size of map should be equal to the number of frames logged. - EXPECT_EQ(frame_id, raw_map.size()); - // Verify stats. - const PacketStatsMap* stats_map = logging_.GetPacketStatsData(); - // Size of stats equals the number of events. - EXPECT_EQ(1u, stats_map->size()); - PacketStatsMap::const_iterator it = stats_map->find(kPacketSentToPacer); - EXPECT_TRUE(it != stats_map->end()); - // We only store the bitrate as a packet statistic. - EXPECT_NEAR(8 * kNumPacketsPerFrame * kBaseSize / (kFrameIntervalMs * 1000), - it->second, kSizeInterval); -} - -TEST_F(TestLogging, GenericLogging) { - // Insert multiple generic types. - const int kNumRuns = 1000; - const int kBaseValue = 20; - for (int i = 0; i < kNumRuns; ++i) { - int value = kBaseValue + base::RandInt(-5, 5); - logging_.InsertGenericEvent(kRtt, value); - if (i % 2) { - logging_.InsertGenericEvent(kPacketLoss, value); - } - if (!(i % 4)) { - logging_.InsertGenericEvent(kJitter, value); - } - } - GenericRawMap raw_map = logging_.GetGenericRawData(); - const GenericStatsMap* stats_map = logging_.GetGenericStatsData(); - // Size of generic map = number of different events. - EXPECT_EQ(3u, raw_map.size()); - EXPECT_EQ(3u, stats_map->size()); - // Raw events - size of internal map = number of calls. - GenericRawMap::iterator rit = raw_map.find(kRtt); - EXPECT_EQ(kNumRuns, rit->second.value.size()); - EXPECT_EQ(kNumRuns, rit->second.timestamp.size()); - rit = raw_map.find(kPacketLoss); - EXPECT_EQ(kNumRuns / 2, rit->second.value.size()); - EXPECT_EQ(kNumRuns / 2, rit->second.timestamp.size()); - rit = raw_map.find(kJitter); - EXPECT_EQ(kNumRuns / 4, rit->second.value.size()); - EXPECT_EQ(kNumRuns / 4, rit->second.timestamp.size()); - // Stats - one value per event. - GenericStatsMap::const_iterator sit = stats_map->find(kRtt); - EXPECT_NEAR(kBaseValue, sit->second, 2.5); - sit = stats_map->find(kPacketLoss); - EXPECT_NEAR(kBaseValue, sit->second, 2.5); - sit = stats_map->find(kJitter); - EXPECT_NEAR(kBaseValue, sit->second, 2.5); -} - -} // namespace cast -} // namespace media diff --git a/chromium/media/cast/logging/proto/proto_utils.cc b/chromium/media/cast/logging/proto/proto_utils.cc new file mode 100644 index 00000000000..03251e64c03 --- /dev/null +++ b/chromium/media/cast/logging/proto/proto_utils.cc @@ -0,0 +1,36 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/cast/logging/proto/proto_utils.h" + +#include "base/logging.h" + +#define TO_PROTO_ENUM(enum) \ + case enum: \ + return proto::enum + +namespace media { +namespace cast { + +proto::EventType ToProtoEventType(CastLoggingEvent event) { + switch (event) { + TO_PROTO_ENUM(UNKNOWN); + TO_PROTO_ENUM(FRAME_CAPTURE_BEGIN); + TO_PROTO_ENUM(FRAME_CAPTURE_END); + TO_PROTO_ENUM(FRAME_ENCODED); + TO_PROTO_ENUM(FRAME_ACK_RECEIVED); + TO_PROTO_ENUM(FRAME_ACK_SENT); + TO_PROTO_ENUM(FRAME_DECODED); + TO_PROTO_ENUM(FRAME_PLAYOUT); + TO_PROTO_ENUM(PACKET_SENT_TO_NETWORK); + TO_PROTO_ENUM(PACKET_RETRANSMITTED); + TO_PROTO_ENUM(PACKET_RTX_REJECTED); + TO_PROTO_ENUM(PACKET_RECEIVED); + } + NOTREACHED(); + return proto::UNKNOWN; +} + +} // namespace cast +} // namespace media diff --git a/chromium/media/cast/logging/proto/proto_utils.h b/chromium/media/cast/logging/proto/proto_utils.h new file mode 100644 index 00000000000..51232fdb964 --- /dev/null +++ b/chromium/media/cast/logging/proto/proto_utils.h @@ -0,0 +1,21 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_CAST_LOGGING_PROTO_PROTO_UTILS_H_ +#define MEDIA_CAST_LOGGING_PROTO_PROTO_UTILS_H_ + +#include "media/cast/logging/logging_defines.h" +#include "media/cast/logging/proto/raw_events.pb.h" + +// Utility functions for cast logging protos. +namespace media { +namespace cast { + +// Converts |event| to a corresponding value in |media::cast::proto::EventType|. +media::cast::proto::EventType ToProtoEventType(CastLoggingEvent event); + +} // namespace cast +} // namespace media + +#endif // MEDIA_CAST_LOGGING_PROTO_PROTO_UTILS_H_ diff --git a/chromium/media/cast/logging/proto/raw_events.proto b/chromium/media/cast/logging/proto/raw_events.proto new file mode 100644 index 00000000000..1d2c537db8f --- /dev/null +++ b/chromium/media/cast/logging/proto/raw_events.proto @@ -0,0 +1,149 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Protocol for audio messages. + +syntax = "proto2"; + +option optimize_for = LITE_RUNTIME; + +package media.cast.proto; + +// Keep in sync with media/cast/logging/logging_defines.h. +// For compatibility reasons, existing values in this enum must not be changed. +enum EventType { + UNKNOWN = 0; + + // Note: 1-28 are deprecated in favor of unified event types. Do not use. + // Generic events. No longer used. + RTT_MS = 1; + PACKET_LOSS = 2; + JITTER_MS = 3; + VIDEO_ACK_RECEIVED = 4; // Sender side frame event. + REMB_BITRATE = 5; // Generic event. No longer used. + // Audio receiver. + AUDIO_ACK_SENT = 6; + // Video receiver. + VIDEO_ACK_SENT = 7; + // Audio sender. + AUDIO_FRAME_CAPTURE_END = 8; + AUDIO_FRAME_CAPTURE_BEGIN = 9; + AUDIO_FRAME_ENCODED = 10; + // Audio receiver. + AUDIO_PLAYOUT_DELAY = 11; + AUDIO_FRAME_DECODED = 12; + // Video sender. + VIDEO_FRAME_CAPTURE_BEGIN = 13; + VIDEO_FRAME_CAPTURE_END = 14; + VIDEO_FRAME_SENT_TO_ENCODER = 15; // Deprecated + VIDEO_FRAME_ENCODED = 16; + // Video receiver. + VIDEO_FRAME_DECODED = 17; + VIDEO_RENDER_DELAY = 18; + // Send-side packet events. + // AUDIO_PACKET_SENT_TO_PACER = 19; // Deprecated + // VIDEO_PACKET_SENT_TO_PACER = 20; // Deprecated + AUDIO_PACKET_SENT_TO_NETWORK = 21; + VIDEO_PACKET_SENT_TO_NETWORK = 22; + AUDIO_PACKET_RETRANSMITTED = 23; + VIDEO_PACKET_RETRANSMITTED = 24; + // Receiver-side packet events. + AUDIO_PACKET_RECEIVED = 25; + VIDEO_PACKET_RECEIVED = 26; + DUPLICATE_AUDIO_PACKET_RECEIVED = 27; + DUPLICATE_VIDEO_PACKET_RECEIVED = 28; + + + // New, unified event types. + FRAME_CAPTURE_BEGIN = 29; + FRAME_CAPTURE_END = 30; + FRAME_ENCODED = 31; + FRAME_ACK_RECEIVED = 32; + FRAME_ACK_SENT = 33; + FRAME_DECODED = 34; + FRAME_PLAYOUT = 35; + PACKET_SENT_TO_NETWORK = 36; + PACKET_RETRANSMITTED = 37; + PACKET_RECEIVED = 38; + PACKET_RTX_REJECTED = 39; +} + +// Contains information independent of the stream that describes the system +// setup, e.g. OS and hardware info. +message GeneralDescription { + optional string product = 1; + optional string product_version = 2; + optional string os = 3; +} + +// Each log will contain one |LogMetadata|. +message LogMetadata { + // |true| if the events are related to audio. |false| if they are related to + // video. + optional bool is_audio = 1; + + // Used as a reference for all event entries. + // i.e. the original RTP timestamp for each event will be + // |first_rtp_timestamp| + |relative_rtp_timestamp|. + optional uint32 first_rtp_timestamp = 2; + + // Number of AggregatedFrameEvent's. + optional int32 num_frame_events = 3; + + // Number of AggregatedPacketEvent's. + optional int32 num_packet_events = 4; + + // The internal timestamp value in milliseconds that represents the time + // of the Unix epoch. This is used for relating the timestamps in the events + // to a real time and date. + optional int64 reference_timestamp_ms_at_unix_epoch = 5; + + // Extra data to attach to the log, e.g. experiment tags, + // in key-value JSON string format. The data is supplied by the application. + optional string extra_data = 6; + + optional GeneralDescription general_description = 7; +} + +message AggregatedFrameEvent { + optional uint32 relative_rtp_timestamp = 1; + + repeated EventType event_type = 2 [packed = true]; + + // The internal timestamp value in milliseconds. Use + // LogMetadata.reference_timestamp_ms_at_unix_epoch to relate to a real time + // and date. + repeated int64 event_timestamp_ms = 3 [packed = true]; + + // Only set if there is a frame encoded event. + optional int32 encoded_frame_size = 4; + + // Only set if there is a frame playout event. + optional int32 delay_millis = 5; + + // Only set if there is a video frame encoded event. + optional bool key_frame = 6; + + // Only set if there is a video frame encoded event. + optional int32 target_bitrate = 7; +}; + +message BasePacketEvent { + optional int32 packet_id = 1; + repeated EventType event_type = 2 [packed = true]; + + // The internal timestamp value in milliseconds. Use + // LogMetadata.reference_timestamp_ms_at_unix_epoch to relate to a real time + // and date. + repeated int64 event_timestamp_ms = 3 [packed = true]; + + // Size of the packet. + optional int32 size = 4; +} + +message AggregatedPacketEvent { + optional uint32 relative_rtp_timestamp = 1; + repeated BasePacketEvent base_packet_event = 2; +}; + diff --git a/chromium/media/cast/logging/raw_event_subscriber.h b/chromium/media/cast/logging/raw_event_subscriber.h new file mode 100644 index 00000000000..b8ebe8c0cde --- /dev/null +++ b/chromium/media/cast/logging/raw_event_subscriber.h @@ -0,0 +1,32 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_CAST_LOGGING_RAW_EVENT_SUBSCRIBER_H_ +#define MEDIA_CAST_LOGGING_RAW_EVENT_SUBSCRIBER_H_ + +#include "media/cast/logging/logging_defines.h" + +namespace media { +namespace cast { + +// A subscriber interface to subscribe to cast raw event logs. +// Those who wish to subscribe to raw event logs must implement this interface, +// and call LoggingImpl::AddRawEventSubscriber() with the subscriber, in order +// to start receiving raw event logs. +class RawEventSubscriber { + public: + virtual ~RawEventSubscriber() {} + + // Called on main thread when a FrameEvent, given by |frame_event|, is logged. + virtual void OnReceiveFrameEvent(const FrameEvent& frame_event) = 0; + + // Called on main thread when a PacketEvent, given by |packet_event|, + // is logged. + virtual void OnReceivePacketEvent(const PacketEvent& packet_event) = 0; +}; + +} // namespace cast +} // namespace media + +#endif // MEDIA_CAST_LOGGING_RAW_EVENT_SUBSCRIBER_H_ diff --git a/chromium/media/cast/logging/raw_event_subscriber_bundle.cc b/chromium/media/cast/logging/raw_event_subscriber_bundle.cc new file mode 100644 index 00000000000..1946b6ce82a --- /dev/null +++ b/chromium/media/cast/logging/raw_event_subscriber_bundle.cc @@ -0,0 +1,99 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/cast/logging/raw_event_subscriber_bundle.h" + +#include "media/cast/cast_environment.h" +#include "media/cast/logging/receiver_time_offset_estimator_impl.h" + +namespace media { +namespace cast { + +RawEventSubscriberBundleForStream::RawEventSubscriberBundleForStream( + const scoped_refptr<CastEnvironment>& cast_environment, + bool is_audio, + ReceiverTimeOffsetEstimator* offset_estimator) + : cast_environment_(cast_environment), + event_subscriber_( + is_audio ? AUDIO_EVENT : VIDEO_EVENT, + is_audio ? kMaxAudioEventEntries : kMaxVideoEventEntries), + stats_subscriber_( + is_audio ? AUDIO_EVENT : VIDEO_EVENT, + cast_environment->Clock(), offset_estimator) { + cast_environment_->Logging()->AddRawEventSubscriber(&event_subscriber_); + cast_environment_->Logging()->AddRawEventSubscriber(&stats_subscriber_); +} + +RawEventSubscriberBundleForStream::~RawEventSubscriberBundleForStream() { + cast_environment_->Logging()->RemoveRawEventSubscriber(&event_subscriber_); + cast_environment_->Logging()->RemoveRawEventSubscriber(&stats_subscriber_); +} + +EncodingEventSubscriber* +RawEventSubscriberBundleForStream::GetEncodingEventSubscriber() { + return &event_subscriber_; +} + +StatsEventSubscriber* +RawEventSubscriberBundleForStream::GetStatsEventSubscriber() { + return &stats_subscriber_; +} + +RawEventSubscriberBundle::RawEventSubscriberBundle( + const scoped_refptr<CastEnvironment>& cast_environment) + : cast_environment_(cast_environment) {} + +RawEventSubscriberBundle::~RawEventSubscriberBundle() { + if (receiver_offset_estimator_.get()) { + cast_environment_->Logging()->RemoveRawEventSubscriber( + receiver_offset_estimator_.get()); + } +} + +void RawEventSubscriberBundle::AddEventSubscribers(bool is_audio) { + if (!receiver_offset_estimator_.get()) { + receiver_offset_estimator_.reset( + new ReceiverTimeOffsetEstimatorImpl); + cast_environment_->Logging()->AddRawEventSubscriber( + receiver_offset_estimator_.get()); + } + SubscribersMapByStream::iterator it = subscribers_.find(is_audio); + if (it != subscribers_.end()) + return; + + subscribers_.insert(std::make_pair( + is_audio, + make_linked_ptr(new RawEventSubscriberBundleForStream( + cast_environment_, is_audio, receiver_offset_estimator_.get())))); +} + +void RawEventSubscriberBundle::RemoveEventSubscribers(bool is_audio) { + SubscribersMapByStream::iterator it = subscribers_.find(is_audio); + if (it == subscribers_.end()) + return; + + subscribers_.erase(it); + if (subscribers_.empty()) { + cast_environment_->Logging()->RemoveRawEventSubscriber( + receiver_offset_estimator_.get()); + receiver_offset_estimator_.reset(); + } +} + +EncodingEventSubscriber* +RawEventSubscriberBundle::GetEncodingEventSubscriber(bool is_audio) { + SubscribersMapByStream::iterator it = subscribers_.find(is_audio); + return it == subscribers_.end() ? + NULL : it->second->GetEncodingEventSubscriber(); +} + +StatsEventSubscriber* +RawEventSubscriberBundle::GetStatsEventSubscriber(bool is_audio) { + SubscribersMapByStream::iterator it = subscribers_.find(is_audio); + return it == subscribers_.end() ? + NULL : it->second->GetStatsEventSubscriber(); +} + +} // namespace cast +} // namespace media diff --git a/chromium/media/cast/logging/raw_event_subscriber_bundle.h b/chromium/media/cast/logging/raw_event_subscriber_bundle.h new file mode 100644 index 00000000000..58ab21e6e8a --- /dev/null +++ b/chromium/media/cast/logging/raw_event_subscriber_bundle.h @@ -0,0 +1,84 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_CAST_LOGGING_RAW_EVENT_SUBSCRIBER_BUNDLE_H_ +#define MEDIA_CAST_LOGGING_RAW_EVENT_SUBSCRIBER_BUNDLE_H_ + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "media/cast/logging/encoding_event_subscriber.h" +#include "media/cast/logging/stats_event_subscriber.h" + +namespace media { +namespace cast { + +class CastEnvironment; +class ReceiverTimeOffsetEstimator; + +// Allow 9MB for serialized video / audio event logs. +const int kMaxSerializedBytes = 9000000; + +// Assume serialized log data for each frame will take up to 150 bytes. +const int kMaxVideoEventEntries = kMaxSerializedBytes / 150; + +// Assume serialized log data for each frame will take up to 75 bytes. +const int kMaxAudioEventEntries = kMaxSerializedBytes / 75; + +// A bundle for raw event subscribers for a single stream. +// It contains an EncodingEventSubscriber and a StatsSubscriber. +class RawEventSubscriberBundleForStream { + public: + RawEventSubscriberBundleForStream( + const scoped_refptr<CastEnvironment>& cast_environment, + bool is_audio, + ReceiverTimeOffsetEstimator* offset_estimator); + ~RawEventSubscriberBundleForStream(); + + EncodingEventSubscriber* GetEncodingEventSubscriber(); + StatsEventSubscriber* GetStatsEventSubscriber(); + + private: + const scoped_refptr<CastEnvironment> cast_environment_; + EncodingEventSubscriber event_subscriber_; + StatsEventSubscriber stats_subscriber_; + + DISALLOW_COPY_AND_ASSIGN(RawEventSubscriberBundleForStream); +}; + +// A bundle of subscribers for all streams. An instance of this object +// is associated with a CastEnvironment. +// This class can be used for managing event subscribers +// in a session where they could be multiple streams (i.e. CastSessionDelegate). +// It also contains a ReceiverTimeOffsetEstimator that is shared by subscribers +// of different streams. +class RawEventSubscriberBundle { + public: + explicit RawEventSubscriberBundle( + const scoped_refptr<CastEnvironment>& cast_environment); + ~RawEventSubscriberBundle(); + + void AddEventSubscribers(bool is_audio); + void RemoveEventSubscribers(bool is_audio); + EncodingEventSubscriber* GetEncodingEventSubscriber( + bool is_audio); + StatsEventSubscriber* GetStatsEventSubscriber(bool is_audio); + + private: + // Map from (is_audio) -> RawEventSubscriberBundleForStream. + // TODO(imcheng): This works because we only have 1 audio and 1 video stream. + // This needs to scale better. + typedef std::map<bool, linked_ptr<RawEventSubscriberBundleForStream> > + SubscribersMapByStream; + const scoped_refptr<CastEnvironment> cast_environment_; + SubscribersMapByStream subscribers_; + scoped_ptr<ReceiverTimeOffsetEstimator> receiver_offset_estimator_; + + DISALLOW_COPY_AND_ASSIGN(RawEventSubscriberBundle); +}; + +} // namespace cast +} // namespace media + +#endif // MEDIA_CAST_LOGGING_RAW_EVENT_SUBSCRIBER_BUNDLE_H_ + diff --git a/chromium/media/cast/logging/receiver_time_offset_estimator.h b/chromium/media/cast/logging/receiver_time_offset_estimator.h new file mode 100644 index 00000000000..5880a8d5ac3 --- /dev/null +++ b/chromium/media/cast/logging/receiver_time_offset_estimator.h @@ -0,0 +1,39 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_CAST_LOGGING_RECEIVER_TIME_OFFSET_ESTIMATOR_H_ +#define MEDIA_CAST_LOGGING_RECEIVER_TIME_OFFSET_ESTIMATOR_H_ + +#include "base/time/time.h" +#include "media/cast/logging/raw_event_subscriber.h" + +namespace media { +namespace cast { + +// Estimates receiver time offset based on raw events received. +// In most cases, the sender and receiver run on different time lines. +// In order to convert receiver time back to sender time (or vice versa) +// a certain time offset has to be applied. +// An implementation of this interface listens to raw events to figure out +// the bounds for the offset value (assuming the true offset value is constant +// over the lifetime of a cast session). +// The offset values provided here should be used as follows: +// - Convert from sender to receiver time: add offset value to sender timestamp. +// - Convert from receiver to sender time: subtract offset value from receiver +// timestamp. +class ReceiverTimeOffsetEstimator : public RawEventSubscriber { + public: + virtual ~ReceiverTimeOffsetEstimator() {} + + // If bounds are known, assigns |lower_bound| and |upper_bound| with the + // lower bound and upper bound for the offset value, respectively. + // Returns true if bounds are known. + virtual bool GetReceiverOffsetBounds(base::TimeDelta* lower_bound, + base::TimeDelta* upper_bound) = 0; +}; + +} // namespace cast +} // namespace media + +#endif // MEDIA_CAST_LOGGING_RECEIVER_TIME_OFFSET_ESTIMATOR_H_ diff --git a/chromium/media/cast/logging/receiver_time_offset_estimator_impl.cc b/chromium/media/cast/logging/receiver_time_offset_estimator_impl.cc new file mode 100644 index 00000000000..44d5eb0b3d7 --- /dev/null +++ b/chromium/media/cast/logging/receiver_time_offset_estimator_impl.cc @@ -0,0 +1,129 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <algorithm> +#include <utility> + +#include "base/logging.h" +#include "media/cast/logging/receiver_time_offset_estimator_impl.h" + +namespace media { +namespace cast { + +// This should be large enough so that we can collect all 3 events before +// the entry gets removed from the map. +const size_t kMaxEventTimesMapSize = 100; + +ReceiverTimeOffsetEstimatorImpl::ReceiverTimeOffsetEstimatorImpl() + : bounded_(false) {} + +ReceiverTimeOffsetEstimatorImpl::~ReceiverTimeOffsetEstimatorImpl() { + DCHECK(thread_checker_.CalledOnValidThread()); +} + +void ReceiverTimeOffsetEstimatorImpl::OnReceiveFrameEvent( + const FrameEvent& frame_event) { + DCHECK(thread_checker_.CalledOnValidThread()); + + if (frame_event.media_type != VIDEO_EVENT) + return; + + CastLoggingEvent event = frame_event.type; + if (event != FRAME_ENCODED && event != FRAME_ACK_SENT && + event != FRAME_ACK_RECEIVED) + return; + + EventTimesMap::iterator it = event_times_map_.find(frame_event.rtp_timestamp); + if (it == event_times_map_.end()) { + EventTimes event_times; + it = event_times_map_.insert(std::make_pair(frame_event.rtp_timestamp, + event_times)).first; + } + switch (event) { + case FRAME_ENCODED: + // Encode is supposed to happen only once. If we see duplicate event, + // throw away the entry. + if (it->second.event_a_time.is_null()) { + it->second.event_a_time = frame_event.timestamp; + } else { + event_times_map_.erase(it); + return; + } + break; + case FRAME_ACK_SENT: + if (it->second.event_b_time.is_null()) { + it->second.event_b_time = frame_event.timestamp; + } else if (it->second.event_b_time != frame_event.timestamp) { + // Duplicate ack sent events are normal due to RTCP redundancy, + // but they must have the same event timestamp. + event_times_map_.erase(it); + return; + } + break; + case FRAME_ACK_RECEIVED: + // If there are duplicate ack received events, pick the one with the + // smallest event timestamp so we can get a better bound. + if (it->second.event_c_time.is_null()) { + it->second.event_c_time = frame_event.timestamp; + } else { + it->second.event_c_time = + std::min(frame_event.timestamp, it->second.event_c_time); + } + break; + default: + NOTREACHED(); + } + + if (!it->second.event_a_time.is_null() && + !it->second.event_b_time.is_null() && + !it->second.event_c_time.is_null()) { + UpdateOffsetBounds(it->second); + event_times_map_.erase(it); + } + + // Keep the map size at most |kMaxEventTimesMapSize|. + if (event_times_map_.size() > kMaxEventTimesMapSize) + event_times_map_.erase(event_times_map_.begin()); +} + +bool ReceiverTimeOffsetEstimatorImpl::GetReceiverOffsetBounds( + base::TimeDelta* lower_bound, + base::TimeDelta* upper_bound) { + if (!bounded_) + return false; + + *lower_bound = offset_lower_bound_; + *upper_bound = offset_upper_bound_; + return true; +} + +void ReceiverTimeOffsetEstimatorImpl::OnReceivePacketEvent( + const PacketEvent& packet_event) { + // Not interested in packet events. + DCHECK(thread_checker_.CalledOnValidThread()); +} + +void ReceiverTimeOffsetEstimatorImpl::UpdateOffsetBounds( + const EventTimes& event) { + base::TimeDelta lower_bound = event.event_b_time - event.event_c_time; + base::TimeDelta upper_bound = event.event_b_time - event.event_a_time; + + if (bounded_) { + lower_bound = std::max(lower_bound, offset_lower_bound_); + upper_bound = std::min(upper_bound, offset_upper_bound_); + } + + if (lower_bound > upper_bound) { + VLOG(2) << "Got bogus offset bound values [" << lower_bound.InMilliseconds() + << ", " << upper_bound.InMilliseconds() << "]."; + return; + } + + offset_lower_bound_ = lower_bound; + offset_upper_bound_ = upper_bound; + bounded_ = true; +} + +} // namespace cast +} // namespace media diff --git a/chromium/media/cast/logging/receiver_time_offset_estimator_impl.h b/chromium/media/cast/logging/receiver_time_offset_estimator_impl.h new file mode 100644 index 00000000000..1d0f6c8357f --- /dev/null +++ b/chromium/media/cast/logging/receiver_time_offset_estimator_impl.h @@ -0,0 +1,64 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_CAST_LOGGING_RECEIVER_TIME_OFFSET_ESTIMATOR_IMPL_H_ +#define MEDIA_CAST_LOGGING_RECEIVER_TIME_OFFSET_ESTIMATOR_IMPL_H_ + +#include "base/time/time.h" +#include "base/threading/thread_checker.h" +#include "media/cast/logging/logging_defines.h" +#include "media/cast/logging/receiver_time_offset_estimator.h" + +namespace media { +namespace cast { + +// This implementation listens to three types of video events: +// 1. FRAME_ENCODED (sender side) +// 2. FRAME_ACK_SENT (receiver side) +// 3. FRAME_ACK_RECEIVED (sender side) +// There is a causal relationship between these events in that these events +// must happen in order. This class obtains the lower and upper bounds for +// the offset by taking the difference of timestamps (2) - (1) and (2) - (3), +// respectively. +// The bound will become better as the latency between the events decreases. +class ReceiverTimeOffsetEstimatorImpl : public ReceiverTimeOffsetEstimator { + public: + ReceiverTimeOffsetEstimatorImpl(); + + virtual ~ReceiverTimeOffsetEstimatorImpl(); + + // RawEventSubscriber implementations. + virtual void OnReceiveFrameEvent(const FrameEvent& frame_event) OVERRIDE; + virtual void OnReceivePacketEvent(const PacketEvent& packet_event) OVERRIDE; + + // ReceiverTimeOffsetEstimator implementation. + virtual bool GetReceiverOffsetBounds(base::TimeDelta* lower_bound, + base::TimeDelta* upper_bound) OVERRIDE; + + private: + struct EventTimes { + base::TimeTicks event_a_time; + base::TimeTicks event_b_time; + base::TimeTicks event_c_time; + }; + + typedef std::map<RtpTimestamp, EventTimes> EventTimesMap; + + void UpdateOffsetBounds(const EventTimes& event); + + // Fixed size storage to store event times for recent frames. + EventTimesMap event_times_map_; + + bool bounded_; + base::TimeDelta offset_lower_bound_; + base::TimeDelta offset_upper_bound_; + + base::ThreadChecker thread_checker_; + DISALLOW_COPY_AND_ASSIGN(ReceiverTimeOffsetEstimatorImpl); +}; + +} // namespace cast +} // namespace media + +#endif // MEDIA_CAST_LOGGING_RECEIVER_TIME_OFFSET_ESTIMATOR_IMPL_H_ diff --git a/chromium/media/cast/logging/receiver_time_offset_estimator_impl_unittest.cc b/chromium/media/cast/logging/receiver_time_offset_estimator_impl_unittest.cc new file mode 100644 index 00000000000..1cdbecf5de6 --- /dev/null +++ b/chromium/media/cast/logging/receiver_time_offset_estimator_impl_unittest.cc @@ -0,0 +1,242 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/test/simple_test_tick_clock.h" +#include "base/time/tick_clock.h" +#include "media/cast/cast_environment.h" +#include "media/cast/logging/logging_defines.h" +#include "media/cast/logging/receiver_time_offset_estimator_impl.h" +#include "media/cast/test/fake_single_thread_task_runner.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace media { +namespace cast { + +class ReceiverTimeOffsetEstimatorImplTest : public ::testing::Test { + protected: + ReceiverTimeOffsetEstimatorImplTest() + : sender_clock_(new base::SimpleTestTickClock()), + task_runner_(new test::FakeSingleThreadTaskRunner(sender_clock_)), + cast_environment_(new CastEnvironment( + scoped_ptr<base::TickClock>(sender_clock_).Pass(), + task_runner_, + task_runner_, + task_runner_)) { + cast_environment_->Logging()->AddRawEventSubscriber(&estimator_); + } + + virtual ~ReceiverTimeOffsetEstimatorImplTest() { + cast_environment_->Logging()->RemoveRawEventSubscriber(&estimator_); + } + + void AdvanceClocks(base::TimeDelta time) { + sender_clock_->Advance(time); + receiver_clock_.Advance(time); + } + + base::SimpleTestTickClock* sender_clock_; // Owned by CastEnvironment. + scoped_refptr<test::FakeSingleThreadTaskRunner> task_runner_; + scoped_refptr<CastEnvironment> cast_environment_; + base::SimpleTestTickClock receiver_clock_; + ReceiverTimeOffsetEstimatorImpl estimator_; +}; + +// Suppose the true offset is 100ms. +// Event A occurred at sender time 20ms. +// Event B occurred at receiver time 130ms. (sender time 30ms) +// Event C occurred at sender time 60ms. +// Then the bound after all 3 events have arrived is [130-60=70, 130-20=110]. +TEST_F(ReceiverTimeOffsetEstimatorImplTest, EstimateOffset) { + int64 true_offset_ms = 100; + receiver_clock_.Advance(base::TimeDelta::FromMilliseconds(true_offset_ms)); + + base::TimeDelta lower_bound; + base::TimeDelta upper_bound; + + EXPECT_FALSE(estimator_.GetReceiverOffsetBounds(&lower_bound, &upper_bound)); + + RtpTimestamp rtp_timestamp = 0; + uint32 frame_id = 0; + + AdvanceClocks(base::TimeDelta::FromMilliseconds(20)); + + cast_environment_->Logging()->InsertEncodedFrameEvent( + sender_clock_->NowTicks(), + FRAME_ENCODED, VIDEO_EVENT, + rtp_timestamp, + frame_id, + 1234, + true, + 5678); + + EXPECT_FALSE(estimator_.GetReceiverOffsetBounds(&lower_bound, &upper_bound)); + + AdvanceClocks(base::TimeDelta::FromMilliseconds(10)); + cast_environment_->Logging()->InsertFrameEvent( + receiver_clock_.NowTicks(), FRAME_ACK_SENT, VIDEO_EVENT, + rtp_timestamp, frame_id); + + EXPECT_FALSE(estimator_.GetReceiverOffsetBounds(&lower_bound, &upper_bound)); + + AdvanceClocks(base::TimeDelta::FromMilliseconds(30)); + cast_environment_->Logging()->InsertFrameEvent( + sender_clock_->NowTicks(), FRAME_ACK_RECEIVED, VIDEO_EVENT, + rtp_timestamp, frame_id); + + EXPECT_TRUE(estimator_.GetReceiverOffsetBounds(&lower_bound, &upper_bound)); + + int64 lower_bound_ms = lower_bound.InMilliseconds(); + int64 upper_bound_ms = upper_bound.InMilliseconds(); + EXPECT_EQ(70, lower_bound_ms); + EXPECT_EQ(110, upper_bound_ms); + EXPECT_GE(true_offset_ms, lower_bound_ms); + EXPECT_LE(true_offset_ms, upper_bound_ms); +} + +// Same scenario as above, but event C arrives before event B. It doens't mean +// event C occurred before event B. +TEST_F(ReceiverTimeOffsetEstimatorImplTest, EventCArrivesBeforeEventB) { + int64 true_offset_ms = 100; + receiver_clock_.Advance(base::TimeDelta::FromMilliseconds(true_offset_ms)); + + base::TimeDelta lower_bound; + base::TimeDelta upper_bound; + + EXPECT_FALSE(estimator_.GetReceiverOffsetBounds(&lower_bound, &upper_bound)); + + RtpTimestamp rtp_timestamp = 0; + uint32 frame_id = 0; + + AdvanceClocks(base::TimeDelta::FromMilliseconds(20)); + + cast_environment_->Logging()->InsertEncodedFrameEvent( + sender_clock_->NowTicks(), + FRAME_ENCODED, VIDEO_EVENT, + rtp_timestamp, + frame_id, + 1234, + true, + 5678); + + EXPECT_FALSE(estimator_.GetReceiverOffsetBounds(&lower_bound, &upper_bound)); + + AdvanceClocks(base::TimeDelta::FromMilliseconds(10)); + base::TimeTicks event_b_time = receiver_clock_.NowTicks(); + AdvanceClocks(base::TimeDelta::FromMilliseconds(30)); + base::TimeTicks event_c_time = sender_clock_->NowTicks(); + + cast_environment_->Logging()->InsertFrameEvent( + event_c_time, FRAME_ACK_RECEIVED, VIDEO_EVENT, rtp_timestamp, frame_id); + + EXPECT_FALSE(estimator_.GetReceiverOffsetBounds(&lower_bound, &upper_bound)); + + cast_environment_->Logging()->InsertFrameEvent( + event_b_time, FRAME_ACK_SENT, VIDEO_EVENT, rtp_timestamp, frame_id); + + EXPECT_TRUE(estimator_.GetReceiverOffsetBounds(&lower_bound, &upper_bound)); + + int64 lower_bound_ms = lower_bound.InMilliseconds(); + int64 upper_bound_ms = upper_bound.InMilliseconds(); + EXPECT_EQ(70, lower_bound_ms); + EXPECT_EQ(110, upper_bound_ms); + EXPECT_GE(true_offset_ms, lower_bound_ms); + EXPECT_LE(true_offset_ms, upper_bound_ms); +} + +TEST_F(ReceiverTimeOffsetEstimatorImplTest, MultipleIterations) { + int64 true_offset_ms = 100; + receiver_clock_.Advance(base::TimeDelta::FromMilliseconds(true_offset_ms)); + + base::TimeDelta lower_bound; + base::TimeDelta upper_bound; + + RtpTimestamp rtp_timestamp_a = 0; + int frame_id_a = 0; + RtpTimestamp rtp_timestamp_b = 90; + int frame_id_b = 1; + RtpTimestamp rtp_timestamp_c = 180; + int frame_id_c = 2; + + // Frame 1 times: [20, 30+100, 60] + // Frame 2 times: [30, 50+100, 55] + // Frame 3 times: [77, 80+100, 110] + // Bound should end up at [95, 103] + // Events times in chronological order: 20, 30 x2, 50, 55, 60, 77, 80, 110 + AdvanceClocks(base::TimeDelta::FromMilliseconds(20)); + cast_environment_->Logging()->InsertEncodedFrameEvent( + sender_clock_->NowTicks(), + FRAME_ENCODED, VIDEO_EVENT, + rtp_timestamp_a, + frame_id_a, + 1234, + true, + 5678); + + AdvanceClocks(base::TimeDelta::FromMilliseconds(10)); + cast_environment_->Logging()->InsertEncodedFrameEvent( + sender_clock_->NowTicks(), + FRAME_ENCODED, VIDEO_EVENT, + rtp_timestamp_b, + frame_id_b, + 1234, + true, + 5678); + cast_environment_->Logging()->InsertFrameEvent( + receiver_clock_.NowTicks(), FRAME_ACK_SENT, VIDEO_EVENT, + rtp_timestamp_a, frame_id_a); + + AdvanceClocks(base::TimeDelta::FromMilliseconds(20)); + cast_environment_->Logging()->InsertFrameEvent( + receiver_clock_.NowTicks(), FRAME_ACK_SENT, VIDEO_EVENT, + rtp_timestamp_b, frame_id_b); + + AdvanceClocks(base::TimeDelta::FromMilliseconds(5)); + cast_environment_->Logging()->InsertFrameEvent(sender_clock_->NowTicks(), + FRAME_ACK_RECEIVED, + VIDEO_EVENT, + rtp_timestamp_b, + frame_id_b); + + AdvanceClocks(base::TimeDelta::FromMilliseconds(5)); + cast_environment_->Logging()->InsertFrameEvent(sender_clock_->NowTicks(), + FRAME_ACK_RECEIVED, + VIDEO_EVENT, + rtp_timestamp_a, + frame_id_a); + + AdvanceClocks(base::TimeDelta::FromMilliseconds(17)); + cast_environment_->Logging()->InsertEncodedFrameEvent( + sender_clock_->NowTicks(), + FRAME_ENCODED, VIDEO_EVENT, + rtp_timestamp_c, + frame_id_c, + 1234, + true, + 5678); + + AdvanceClocks(base::TimeDelta::FromMilliseconds(3)); + cast_environment_->Logging()->InsertFrameEvent( + receiver_clock_.NowTicks(), FRAME_ACK_SENT, VIDEO_EVENT, + rtp_timestamp_c, frame_id_c); + + AdvanceClocks(base::TimeDelta::FromMilliseconds(30)); + cast_environment_->Logging()->InsertFrameEvent(sender_clock_->NowTicks(), + FRAME_ACK_RECEIVED, + VIDEO_EVENT, + rtp_timestamp_c, + frame_id_c); + + EXPECT_TRUE(estimator_.GetReceiverOffsetBounds(&lower_bound, &upper_bound)); + int64 lower_bound_ms = lower_bound.InMilliseconds(); + int64 upper_bound_ms = upper_bound.InMilliseconds(); + EXPECT_EQ(95, lower_bound_ms); + EXPECT_EQ(103, upper_bound_ms); + EXPECT_GE(true_offset_ms, lower_bound_ms); + EXPECT_LE(true_offset_ms, upper_bound_ms); +} + +} // namespace cast +} // namespace media diff --git a/chromium/media/cast/logging/serialize_deserialize_test.cc b/chromium/media/cast/logging/serialize_deserialize_test.cc new file mode 100644 index 00000000000..7e5aa7d3b5e --- /dev/null +++ b/chromium/media/cast/logging/serialize_deserialize_test.cc @@ -0,0 +1,214 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Joint LogSerializer and LogDeserializer testing to make sure they stay in +// sync. + +#include "base/memory/scoped_ptr.h" +#include "media/cast/logging/log_deserializer.h" +#include "media/cast/logging/log_serializer.h" +#include "media/cast/logging/logging_defines.h" +#include "media/cast/logging/proto/proto_utils.h" +#include "testing/gtest/include/gtest/gtest.h" + +using media::cast::proto::AggregatedFrameEvent; +using media::cast::proto::AggregatedPacketEvent; +using media::cast::proto::BasePacketEvent; +using media::cast::proto::LogMetadata; + +namespace { + +const media::cast::CastLoggingEvent kVideoFrameEvents[] = { + media::cast::FRAME_CAPTURE_BEGIN, media::cast::FRAME_CAPTURE_END, + media::cast::FRAME_ENCODED, media::cast::FRAME_DECODED, + media::cast::FRAME_PLAYOUT }; + +const media::cast::CastLoggingEvent kVideoPacketEvents[] = { + media::cast::PACKET_SENT_TO_NETWORK, media::cast::PACKET_RECEIVED}; + +// The frame event fields cycle through these numbers. +const int kEncodedFrameSize[] = {512, 425, 399, 400, 237}; +const int kDelayMillis[] = {15, 4, 8, 42, 23, 16}; + +const int kMaxSerializedBytes = 10000; + +} + +namespace media { +namespace cast { + +class SerializeDeserializeTest : public ::testing::Test { + protected: + SerializeDeserializeTest() + : serialized_(new char[kMaxSerializedBytes]), output_bytes_(0) {} + + virtual ~SerializeDeserializeTest() {} + + void Init() { + metadata_.set_first_rtp_timestamp(12345678 * 90); + metadata_.set_is_audio(false); + metadata_.set_num_frame_events(10); + metadata_.set_num_packet_events(10); + + int64 event_time_ms = 0; + // Insert frame and packet events with RTP timestamps 0, 90, 180, ... + for (int i = 0; i < metadata_.num_frame_events(); i++) { + linked_ptr<AggregatedFrameEvent> frame_event(new AggregatedFrameEvent); + frame_event->set_relative_rtp_timestamp(i * 90); + for (uint32 event_index = 0; event_index < arraysize(kVideoFrameEvents); + ++event_index) { + frame_event->add_event_type( + ToProtoEventType(kVideoFrameEvents[event_index])); + frame_event->add_event_timestamp_ms(event_time_ms); + event_time_ms += 1024; + } + frame_event->set_encoded_frame_size( + kEncodedFrameSize[i % arraysize(kEncodedFrameSize)]); + frame_event->set_delay_millis(kDelayMillis[i % arraysize(kDelayMillis)]); + + frame_event_list_.push_back(frame_event); + } + + event_time_ms = 0; + int packet_id = 0; + for (int i = 0; i < metadata_.num_packet_events(); i++) { + linked_ptr<AggregatedPacketEvent> packet_event(new AggregatedPacketEvent); + packet_event->set_relative_rtp_timestamp(i * 90); + for (int j = 0; j < 10; j++) { + BasePacketEvent* base_event = packet_event->add_base_packet_event(); + base_event->set_packet_id(packet_id); + packet_id++; + for (uint32 event_index = 0; + event_index < arraysize(kVideoPacketEvents); + ++event_index) { + base_event->add_event_type( + ToProtoEventType(kVideoPacketEvents[event_index])); + base_event->add_event_timestamp_ms(event_time_ms); + event_time_ms += 256; + } + } + packet_event_list_.push_back(packet_event); + } + } + + void Verify(const DeserializedLog& video_log) { + const LogMetadata& returned_metadata = video_log.metadata; + const FrameEventMap& returned_frame_events = video_log.frame_events; + const PacketEventMap& returned_packet_events = video_log.packet_events; + + EXPECT_EQ(metadata_.SerializeAsString(), + returned_metadata.SerializeAsString()); + + // Check that the returned map is equal to the original map. + EXPECT_EQ(frame_event_list_.size(), returned_frame_events.size()); + for (FrameEventMap::const_iterator frame_it = returned_frame_events.begin(); + frame_it != returned_frame_events.end(); + ++frame_it) { + FrameEventList::iterator original_it = frame_event_list_.begin(); + ASSERT_NE(frame_event_list_.end(), original_it); + // Compare protos by serializing and checking the bytes. + EXPECT_EQ((*original_it)->SerializeAsString(), + frame_it->second->SerializeAsString()); + frame_event_list_.erase(frame_event_list_.begin()); + } + EXPECT_TRUE(frame_event_list_.empty()); + + EXPECT_EQ(packet_event_list_.size(), returned_packet_events.size()); + for (PacketEventMap::const_iterator packet_it = + returned_packet_events.begin(); + packet_it != returned_packet_events.end(); + ++packet_it) { + PacketEventList::iterator original_it = packet_event_list_.begin(); + ASSERT_NE(packet_event_list_.end(), original_it); + // Compare protos by serializing and checking the bytes. + EXPECT_EQ((*original_it)->SerializeAsString(), + packet_it->second->SerializeAsString()); + packet_event_list_.erase(packet_event_list_.begin()); + } + EXPECT_TRUE(packet_event_list_.empty()); + } + + LogMetadata metadata_; + FrameEventList frame_event_list_; + PacketEventList packet_event_list_; + scoped_ptr<char[]> serialized_; + int output_bytes_; +}; + +TEST_F(SerializeDeserializeTest, Uncompressed) { + bool compressed = false; + Init(); + + bool success = SerializeEvents(metadata_, + frame_event_list_, + packet_event_list_, + compressed, + kMaxSerializedBytes, + serialized_.get(), + &output_bytes_); + ASSERT_TRUE(success); + ASSERT_GT(output_bytes_, 0); + + DeserializedLog audio_log; + DeserializedLog video_log; + success = DeserializeEvents( + serialized_.get(), output_bytes_, compressed, &audio_log, &video_log); + ASSERT_TRUE(success); + + Verify(video_log); +} + +TEST_F(SerializeDeserializeTest, UncompressedInsufficientSpace) { + bool compressed = false; + Init(); + serialized_.reset(new char[100]); + bool success = SerializeEvents(metadata_, + frame_event_list_, + packet_event_list_, + compressed, + 100, + serialized_.get(), + &output_bytes_); + EXPECT_FALSE(success); + EXPECT_EQ(0, output_bytes_); +} + +TEST_F(SerializeDeserializeTest, Compressed) { + bool compressed = true; + Init(); + bool success = SerializeEvents(metadata_, + frame_event_list_, + packet_event_list_, + compressed, + kMaxSerializedBytes, + serialized_.get(), + &output_bytes_); + ASSERT_TRUE(success); + ASSERT_GT(output_bytes_, 0); + + DeserializedLog audio_log; + DeserializedLog video_log; + success = DeserializeEvents( + serialized_.get(), output_bytes_, compressed, &audio_log, &video_log); + ASSERT_TRUE(success); + Verify(video_log); +} + +TEST_F(SerializeDeserializeTest, CompressedInsufficientSpace) { + bool compressed = true; + Init(); + serialized_.reset(new char[100]); + bool success = SerializeEvents(metadata_, + frame_event_list_, + packet_event_list_, + compressed, + 100, + serialized_.get(), + &output_bytes_); + EXPECT_FALSE(success); + EXPECT_EQ(0, output_bytes_); +} + +} // namespace cast +} // namespace media diff --git a/chromium/media/cast/logging/simple_event_subscriber.cc b/chromium/media/cast/logging/simple_event_subscriber.cc new file mode 100644 index 00000000000..984d8f7d830 --- /dev/null +++ b/chromium/media/cast/logging/simple_event_subscriber.cc @@ -0,0 +1,46 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/cast/logging/simple_event_subscriber.h" + +#include <vector> + +#include "base/logging.h" + +namespace media { +namespace cast { + +SimpleEventSubscriber::SimpleEventSubscriber() {} + +SimpleEventSubscriber::~SimpleEventSubscriber() { + DCHECK(thread_checker_.CalledOnValidThread()); +} + +void SimpleEventSubscriber::OnReceiveFrameEvent(const FrameEvent& frame_event) { + DCHECK(thread_checker_.CalledOnValidThread()); + frame_events_.push_back(frame_event); +} + +void SimpleEventSubscriber::OnReceivePacketEvent( + const PacketEvent& packet_event) { + DCHECK(thread_checker_.CalledOnValidThread()); + packet_events_.push_back(packet_event); +} + +void SimpleEventSubscriber::GetFrameEventsAndReset( + std::vector<FrameEvent>* frame_events) { + DCHECK(thread_checker_.CalledOnValidThread()); + frame_events->swap(frame_events_); + frame_events_.clear(); +} + +void SimpleEventSubscriber::GetPacketEventsAndReset( + std::vector<PacketEvent>* packet_events) { + DCHECK(thread_checker_.CalledOnValidThread()); + packet_events->swap(packet_events_); + packet_events_.clear(); +} + +} // namespace cast +} // namespace media diff --git a/chromium/media/cast/logging/simple_event_subscriber.h b/chromium/media/cast/logging/simple_event_subscriber.h new file mode 100644 index 00000000000..adc4763f5f4 --- /dev/null +++ b/chromium/media/cast/logging/simple_event_subscriber.h @@ -0,0 +1,52 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_CAST_LOGGING_SIMPLE_EVENT_SUBSCRIBER_H_ +#define MEDIA_CAST_LOGGING_SIMPLE_EVENT_SUBSCRIBER_H_ + +#include <vector> + +#include "base/compiler_specific.h" +#include "base/threading/thread_checker.h" +#include "media/cast/logging/raw_event_subscriber.h" + +namespace media { +namespace cast { + +// RawEventSubscriber implementation that records all incoming raw events +// in std::vector's. +// The user of this class can call the GetXXXEventsAndReset functions to get +// list of events that have acccumulated since last inovcation. +class SimpleEventSubscriber : public RawEventSubscriber { + public: + SimpleEventSubscriber(); + + virtual ~SimpleEventSubscriber(); + + // RawEventSubscriber implementations. + virtual void OnReceiveFrameEvent(const FrameEvent& frame_event) OVERRIDE; + virtual void OnReceivePacketEvent(const PacketEvent& packet_event) OVERRIDE; + + // Assigns frame events received so far to |frame_events| and clears them + // from this object. + void GetFrameEventsAndReset(std::vector<FrameEvent>* frame_events); + + // Assigns packet events received so far to |packet_events| and clears them + // from this object. + void GetPacketEventsAndReset(std::vector<PacketEvent>* packet_events); + + private: + std::vector<FrameEvent> frame_events_; + std::vector<PacketEvent> packet_events_; + + // All functions must be called on the main thread. + base::ThreadChecker thread_checker_; + + DISALLOW_COPY_AND_ASSIGN(SimpleEventSubscriber); +}; + +} // namespace cast +} // namespace media + +#endif // MEDIA_CAST_LOGGING_SIMPLE_EVENT_SUBSCRIBER_H_ diff --git a/chromium/media/cast/logging/simple_event_subscriber_unittest.cc b/chromium/media/cast/logging/simple_event_subscriber_unittest.cc new file mode 100644 index 00000000000..311a2341951 --- /dev/null +++ b/chromium/media/cast/logging/simple_event_subscriber_unittest.cc @@ -0,0 +1,87 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/test/simple_test_tick_clock.h" +#include "base/time/tick_clock.h" +#include "media/cast/cast_environment.h" +#include "media/cast/logging/logging_defines.h" +#include "media/cast/logging/simple_event_subscriber.h" +#include "media/cast/test/fake_single_thread_task_runner.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace media { +namespace cast { + +class SimpleEventSubscriberTest : public ::testing::Test { + protected: + SimpleEventSubscriberTest() + : testing_clock_(new base::SimpleTestTickClock()), + task_runner_(new test::FakeSingleThreadTaskRunner(testing_clock_)), + cast_environment_(new CastEnvironment( + scoped_ptr<base::TickClock>(testing_clock_).Pass(), + task_runner_, + task_runner_, + task_runner_)) { + cast_environment_->Logging()->AddRawEventSubscriber(&event_subscriber_); + } + + virtual ~SimpleEventSubscriberTest() { + cast_environment_->Logging()->RemoveRawEventSubscriber(&event_subscriber_); + } + + base::SimpleTestTickClock* testing_clock_; // Owned by CastEnvironment. + scoped_refptr<test::FakeSingleThreadTaskRunner> task_runner_; + scoped_refptr<CastEnvironment> cast_environment_; + SimpleEventSubscriber event_subscriber_; +}; + +TEST_F(SimpleEventSubscriberTest, GetAndResetEvents) { + // Log some frame events. + cast_environment_->Logging()->InsertEncodedFrameEvent( + testing_clock_->NowTicks(), FRAME_ENCODED, AUDIO_EVENT, + /*rtp_timestamp*/ 100u, /*frame_id*/ 0u, /*frame_size*/ 123, + /*key_frame*/ false, 0); + cast_environment_->Logging()->InsertFrameEventWithDelay( + testing_clock_->NowTicks(), FRAME_PLAYOUT, AUDIO_EVENT, + /*rtp_timestamp*/ 100u, + /*frame_id*/ 0u, /*delay*/ base::TimeDelta::FromMilliseconds(100)); + cast_environment_->Logging()->InsertFrameEvent( + testing_clock_->NowTicks(), FRAME_DECODED, AUDIO_EVENT, + /*rtp_timestamp*/ 200u, + /*frame_id*/ 0u); + + // Log some packet events. + cast_environment_->Logging()->InsertPacketEvent( + testing_clock_->NowTicks(), PACKET_RECEIVED, AUDIO_EVENT, + /*rtp_timestamp*/ 200u, + /*frame_id*/ 0u, /*packet_id*/ 1u, /*max_packet_id*/ 5u, /*size*/ 100u); + cast_environment_->Logging()->InsertPacketEvent( + testing_clock_->NowTicks(), FRAME_DECODED, VIDEO_EVENT, + /*rtp_timestamp*/ 200u, /*frame_id*/ 0u, /*packet_id*/ 1u, + /*max_packet_id*/ 5u, /*size*/ 100u); + cast_environment_->Logging()->InsertPacketEvent( + testing_clock_->NowTicks(), FRAME_DECODED, VIDEO_EVENT, + /*rtp_timestamp*/ 300u, /*frame_id*/ 0u, /*packet_id*/ 1u, + /*max_packet_id*/ 5u, /*size*/ 100u); + + std::vector<FrameEvent> frame_events; + event_subscriber_.GetFrameEventsAndReset(&frame_events); + EXPECT_EQ(3u, frame_events.size()); + + std::vector<PacketEvent> packet_events; + event_subscriber_.GetPacketEventsAndReset(&packet_events); + EXPECT_EQ(3u, packet_events.size()); + + // Calling this function again should result in empty vector because no events + // were logged since last call. + event_subscriber_.GetFrameEventsAndReset(&frame_events); + event_subscriber_.GetPacketEventsAndReset(&packet_events); + EXPECT_TRUE(frame_events.empty()); + EXPECT_TRUE(packet_events.empty()); +} + +} // namespace cast +} // namespace media diff --git a/chromium/media/cast/logging/stats_event_subscriber.cc b/chromium/media/cast/logging/stats_event_subscriber.cc new file mode 100644 index 00000000000..9e3226a2161 --- /dev/null +++ b/chromium/media/cast/logging/stats_event_subscriber.cc @@ -0,0 +1,400 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/cast/logging/stats_event_subscriber.h" + +#include "base/logging.h" +#include "base/values.h" + +#define STAT_ENUM_TO_STRING(enum) \ + case enum: \ + return #enum + +namespace media { +namespace cast { + +namespace { + +using media::cast::CastLoggingEvent; +using media::cast::EventMediaType; + +const size_t kMaxFrameEventTimeMapSize = 100; +const size_t kMaxPacketEventTimeMapSize = 1000; + +bool IsReceiverEvent(CastLoggingEvent event) { + return event == FRAME_DECODED + || event == FRAME_PLAYOUT + || event == FRAME_ACK_SENT + || event == PACKET_RECEIVED; +} + +} // namespace + +StatsEventSubscriber::StatsEventSubscriber( + EventMediaType event_media_type, + base::TickClock* clock, + ReceiverTimeOffsetEstimator* offset_estimator) + : event_media_type_(event_media_type), + clock_(clock), + offset_estimator_(offset_estimator), + network_latency_datapoints_(0), + e2e_latency_datapoints_(0) { + DCHECK(event_media_type == AUDIO_EVENT || event_media_type == VIDEO_EVENT); + base::TimeTicks now = clock_->NowTicks(); + start_time_ = now; + last_response_received_time_ = base::TimeTicks(); +} + +StatsEventSubscriber::~StatsEventSubscriber() { + DCHECK(thread_checker_.CalledOnValidThread()); +} + +void StatsEventSubscriber::OnReceiveFrameEvent(const FrameEvent& frame_event) { + DCHECK(thread_checker_.CalledOnValidThread()); + + CastLoggingEvent type = frame_event.type; + if (frame_event.media_type != event_media_type_) + return; + + FrameStatsMap::iterator it = frame_stats_.find(type); + if (it == frame_stats_.end()) { + FrameLogStats stats; + stats.event_counter = 1; + stats.sum_size = frame_event.size; + stats.sum_delay = frame_event.delay_delta; + frame_stats_.insert(std::make_pair(type, stats)); + } else { + ++(it->second.event_counter); + it->second.sum_size += frame_event.size; + it->second.sum_delay += frame_event.delay_delta; + } + + if (type == FRAME_CAPTURE_BEGIN) { + RecordFrameCapturedTime(frame_event); + } else if (type == FRAME_PLAYOUT) { + RecordE2ELatency(frame_event); + } + + if (IsReceiverEvent(type)) + UpdateLastResponseTime(frame_event.timestamp); +} + +void StatsEventSubscriber::OnReceivePacketEvent( + const PacketEvent& packet_event) { + DCHECK(thread_checker_.CalledOnValidThread()); + + CastLoggingEvent type = packet_event.type; + if (packet_event.media_type != event_media_type_) + return; + + PacketStatsMap::iterator it = packet_stats_.find(type); + if (it == packet_stats_.end()) { + PacketLogStats stats; + stats.event_counter = 1; + stats.sum_size = packet_event.size; + packet_stats_.insert(std::make_pair(type, stats)); + } else { + ++(it->second.event_counter); + it->second.sum_size += packet_event.size; + } + + if (type == PACKET_SENT_TO_NETWORK || + type == PACKET_RECEIVED) { + RecordNetworkLatency(packet_event); + } else if (type == PACKET_RETRANSMITTED) { + // We only measure network latency using packets that doesn't have to be + // retransmitted as there is precisely one sent-receive timestamp pairs. + ErasePacketSentTime(packet_event); + } + + if (IsReceiverEvent(type)) + UpdateLastResponseTime(packet_event.timestamp); +} + +scoped_ptr<base::DictionaryValue> StatsEventSubscriber::GetStats() const { + StatsMap stats_map; + GetStatsInternal(&stats_map); + scoped_ptr<base::DictionaryValue> ret(new base::DictionaryValue); + + scoped_ptr<base::DictionaryValue> stats(new base::DictionaryValue); + for (StatsMap::const_iterator it = stats_map.begin(); it != stats_map.end(); + ++it) { + stats->SetDouble(CastStatToString(it->first), it->second); + } + + ret->Set(event_media_type_ == AUDIO_EVENT ? "audio" : "video", + stats.release()); + + return ret.Pass(); +} + +void StatsEventSubscriber::Reset() { + DCHECK(thread_checker_.CalledOnValidThread()); + + frame_stats_.clear(); + packet_stats_.clear(); + total_network_latency_ = base::TimeDelta(); + network_latency_datapoints_ = 0; + total_e2e_latency_ = base::TimeDelta(); + e2e_latency_datapoints_ = 0; + frame_captured_times_.clear(); + packet_sent_times_.clear(); + start_time_ = clock_->NowTicks(); + last_response_received_time_ = base::TimeTicks(); +} + +// static +const char* StatsEventSubscriber::CastStatToString(CastStat stat) { + switch (stat) { + STAT_ENUM_TO_STRING(CAPTURE_FPS); + STAT_ENUM_TO_STRING(ENCODE_FPS); + STAT_ENUM_TO_STRING(DECODE_FPS); + STAT_ENUM_TO_STRING(AVG_ENCODE_TIME_MS); + STAT_ENUM_TO_STRING(AVG_PLAYOUT_DELAY_MS); + STAT_ENUM_TO_STRING(AVG_NETWORK_LATENCY_MS); + STAT_ENUM_TO_STRING(AVG_E2E_LATENCY_MS); + STAT_ENUM_TO_STRING(ENCODE_KBPS); + STAT_ENUM_TO_STRING(TRANSMISSION_KBPS); + STAT_ENUM_TO_STRING(RETRANSMISSION_KBPS); + STAT_ENUM_TO_STRING(PACKET_LOSS_FRACTION); + STAT_ENUM_TO_STRING(MS_SINCE_LAST_RECEIVER_RESPONSE); + } + NOTREACHED(); + return ""; +} + +void StatsEventSubscriber::GetStatsInternal(StatsMap* stats_map) const { + DCHECK(thread_checker_.CalledOnValidThread()); + + stats_map->clear(); + + base::TimeTicks end_time = clock_->NowTicks(); + + PopulateFpsStat( + end_time, FRAME_CAPTURE_BEGIN, CAPTURE_FPS, stats_map); + PopulateFpsStat( + end_time, FRAME_ENCODED, ENCODE_FPS, stats_map); + PopulateFpsStat( + end_time, FRAME_DECODED, DECODE_FPS, stats_map); + PopulatePlayoutDelayStat(stats_map); + PopulateFrameBitrateStat(end_time, stats_map); + PopulatePacketBitrateStat(end_time, + PACKET_SENT_TO_NETWORK, + TRANSMISSION_KBPS, + stats_map); + PopulatePacketBitrateStat(end_time, + PACKET_RETRANSMITTED, + RETRANSMISSION_KBPS, + stats_map); + PopulatePacketLossPercentageStat(stats_map); + + if (network_latency_datapoints_ > 0) { + double avg_network_latency_ms = + total_network_latency_.InMillisecondsF() / + network_latency_datapoints_; + stats_map->insert( + std::make_pair(AVG_NETWORK_LATENCY_MS, avg_network_latency_ms)); + } + + if (e2e_latency_datapoints_ > 0) { + double avg_e2e_latency_ms = + total_e2e_latency_.InMillisecondsF() / e2e_latency_datapoints_; + stats_map->insert(std::make_pair(AVG_E2E_LATENCY_MS, avg_e2e_latency_ms)); + } + + if (!last_response_received_time_.is_null()) { + stats_map->insert( + std::make_pair(MS_SINCE_LAST_RECEIVER_RESPONSE, + (end_time - last_response_received_time_).InMillisecondsF())); + } +} + +bool StatsEventSubscriber::GetReceiverOffset(base::TimeDelta* offset) { + base::TimeDelta receiver_offset_lower_bound; + base::TimeDelta receiver_offset_upper_bound; + if (!offset_estimator_->GetReceiverOffsetBounds( + &receiver_offset_lower_bound, &receiver_offset_upper_bound)) { + return false; + } + + *offset = (receiver_offset_lower_bound + receiver_offset_upper_bound) / 2; + return true; +} + +void StatsEventSubscriber::RecordFrameCapturedTime( + const FrameEvent& frame_event) { + frame_captured_times_.insert( + std::make_pair(frame_event.rtp_timestamp, frame_event.timestamp)); + if (frame_captured_times_.size() > kMaxFrameEventTimeMapSize) + frame_captured_times_.erase(frame_captured_times_.begin()); +} + +void StatsEventSubscriber::RecordE2ELatency(const FrameEvent& frame_event) { + base::TimeDelta receiver_offset; + if (!GetReceiverOffset(&receiver_offset)) + return; + + FrameEventTimeMap::iterator it = + frame_captured_times_.find(frame_event.rtp_timestamp); + if (it == frame_captured_times_.end()) + return; + + // Playout time is event time + playout delay. + base::TimeTicks playout_time = + frame_event.timestamp + frame_event.delay_delta - receiver_offset; + total_e2e_latency_ += playout_time - it->second; + e2e_latency_datapoints_++; +} + +void StatsEventSubscriber::UpdateLastResponseTime( + base::TimeTicks receiver_time) { + base::TimeDelta receiver_offset; + if (!GetReceiverOffset(&receiver_offset)) + return; + base::TimeTicks sender_time = receiver_time - receiver_offset; + last_response_received_time_ = sender_time; +} + +void StatsEventSubscriber::ErasePacketSentTime( + const PacketEvent& packet_event) { + std::pair<RtpTimestamp, uint16> key( + std::make_pair(packet_event.rtp_timestamp, packet_event.packet_id)); + packet_sent_times_.erase(key); +} + +void StatsEventSubscriber::RecordNetworkLatency( + const PacketEvent& packet_event) { + base::TimeDelta receiver_offset; + if (!GetReceiverOffset(&receiver_offset)) + return; + + std::pair<RtpTimestamp, uint16> key( + std::make_pair(packet_event.rtp_timestamp, packet_event.packet_id)); + PacketEventTimeMap::iterator it = packet_sent_times_.find(key); + if (it == packet_sent_times_.end()) { + std::pair<RtpTimestamp, uint16> key( + std::make_pair(packet_event.rtp_timestamp, packet_event.packet_id)); + std::pair<base::TimeTicks, CastLoggingEvent> value = + std::make_pair(packet_event.timestamp, packet_event.type); + packet_sent_times_.insert(std::make_pair(key, value)); + if (packet_sent_times_.size() > kMaxPacketEventTimeMapSize) + packet_sent_times_.erase(packet_sent_times_.begin()); + } else { + std::pair<base::TimeTicks, CastLoggingEvent> value = it->second; + CastLoggingEvent recorded_type = value.second; + bool match = false; + base::TimeTicks packet_sent_time; + base::TimeTicks packet_received_time; + if (recorded_type == PACKET_SENT_TO_NETWORK && + packet_event.type == PACKET_RECEIVED) { + packet_sent_time = value.first; + packet_received_time = packet_event.timestamp; + match = true; + } else if (recorded_type == PACKET_RECEIVED && + packet_event.type == PACKET_SENT_TO_NETWORK) { + packet_sent_time = packet_event.timestamp; + packet_received_time = value.first; + match = true; + } + if (match) { + // Subtract by offset. + packet_received_time -= receiver_offset; + + total_network_latency_ += packet_received_time - packet_sent_time; + network_latency_datapoints_++; + packet_sent_times_.erase(it); + } + } +} + +void StatsEventSubscriber::PopulateFpsStat(base::TimeTicks end_time, + CastLoggingEvent event, + CastStat stat, + StatsMap* stats_map) const { + FrameStatsMap::const_iterator it = frame_stats_.find(event); + if (it != frame_stats_.end()) { + double fps = 0.0; + base::TimeDelta duration = (end_time - start_time_); + int count = it->second.event_counter; + if (duration > base::TimeDelta()) + fps = count / duration.InSecondsF(); + stats_map->insert(std::make_pair(stat, fps)); + } +} + +void StatsEventSubscriber::PopulatePlayoutDelayStat(StatsMap* stats_map) const { + FrameStatsMap::const_iterator it = frame_stats_.find(FRAME_PLAYOUT); + if (it != frame_stats_.end()) { + double avg_delay_ms = 0.0; + base::TimeDelta sum_delay = it->second.sum_delay; + int count = it->second.event_counter; + if (count != 0) + avg_delay_ms = sum_delay.InMillisecondsF() / count; + stats_map->insert(std::make_pair(AVG_PLAYOUT_DELAY_MS, avg_delay_ms)); + } +} + +void StatsEventSubscriber::PopulateFrameBitrateStat(base::TimeTicks end_time, + StatsMap* stats_map) const { + FrameStatsMap::const_iterator it = frame_stats_.find(FRAME_ENCODED); + if (it != frame_stats_.end()) { + double kbps = 0.0; + base::TimeDelta duration = end_time - start_time_; + if (duration > base::TimeDelta()) { + kbps = it->second.sum_size / duration.InMillisecondsF() * 8; + } + + stats_map->insert(std::make_pair(ENCODE_KBPS, kbps)); + } +} + +void StatsEventSubscriber::PopulatePacketBitrateStat( + base::TimeTicks end_time, + CastLoggingEvent event, + CastStat stat, + StatsMap* stats_map) const { + PacketStatsMap::const_iterator it = packet_stats_.find(event); + if (it != packet_stats_.end()) { + double kbps = 0; + base::TimeDelta duration = end_time - start_time_; + if (duration > base::TimeDelta()) { + kbps = it->second.sum_size / duration.InMillisecondsF() * 8; + } + + stats_map->insert(std::make_pair(stat, kbps)); + } +} + +void StatsEventSubscriber::PopulatePacketLossPercentageStat( + StatsMap* stats_map) const { + // We assume that retransmission means that the packet's previous + // (re)transmission was lost. + // This means the percentage of packet loss is + // (# of retransmit events) / (# of transmit + retransmit events). + PacketStatsMap::const_iterator sent_it = + packet_stats_.find(PACKET_SENT_TO_NETWORK); + if (sent_it == packet_stats_.end()) + return; + PacketStatsMap::const_iterator retransmitted_it = + packet_stats_.find(PACKET_RETRANSMITTED); + int sent_count = sent_it->second.event_counter; + int retransmitted_count = 0; + if (retransmitted_it != packet_stats_.end()) + retransmitted_count = retransmitted_it->second.event_counter; + double packet_loss_fraction = static_cast<double>(retransmitted_count) / + (sent_count + retransmitted_count); + stats_map->insert( + std::make_pair(PACKET_LOSS_FRACTION, packet_loss_fraction)); +} + +StatsEventSubscriber::FrameLogStats::FrameLogStats() + : event_counter(0), sum_size(0) {} +StatsEventSubscriber::FrameLogStats::~FrameLogStats() {} + +StatsEventSubscriber::PacketLogStats::PacketLogStats() + : event_counter(0), sum_size(0) {} +StatsEventSubscriber::PacketLogStats::~PacketLogStats() {} + +} // namespace cast +} // namespace media diff --git a/chromium/media/cast/logging/stats_event_subscriber.h b/chromium/media/cast/logging/stats_event_subscriber.h new file mode 100644 index 00000000000..173378ab0b2 --- /dev/null +++ b/chromium/media/cast/logging/stats_event_subscriber.h @@ -0,0 +1,176 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_CAST_LOGGING_STATS_EVENT_SUBSCRIBER_H_ +#define MEDIA_CAST_LOGGING_STATS_EVENT_SUBSCRIBER_H_ + +#include "base/gtest_prod_util.h" +#include "base/memory/scoped_ptr.h" +#include "base/threading/thread_checker.h" +#include "base/time/tick_clock.h" +#include "media/cast/logging/logging_defines.h" +#include "media/cast/logging/raw_event_subscriber.h" +#include "media/cast/logging/receiver_time_offset_estimator.h" + +namespace base { +class DictionaryValue; +} + +namespace media { +namespace cast { + +class StatsEventSubscriberTest; + +// A RawEventSubscriber implementation that subscribes to events, +// and aggregates them into stats. +class StatsEventSubscriber : public RawEventSubscriber { + public: + StatsEventSubscriber(EventMediaType event_media_type, + base::TickClock* clock, + ReceiverTimeOffsetEstimator* offset_estimator); + + virtual ~StatsEventSubscriber(); + + // RawReventSubscriber implementations. + virtual void OnReceiveFrameEvent(const FrameEvent& frame_event) OVERRIDE; + virtual void OnReceivePacketEvent(const PacketEvent& packet_event) OVERRIDE; + + // Returns stats as a DictionaryValue. The dictionary contains one entry - + // "audio" or "video" pointing to an inner dictionary. + // The inner dictionary consists of string - double entries, where the string + // describes the name of the stat, and the double describes + // the value of the stat. See CastStat and StatsMap below. + scoped_ptr<base::DictionaryValue> GetStats() const; + + // Resets stats in this object. + void Reset(); + + private: + friend class StatsEventSubscriberTest; + FRIEND_TEST_ALL_PREFIXES(StatsEventSubscriberTest, EmptyStats); + FRIEND_TEST_ALL_PREFIXES(StatsEventSubscriberTest, Capture); + FRIEND_TEST_ALL_PREFIXES(StatsEventSubscriberTest, Encode); + FRIEND_TEST_ALL_PREFIXES(StatsEventSubscriberTest, Decode); + FRIEND_TEST_ALL_PREFIXES(StatsEventSubscriberTest, PlayoutDelay); + FRIEND_TEST_ALL_PREFIXES(StatsEventSubscriberTest, E2ELatency); + FRIEND_TEST_ALL_PREFIXES(StatsEventSubscriberTest, Packets); + + // Generic statistics given the raw data. More specific data (e.g. frame rate + // and bit rate) can be computed given the basic metrics. + // Some of the metrics will only be set when applicable, e.g. delay and size. + struct FrameLogStats { + FrameLogStats(); + ~FrameLogStats(); + int event_counter; + size_t sum_size; + base::TimeDelta sum_delay; + }; + + struct PacketLogStats { + PacketLogStats(); + ~PacketLogStats(); + int event_counter; + size_t sum_size; + }; + + enum CastStat { + // Capture frame rate. + CAPTURE_FPS, + // Encode frame rate. + ENCODE_FPS, + // Decode frame rate. + DECODE_FPS, + // Average encode duration in milliseconds. + // TODO(imcheng): This stat is not populated yet because we do not have + // the time when encode started. Record it in FRAME_ENCODED event. + AVG_ENCODE_TIME_MS, + // Average playout delay in milliseconds, with target delay already + // accounted for. Ideally, every frame should have a playout delay of 0. + AVG_PLAYOUT_DELAY_MS, + // Duration from when a packet is transmitted to when it is received. + // This measures latency from sender to receiver. + AVG_NETWORK_LATENCY_MS, + // Duration from when a frame is captured to when it should be played out. + AVG_E2E_LATENCY_MS, + // Encode bitrate in kbps. + ENCODE_KBPS, + // Packet transmission bitrate in kbps. + TRANSMISSION_KBPS, + // Packet retransmission bitrate in kbps. + RETRANSMISSION_KBPS, + // Fraction of packet loss. + PACKET_LOSS_FRACTION, + // Duration in milliseconds since last receiver response. + MS_SINCE_LAST_RECEIVER_RESPONSE + }; + + typedef std::map<CastStat, double> StatsMap; + typedef std::map<RtpTimestamp, base::TimeTicks> FrameEventTimeMap; + typedef std::map< + std::pair<RtpTimestamp, uint16>, + std::pair<base::TimeTicks, CastLoggingEvent> > + PacketEventTimeMap; + typedef std::map<CastLoggingEvent, FrameLogStats> FrameStatsMap; + typedef std::map<CastLoggingEvent, PacketLogStats> PacketStatsMap; + + static const char* CastStatToString(CastStat stat); + + // Assigns |stats_map| with stats data. Used for testing. + void GetStatsInternal(StatsMap* stats_map) const; + + bool GetReceiverOffset(base::TimeDelta* offset); + void RecordFrameCapturedTime(const FrameEvent& frame_event); + void RecordE2ELatency(const FrameEvent& frame_event); + void RecordPacketSentTime(const PacketEvent& packet_event); + void ErasePacketSentTime(const PacketEvent& packet_event); + void RecordNetworkLatency(const PacketEvent& packet_event); + void UpdateLastResponseTime(base::TimeTicks receiver_time); + + void PopulateFpsStat(base::TimeTicks now, + CastLoggingEvent event, + CastStat stat, + StatsMap* stats_map) const; + void PopulatePlayoutDelayStat(StatsMap* stats_map) const; + void PopulateFrameBitrateStat(base::TimeTicks now, StatsMap* stats_map) const; + void PopulatePacketBitrateStat(base::TimeTicks now, + CastLoggingEvent event, + CastStat stat, + StatsMap* stats_map) const; + void PopulatePacketLossPercentageStat(StatsMap* stats_map) const; + + const EventMediaType event_media_type_; + + // Not owned by this class. + base::TickClock* const clock_; + + // Not owned by this class. + ReceiverTimeOffsetEstimator* const offset_estimator_; + + FrameStatsMap frame_stats_; + PacketStatsMap packet_stats_; + + base::TimeDelta total_network_latency_; + int network_latency_datapoints_; + base::TimeDelta total_e2e_latency_; + int e2e_latency_datapoints_; + + base::TimeTicks last_response_received_time_; + + // Fixed size map to record when recent frames were captured. + FrameEventTimeMap frame_captured_times_; + + // Fixed size map to record when recent packets were sent. + PacketEventTimeMap packet_sent_times_; + + // Sender time assigned on creation and |Reset()|. + base::TimeTicks start_time_; + + base::ThreadChecker thread_checker_; + DISALLOW_COPY_AND_ASSIGN(StatsEventSubscriber); +}; + +} // namespace cast +} // namespace media + +#endif // MEDIA_CAST_LOGGING_STATS_EVENT_SUBSCRIBER_H_ diff --git a/chromium/media/cast/logging/stats_event_subscriber_unittest.cc b/chromium/media/cast/logging/stats_event_subscriber_unittest.cc new file mode 100644 index 00000000000..33faa020596 --- /dev/null +++ b/chromium/media/cast/logging/stats_event_subscriber_unittest.cc @@ -0,0 +1,401 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/rand_util.h" +#include "base/test/simple_test_tick_clock.h" +#include "base/time/tick_clock.h" +#include "media/cast/cast_environment.h" +#include "media/cast/logging/logging_defines.h" +#include "media/cast/logging/stats_event_subscriber.h" +#include "media/cast/test/fake_receiver_time_offset_estimator.h" +#include "media/cast/test/fake_single_thread_task_runner.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { +const int kReceiverOffsetSecs = 100; +} + +namespace media { +namespace cast { + +class StatsEventSubscriberTest : public ::testing::Test { + protected: + StatsEventSubscriberTest() + : sender_clock_(new base::SimpleTestTickClock()), + task_runner_(new test::FakeSingleThreadTaskRunner(sender_clock_)), + cast_environment_(new CastEnvironment( + scoped_ptr<base::TickClock>(sender_clock_).Pass(), + task_runner_, + task_runner_, + task_runner_)), + fake_offset_estimator_( + base::TimeDelta::FromSeconds(kReceiverOffsetSecs)) { + receiver_clock_.Advance(base::TimeDelta::FromSeconds(kReceiverOffsetSecs)); + cast_environment_->Logging()->AddRawEventSubscriber( + &fake_offset_estimator_); + } + + virtual ~StatsEventSubscriberTest() { + if (subscriber_.get()) + cast_environment_->Logging()->RemoveRawEventSubscriber(subscriber_.get()); + cast_environment_->Logging()->RemoveRawEventSubscriber( + &fake_offset_estimator_); + } + + void AdvanceClocks(base::TimeDelta delta) { + sender_clock_->Advance(delta); + receiver_clock_.Advance(delta); + } + + void Init(EventMediaType event_media_type) { + DCHECK(!subscriber_.get()); + subscriber_.reset(new StatsEventSubscriber( + event_media_type, cast_environment_->Clock(), &fake_offset_estimator_)); + cast_environment_->Logging()->AddRawEventSubscriber(subscriber_.get()); + } + + base::SimpleTestTickClock* sender_clock_; // Owned by CastEnvironment. + base::SimpleTestTickClock receiver_clock_; + scoped_refptr<test::FakeSingleThreadTaskRunner> task_runner_; + scoped_refptr<CastEnvironment> cast_environment_; + test::FakeReceiverTimeOffsetEstimator fake_offset_estimator_; + scoped_ptr<StatsEventSubscriber> subscriber_; +}; + +TEST_F(StatsEventSubscriberTest, Capture) { + Init(VIDEO_EVENT); + + uint32 rtp_timestamp = 0; + uint32 frame_id = 0; + int num_frames = 10; + base::TimeTicks start_time = sender_clock_->NowTicks(); + for (int i = 0; i < num_frames; i++) { + cast_environment_->Logging()->InsertFrameEvent(sender_clock_->NowTicks(), + FRAME_CAPTURE_BEGIN, + VIDEO_EVENT, + rtp_timestamp, + frame_id); + + AdvanceClocks(base::TimeDelta::FromMicroseconds(34567)); + rtp_timestamp += 90; + frame_id++; + } + + base::TimeTicks end_time = sender_clock_->NowTicks(); + + StatsEventSubscriber::StatsMap stats_map; + subscriber_->GetStatsInternal(&stats_map); + + StatsEventSubscriber::StatsMap::iterator it = + stats_map.find(StatsEventSubscriber::CAPTURE_FPS); + ASSERT_NE(it, stats_map.end()); + + base::TimeDelta duration = end_time - start_time; + EXPECT_DOUBLE_EQ( + it->second, + static_cast<double>(num_frames) / duration.InMillisecondsF() * 1000); +} + +TEST_F(StatsEventSubscriberTest, Encode) { + Init(VIDEO_EVENT); + + uint32 rtp_timestamp = 0; + uint32 frame_id = 0; + int num_frames = 10; + base::TimeTicks start_time = sender_clock_->NowTicks(); + int total_size = 0; + for (int i = 0; i < num_frames; i++) { + int size = 1000 + base::RandInt(-100, 100); + total_size += size; + cast_environment_->Logging()->InsertEncodedFrameEvent( + sender_clock_->NowTicks(), + FRAME_ENCODED, VIDEO_EVENT, + rtp_timestamp, + frame_id, + size, + true, + 5678); + + AdvanceClocks(base::TimeDelta::FromMicroseconds(35678)); + rtp_timestamp += 90; + frame_id++; + } + + base::TimeTicks end_time = sender_clock_->NowTicks(); + + StatsEventSubscriber::StatsMap stats_map; + subscriber_->GetStatsInternal(&stats_map); + + StatsEventSubscriber::StatsMap::iterator it = + stats_map.find(StatsEventSubscriber::ENCODE_FPS); + ASSERT_NE(it, stats_map.end()); + + base::TimeDelta duration = end_time - start_time; + EXPECT_DOUBLE_EQ( + it->second, + static_cast<double>(num_frames) / duration.InMillisecondsF() * 1000); + + it = stats_map.find(StatsEventSubscriber::ENCODE_KBPS); + ASSERT_NE(it, stats_map.end()); + + EXPECT_DOUBLE_EQ(it->second, + static_cast<double>(total_size) / duration.InMillisecondsF() * 8); +} + +TEST_F(StatsEventSubscriberTest, Decode) { + Init(VIDEO_EVENT); + + uint32 rtp_timestamp = 0; + uint32 frame_id = 0; + int num_frames = 10; + base::TimeTicks start_time = sender_clock_->NowTicks(); + for (int i = 0; i < num_frames; i++) { + cast_environment_->Logging()->InsertFrameEvent(receiver_clock_.NowTicks(), + FRAME_DECODED, VIDEO_EVENT, + rtp_timestamp, + frame_id); + + AdvanceClocks(base::TimeDelta::FromMicroseconds(36789)); + rtp_timestamp += 90; + frame_id++; + } + + base::TimeTicks end_time = sender_clock_->NowTicks(); + + StatsEventSubscriber::StatsMap stats_map; + subscriber_->GetStatsInternal(&stats_map); + + StatsEventSubscriber::StatsMap::iterator it = + stats_map.find(StatsEventSubscriber::DECODE_FPS); + ASSERT_NE(it, stats_map.end()); + + base::TimeDelta duration = end_time - start_time; + EXPECT_DOUBLE_EQ( + it->second, + static_cast<double>(num_frames) / duration.InMillisecondsF() * 1000); +} + +TEST_F(StatsEventSubscriberTest, PlayoutDelay) { + Init(VIDEO_EVENT); + + uint32 rtp_timestamp = 0; + uint32 frame_id = 0; + int num_frames = 10; + int total_delay_ms = 0; + for (int i = 0; i < num_frames; i++) { + int delay_ms = base::RandInt(-50, 50); + base::TimeDelta delay = base::TimeDelta::FromMilliseconds(delay_ms); + total_delay_ms += delay_ms; + cast_environment_->Logging()->InsertFrameEventWithDelay( + receiver_clock_.NowTicks(), + FRAME_PLAYOUT, + VIDEO_EVENT, + rtp_timestamp, + frame_id, + delay); + + AdvanceClocks(base::TimeDelta::FromMicroseconds(37890)); + rtp_timestamp += 90; + frame_id++; + } + + StatsEventSubscriber::StatsMap stats_map; + subscriber_->GetStatsInternal(&stats_map); + + StatsEventSubscriber::StatsMap::iterator it = + stats_map.find(StatsEventSubscriber::AVG_PLAYOUT_DELAY_MS); + ASSERT_NE(it, stats_map.end()); + + EXPECT_DOUBLE_EQ( + it->second, static_cast<double>(total_delay_ms) / num_frames); +} + +TEST_F(StatsEventSubscriberTest, E2ELatency) { + Init(VIDEO_EVENT); + + uint32 rtp_timestamp = 0; + uint32 frame_id = 0; + int num_frames = 10; + base::TimeDelta total_latency; + for (int i = 0; i < num_frames; i++) { + cast_environment_->Logging()->InsertFrameEvent(sender_clock_->NowTicks(), + FRAME_CAPTURE_BEGIN, + VIDEO_EVENT, + rtp_timestamp, + frame_id); + + int latency_micros = 100000 + base::RandInt(-5000, 50000); + base::TimeDelta latency = base::TimeDelta::FromMicroseconds(latency_micros); + AdvanceClocks(latency); + + int delay_micros = base::RandInt(-50000, 50000); + base::TimeDelta delay = base::TimeDelta::FromMilliseconds(delay_micros); + total_latency += latency + delay; + + cast_environment_->Logging()->InsertFrameEventWithDelay( + receiver_clock_.NowTicks(), + FRAME_PLAYOUT, + VIDEO_EVENT, + rtp_timestamp, + frame_id, + delay); + + rtp_timestamp += 90; + frame_id++; + } + + StatsEventSubscriber::StatsMap stats_map; + subscriber_->GetStatsInternal(&stats_map); + + StatsEventSubscriber::StatsMap::iterator it = + stats_map.find(StatsEventSubscriber::AVG_E2E_LATENCY_MS); + ASSERT_NE(it, stats_map.end()); + + EXPECT_DOUBLE_EQ( + it->second, total_latency.InMillisecondsF() / num_frames); +} + +TEST_F(StatsEventSubscriberTest, Packets) { + Init(VIDEO_EVENT); + + uint32 rtp_timestamp = 0; + int num_packets = 10; + int num_latency_recorded_packets = 0; + base::TimeTicks start_time = sender_clock_->NowTicks(); + int total_size = 0; + int retransmit_total_size = 0; + base::TimeDelta total_latency; + int num_packets_sent = 0; + int num_packets_retransmitted = 0; + // Every 2nd packet will be retransmitted once. + // Every 4th packet will be retransmitted twice. + // Every 8th packet will be retransmitted 3 times. + for (int i = 0; i < num_packets; i++) { + int size = 1000 + base::RandInt(-100, 100); + total_size += size; + + cast_environment_->Logging()->InsertPacketEvent(sender_clock_->NowTicks(), + PACKET_SENT_TO_NETWORK, + VIDEO_EVENT, + rtp_timestamp, + 0, + i, + num_packets - 1, + size); + num_packets_sent++; + + int latency_micros = 20000 + base::RandInt(-10000, 10000); + base::TimeDelta latency = base::TimeDelta::FromMicroseconds(latency_micros); + // Latency is only recorded for packets that aren't retransmitted. + if (i % 2 != 0) { + total_latency += latency; + num_latency_recorded_packets++; + } + + AdvanceClocks(latency); + + base::TimeTicks received_time = receiver_clock_.NowTicks(); + + // Retransmission 1. + AdvanceClocks(base::TimeDelta::FromMicroseconds(12345)); + if (i % 2 == 0) { + cast_environment_->Logging()->InsertPacketEvent( + receiver_clock_.NowTicks(), + PACKET_RETRANSMITTED, + VIDEO_EVENT, + rtp_timestamp, + 0, + i, + num_packets - 1, + size); + retransmit_total_size += size; + num_packets_sent++; + num_packets_retransmitted++; + } + + // Retransmission 2. + AdvanceClocks(base::TimeDelta::FromMicroseconds(13456)); + if (i % 4 == 0) { + cast_environment_->Logging()->InsertPacketEvent( + receiver_clock_.NowTicks(), + PACKET_RETRANSMITTED, + VIDEO_EVENT, + rtp_timestamp, + 0, + i, + num_packets - 1, + size); + retransmit_total_size += size; + num_packets_sent++; + num_packets_retransmitted++; + } + + // Retransmission 3. + AdvanceClocks(base::TimeDelta::FromMicroseconds(14567)); + if (i % 8 == 0) { + cast_environment_->Logging()->InsertPacketEvent( + receiver_clock_.NowTicks(), + PACKET_RETRANSMITTED, + VIDEO_EVENT, + rtp_timestamp, + 0, + i, + num_packets - 1, + size); + retransmit_total_size += size; + num_packets_sent++; + num_packets_retransmitted++; + } + + cast_environment_->Logging()->InsertPacketEvent(received_time, + PACKET_RECEIVED, + VIDEO_EVENT, + rtp_timestamp, + 0, + i, + num_packets - 1, + size); + } + + base::TimeTicks end_time = sender_clock_->NowTicks(); + base::TimeDelta duration = end_time - start_time; + + StatsEventSubscriber::StatsMap stats_map; + subscriber_->GetStatsInternal(&stats_map); + + // Measure AVG_NETWORK_LATENCY_MS, TRANSMISSION_KBPS, RETRANSMISSION_KBPS, + // and PACKET_LOSS_FRACTION. + StatsEventSubscriber::StatsMap::iterator it = + stats_map.find(StatsEventSubscriber::AVG_NETWORK_LATENCY_MS); + ASSERT_NE(it, stats_map.end()); + + EXPECT_DOUBLE_EQ( + it->second, + total_latency.InMillisecondsF() / num_latency_recorded_packets); + + it = stats_map.find(StatsEventSubscriber::TRANSMISSION_KBPS); + ASSERT_NE(it, stats_map.end()); + + EXPECT_DOUBLE_EQ(it->second, + static_cast<double>(total_size) / duration.InMillisecondsF() * 8); + + it = stats_map.find(StatsEventSubscriber::RETRANSMISSION_KBPS); + ASSERT_NE(it, stats_map.end()); + + EXPECT_DOUBLE_EQ(it->second, + static_cast<double>(retransmit_total_size) / + duration.InMillisecondsF() * 8); + + it = stats_map.find(StatsEventSubscriber::PACKET_LOSS_FRACTION); + ASSERT_NE(it, stats_map.end()); + + EXPECT_DOUBLE_EQ( + it->second, + static_cast<double>(num_packets_retransmitted) / num_packets_sent); +} + +} // namespace cast +} // namespace media diff --git a/chromium/media/cast/net/cast_net_defines.h b/chromium/media/cast/net/cast_net_defines.h deleted file mode 100644 index a9f1629a91a..00000000000 --- a/chromium/media/cast/net/cast_net_defines.h +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef MEDIA_CAST_NET_CAST_NET_DEFINES_H_ -#define MEDIA_CAST_NET_CAST_NET_DEFINES_H_ - -#include "base/basictypes.h" - -namespace media { -namespace cast { - -class FrameIdWrapHelper { - public: - FrameIdWrapHelper() - : first_(true), - frame_id_wrap_count_(0), - range_(kLowRange) {} - - uint32 MapTo32bitsFrameId(const uint8 over_the_wire_frame_id) { - if (first_) { - first_ = false; - if (over_the_wire_frame_id == 0xff) { - // Special case for startup. - return kStartFrameId; - } - } - - uint32 wrap_count = frame_id_wrap_count_; - switch (range_) { - case kLowRange: - if (over_the_wire_frame_id > kLowRangeThreshold && - over_the_wire_frame_id < kHighRangeThreshold) { - range_ = kMiddleRange; - } - if (over_the_wire_frame_id > kHighRangeThreshold) { - // Wrap count was incremented in High->Low transition, but this frame - // is 'old', actually from before the wrap count got incremented. - --wrap_count; - } - break; - case kMiddleRange: - if (over_the_wire_frame_id > kHighRangeThreshold) { - range_ = kHighRange; - } - break; - case kHighRange: - if (over_the_wire_frame_id < kLowRangeThreshold) { - // Wrap-around detected. - range_ = kLowRange; - ++frame_id_wrap_count_; - // Frame triggering wrap-around so wrap count should be incremented as - // as well to match |frame_id_wrap_count_|. - ++wrap_count; - } - break; - } - return (wrap_count << 8) + over_the_wire_frame_id; - } - - private: - enum Range { - kLowRange, - kMiddleRange, - kHighRange, - }; - - static const uint8 kLowRangeThreshold = 0x0f; - static const uint8 kHighRangeThreshold = 0xf0; - static const uint32 kStartFrameId = GG_UINT32_C(0xffffffff); - - bool first_; - uint32 frame_id_wrap_count_; - Range range_; -}; - - -} // namespace cast -} // namespace media - -#endif // MEDIA_CAST_NET_CAST_NET_DEFINES_H_ diff --git a/chromium/media/cast/net/pacing/mock_paced_packet_sender.h b/chromium/media/cast/net/pacing/mock_paced_packet_sender.h deleted file mode 100644 index 9933516f14c..00000000000 --- a/chromium/media/cast/net/pacing/mock_paced_packet_sender.h +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef MEDIA_CAST_NET_PACING_MOCK_PACED_PACKET_SENDER_H_ -#define MEDIA_CAST_NET_PACING_MOCK_PACED_PACKET_SENDER_H_ - -#include "media/cast/net/pacing/paced_sender.h" -#include "testing/gmock/include/gmock/gmock.h" - -namespace media { -namespace cast { - -class MockPacedPacketSender : public PacedPacketSender { - public: - MockPacedPacketSender(); - virtual ~MockPacedPacketSender(); - - MOCK_METHOD1(SendPackets, bool(const PacketList& packets)); - MOCK_METHOD1(ResendPackets, bool(const PacketList& packets)); - MOCK_METHOD1(SendRtcpPacket, bool(const Packet& packet)); -}; - -} // namespace cast -} // namespace media - -#endif // MEDIA_CAST_NET_PACING_MOCK_PACED_PACKET_SENDER_H_ diff --git a/chromium/media/cast/net/pacing/paced_sender.cc b/chromium/media/cast/net/pacing/paced_sender.cc deleted file mode 100644 index 8a07380df0d..00000000000 --- a/chromium/media/cast/net/pacing/paced_sender.cc +++ /dev/null @@ -1,148 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "media/cast/net/pacing/paced_sender.h" - -#include "base/bind.h" -#include "base/message_loop/message_loop.h" - -namespace media { -namespace cast { - -static const int64 kPacingIntervalMs = 10; -// Each frame will be split into no more than kPacingMaxBurstsPerFrame -// bursts of packets. -static const size_t kPacingMaxBurstsPerFrame = 3; - -PacedSender::PacedSender(scoped_refptr<CastEnvironment> cast_environment, - PacketSender* transport) - : cast_environment_(cast_environment), - burst_size_(1), - packets_sent_in_burst_(0), - transport_(transport), - weak_factory_(this) { - ScheduleNextSend(); -} - -PacedSender::~PacedSender() {} - -bool PacedSender::SendPackets(const PacketList& packets) { - DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); - cast_environment_->Logging()->InsertPacketListEvent(kPacketSentToPacer, - packets); - return SendPacketsToTransport(packets, &packet_list_); -} - -bool PacedSender::ResendPackets(const PacketList& packets) { - DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); - cast_environment_->Logging()->InsertPacketListEvent(kPacketRetransmited, - packets); - return SendPacketsToTransport(packets, &resend_packet_list_); -} - -bool PacedSender::SendPacketsToTransport(const PacketList& packets, - PacketList* packets_not_sent) { - DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); - UpdateBurstSize(packets.size()); - - if (!packets_not_sent->empty()) { - packets_not_sent->insert(packets_not_sent->end(), - packets.begin(), packets.end()); - return true; - } - PacketList packets_to_send; - PacketList::const_iterator first_to_store_it = packets.begin(); - - size_t max_packets_to_send_now = burst_size_ - packets_sent_in_burst_; - if (max_packets_to_send_now > 0) { - size_t packets_to_send_now = std::min(max_packets_to_send_now, - packets.size()); - - std::advance(first_to_store_it, packets_to_send_now); - packets_to_send.insert(packets_to_send.begin(), - packets.begin(), first_to_store_it); - } - packets_not_sent->insert(packets_not_sent->end(), - first_to_store_it, packets.end()); - packets_sent_in_burst_ += packets_to_send.size(); - if (packets_to_send.empty()) return true; - - cast_environment_->Logging()->InsertPacketListEvent(kPacketSentToNetwork, - packets); - return transport_->SendPackets(packets_to_send); -} - -bool PacedSender::SendRtcpPacket(const Packet& packet) { - DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); - // We pass the RTCP packets straight through. - return transport_->SendPacket(packet); -} - -void PacedSender::ScheduleNextSend() { - base::TimeDelta time_to_next = time_last_process_ - - cast_environment_->Clock()->NowTicks() + - base::TimeDelta::FromMilliseconds(kPacingIntervalMs); - - time_to_next = std::max(time_to_next, base::TimeDelta()); - - cast_environment_->PostDelayedTask(CastEnvironment::MAIN, FROM_HERE, - base::Bind(&PacedSender::SendNextPacketBurst, weak_factory_.GetWeakPtr()), - time_to_next); -} - -void PacedSender::SendNextPacketBurst() { - DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); - SendStoredPackets(); - time_last_process_ = cast_environment_->Clock()->NowTicks(); - ScheduleNextSend(); -} - -void PacedSender::SendStoredPackets() { - DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); - if (packet_list_.empty() && resend_packet_list_.empty()) return; - - size_t packets_to_send = burst_size_; - PacketList packets_to_resend; - - // Send our re-send packets first. - if (!resend_packet_list_.empty()) { - PacketList::iterator it = resend_packet_list_.begin(); - size_t packets_to_send_now = std::min(packets_to_send, - resend_packet_list_.size()); - std::advance(it, packets_to_send_now); - packets_to_resend.insert(packets_to_resend.begin(), - resend_packet_list_.begin(), it); - resend_packet_list_.erase(resend_packet_list_.begin(), it); - packets_to_send -= packets_to_resend.size(); - } - if (!packet_list_.empty() && packets_to_send > 0) { - PacketList::iterator it = packet_list_.begin(); - size_t packets_to_send_now = std::min(packets_to_send, - packet_list_.size()); - - std::advance(it, packets_to_send_now); - packets_to_resend.insert(packets_to_resend.end(), - packet_list_.begin(), it); - packet_list_.erase(packet_list_.begin(), it); - - if (packet_list_.empty()) { - burst_size_ = 1; // Reset burst size after we sent the last stored packet - packets_sent_in_burst_ = 0; - } - } - transport_->SendPackets(packets_to_resend); -} - -void PacedSender::UpdateBurstSize(size_t packets_to_send) { - DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); - packets_to_send = std::max(packets_to_send, - resend_packet_list_.size() + packet_list_.size()); - - packets_to_send += (kPacingMaxBurstsPerFrame - 1); // Round up. - burst_size_ = std::max(packets_to_send / kPacingMaxBurstsPerFrame, - burst_size_); -} - -} // namespace cast -} // namespace media diff --git a/chromium/media/cast/net/pacing/paced_sender.gyp b/chromium/media/cast/net/pacing/paced_sender.gyp deleted file mode 100644 index 1947dd4ec40..00000000000 --- a/chromium/media/cast/net/pacing/paced_sender.gyp +++ /dev/null @@ -1,22 +0,0 @@ -# Copyright 2013 The Chromium Authors. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - -{ - 'targets': [ - { - 'target_name': 'cast_paced_sender', - 'type': 'static_library', - 'include_dirs': [ - '<(DEPTH)/', - ], - 'sources': [ - 'paced_sender.h', - 'paced_sender.cc', - ], - 'dependencies': [ - '<(DEPTH)/base/base.gyp:base', - ], - }, - ], # targets -} diff --git a/chromium/media/cast/net/pacing/paced_sender.h b/chromium/media/cast/net/pacing/paced_sender.h deleted file mode 100644 index 89283257134..00000000000 --- a/chromium/media/cast/net/pacing/paced_sender.h +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef MEDIA_CAST_NET_PACING_PACED_SENDER_H_ -#define MEDIA_CAST_NET_PACING_PACED_SENDER_H_ - -#include <list> -#include <vector> - -#include "base/basictypes.h" -#include "base/memory/scoped_ptr.h" -#include "base/memory/weak_ptr.h" -#include "base/threading/non_thread_safe.h" -#include "base/time/default_tick_clock.h" -#include "base/time/tick_clock.h" -#include "base/time/time.h" -#include "media/cast/cast_config.h" -#include "media/cast/cast_environment.h" - -namespace media { -namespace cast { - -// We have this pure virtual class to enable mocking. -class PacedPacketSender { - public: - // Inform the pacer / sender of the total number of packets. - virtual bool SendPackets(const PacketList& packets) = 0; - - virtual bool ResendPackets(const PacketList& packets) = 0; - - virtual bool SendRtcpPacket(const Packet& packet) = 0; - - virtual ~PacedPacketSender() {} -}; - -class PacedSender : public PacedPacketSender, - public base::NonThreadSafe, - public base::SupportsWeakPtr<PacedSender> { - public: - PacedSender(scoped_refptr<CastEnvironment> cast_environment, - PacketSender* transport); - virtual ~PacedSender(); - - virtual bool SendPackets(const PacketList& packets) OVERRIDE; - - virtual bool ResendPackets(const PacketList& packets) OVERRIDE; - - virtual bool SendRtcpPacket(const Packet& packet) OVERRIDE; - - protected: - // Schedule a delayed task on the main cast thread when it's time to send the - // next packet burst. - void ScheduleNextSend(); - - // Process any pending packets in the queue(s). - void SendNextPacketBurst(); - - private: - bool SendPacketsToTransport(const PacketList& packets, - PacketList* packets_not_sent); - void SendStoredPackets(); - void UpdateBurstSize(size_t num_of_packets); - - scoped_refptr<CastEnvironment> cast_environment_; - size_t burst_size_; - size_t packets_sent_in_burst_; - base::TimeTicks time_last_process_; - // Note: We can't combine the |packet_list_| and the |resend_packet_list_| - // since then we might get reordering of the retransmitted packets. - PacketList packet_list_; - PacketList resend_packet_list_; - PacketSender* transport_; - - base::WeakPtrFactory<PacedSender> weak_factory_; - - DISALLOW_COPY_AND_ASSIGN(PacedSender); -}; - -} // namespace cast -} // namespace media - -#endif // MEDIA_CAST_NET_PACING_PACED_SENDER_H_ diff --git a/chromium/media/cast/net/pacing/paced_sender_unittest.cc b/chromium/media/cast/net/pacing/paced_sender_unittest.cc deleted file mode 100644 index 15b81362f69..00000000000 --- a/chromium/media/cast/net/pacing/paced_sender_unittest.cc +++ /dev/null @@ -1,257 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "base/test/simple_test_tick_clock.h" -#include "media/cast/net/pacing/paced_sender.h" -#include "media/cast/test/fake_task_runner.h" -#include "testing/gmock/include/gmock/gmock.h" - -namespace media { -namespace cast { - -using testing::_; - -static const uint8 kValue = 123; -static const size_t kSize1 = 100; -static const size_t kSize2 = 101; -static const size_t kSize3 = 102; -static const size_t kSize4 = 103; -static const size_t kNackSize = 104; -static const int64 kStartMillisecond = GG_INT64_C(12345678900000); - -class TestPacketSender : public PacketSender { - public: - virtual bool SendPackets(const PacketList& packets) OVERRIDE { - PacketList::const_iterator it = packets.begin(); - for (; it != packets.end(); ++it) { - EXPECT_FALSE(expected_packet_size_.empty()); - size_t expected_packet_size = expected_packet_size_.front(); - expected_packet_size_.pop_front(); - EXPECT_EQ(expected_packet_size, it->size()); - } - return true; - } - - virtual bool SendPacket(const Packet& packet) OVERRIDE { - return true; - } - - void AddExpectedSize(int expected_packet_size, int repeat_count) { - for (int i = 0; i < repeat_count; ++i) { - expected_packet_size_.push_back(expected_packet_size); - } - } - - private: - std::list<int> expected_packet_size_; -}; - -class PacedSenderTest : public ::testing::Test { - protected: - PacedSenderTest() { - testing_clock_.Advance( - base::TimeDelta::FromMilliseconds(kStartMillisecond)); - } - - virtual ~PacedSenderTest() {} - - virtual void SetUp() { - task_runner_ = new test::FakeTaskRunner(&testing_clock_); - cast_environment_ = new CastEnvironment(&testing_clock_, task_runner_, - task_runner_, task_runner_, task_runner_, task_runner_, - GetDefaultCastLoggingConfig()); - paced_sender_.reset(new PacedSender(cast_environment_, &mock_transport_)); - } - - PacketList CreatePacketList(size_t packet_size, int num_of_packets_in_frame) { - PacketList packets; - for (int i = 0; i < num_of_packets_in_frame; ++i) { - packets.push_back(Packet(packet_size, kValue)); - } - return packets; - } - - base::SimpleTestTickClock testing_clock_; - TestPacketSender mock_transport_; - scoped_refptr<test::FakeTaskRunner> task_runner_; - scoped_ptr<PacedSender> paced_sender_; - scoped_refptr<CastEnvironment> cast_environment_; -}; - -TEST_F(PacedSenderTest, PassThroughRtcp) { - mock_transport_.AddExpectedSize(kSize1, 1); - PacketList packets = CreatePacketList(kSize1, 1); - - EXPECT_TRUE(paced_sender_->SendPackets(packets)); - EXPECT_TRUE(paced_sender_->ResendPackets(packets)); - - mock_transport_.AddExpectedSize(kSize2, 1); - EXPECT_TRUE(paced_sender_->SendRtcpPacket(Packet(kSize2, kValue))); -} - -TEST_F(PacedSenderTest, BasicPace) { - int num_of_packets = 9; - PacketList packets = CreatePacketList(kSize1, num_of_packets); - - mock_transport_.AddExpectedSize(kSize1, 3); - EXPECT_TRUE(paced_sender_->SendPackets(packets)); - - // Check that we get the next burst. - mock_transport_.AddExpectedSize(kSize1, 3); - - base::TimeDelta timeout = base::TimeDelta::FromMilliseconds(10); - testing_clock_.Advance(timeout); - task_runner_->RunTasks(); - - // If we call process too early make sure we don't send any packets. - timeout = base::TimeDelta::FromMilliseconds(5); - testing_clock_.Advance(timeout); - task_runner_->RunTasks(); - - // Check that we get the next burst. - mock_transport_.AddExpectedSize(kSize1, 3); - testing_clock_.Advance(timeout); - task_runner_->RunTasks(); - - // Check that we don't get any more packets. - testing_clock_.Advance(timeout); - task_runner_->RunTasks(); -} - -TEST_F(PacedSenderTest, PaceWithNack) { - // Testing what happen when we get multiple NACK requests for a fully lost - // frames just as we sent the first packets in a frame. - int num_of_packets_in_frame = 9; - int num_of_packets_in_nack = 9; - - PacketList first_frame_packets = - CreatePacketList(kSize1, num_of_packets_in_frame); - - PacketList second_frame_packets = - CreatePacketList(kSize2, num_of_packets_in_frame); - - PacketList nack_packets = - CreatePacketList(kNackSize, num_of_packets_in_nack); - - // Check that the first burst of the frame go out on the wire. - mock_transport_.AddExpectedSize(kSize1, 3); - EXPECT_TRUE(paced_sender_->SendPackets(first_frame_packets)); - - // Add first NACK request. - EXPECT_TRUE(paced_sender_->ResendPackets(nack_packets)); - - // Check that we get the first NACK burst. - mock_transport_.AddExpectedSize(kNackSize, 5); - base::TimeDelta timeout = base::TimeDelta::FromMilliseconds(10); - testing_clock_.Advance(timeout); - task_runner_->RunTasks(); - - // Add second NACK request. - EXPECT_TRUE(paced_sender_->ResendPackets(nack_packets)); - - // Check that we get the next NACK burst. - mock_transport_.AddExpectedSize(kNackSize, 7); - testing_clock_.Advance(timeout); - task_runner_->RunTasks(); - - // End of NACK plus a packet from the oldest frame. - mock_transport_.AddExpectedSize(kNackSize, 6); - mock_transport_.AddExpectedSize(kSize1, 1); - testing_clock_.Advance(timeout); - task_runner_->RunTasks(); - - // Add second frame. - // Make sure we don't delay the second frame due to the previous packets. - EXPECT_TRUE(paced_sender_->SendPackets(second_frame_packets)); - - // Last packets of frame 1 and the first packets of frame 2. - mock_transport_.AddExpectedSize(kSize1, 5); - mock_transport_.AddExpectedSize(kSize2, 2); - testing_clock_.Advance(timeout); - task_runner_->RunTasks(); - - // Last packets of frame 2. - mock_transport_.AddExpectedSize(kSize2, 7); - testing_clock_.Advance(timeout); - task_runner_->RunTasks(); - - // No more packets. - testing_clock_.Advance(timeout); - task_runner_->RunTasks(); -} - -TEST_F(PacedSenderTest, PaceWith60fps) { - // Testing what happen when we get multiple NACK requests for a fully lost - // frames just as we sent the first packets in a frame. - int num_of_packets_in_frame = 9; - - PacketList first_frame_packets = - CreatePacketList(kSize1, num_of_packets_in_frame); - - PacketList second_frame_packets = - CreatePacketList(kSize2, num_of_packets_in_frame); - - PacketList third_frame_packets = - CreatePacketList(kSize3, num_of_packets_in_frame); - - PacketList fourth_frame_packets = - CreatePacketList(kSize4, num_of_packets_in_frame); - - base::TimeDelta timeout_10ms = base::TimeDelta::FromMilliseconds(10); - - // Check that the first burst of the frame go out on the wire. - mock_transport_.AddExpectedSize(kSize1, 3); - EXPECT_TRUE(paced_sender_->SendPackets(first_frame_packets)); - - mock_transport_.AddExpectedSize(kSize1, 3); - testing_clock_.Advance(timeout_10ms); - task_runner_->RunTasks(); - - testing_clock_.Advance(base::TimeDelta::FromMilliseconds(6)); - - // Add second frame, after 16 ms. - EXPECT_TRUE(paced_sender_->SendPackets(second_frame_packets)); - testing_clock_.Advance(base::TimeDelta::FromMilliseconds(4)); - - mock_transport_.AddExpectedSize(kSize1, 3); - mock_transport_.AddExpectedSize(kSize2, 1); - testing_clock_.Advance(timeout_10ms); - task_runner_->RunTasks(); - - mock_transport_.AddExpectedSize(kSize2, 4); - testing_clock_.Advance(timeout_10ms); - task_runner_->RunTasks(); - - testing_clock_.Advance(base::TimeDelta::FromMilliseconds(3)); - - // Add third frame, after 33 ms. - EXPECT_TRUE(paced_sender_->SendPackets(third_frame_packets)); - mock_transport_.AddExpectedSize(kSize2, 4); - mock_transport_.AddExpectedSize(kSize3, 1); - - testing_clock_.Advance(base::TimeDelta::FromMilliseconds(7)); - task_runner_->RunTasks(); - - // Add fourth frame, after 50 ms. - EXPECT_TRUE(paced_sender_->SendPackets(fourth_frame_packets)); - - mock_transport_.AddExpectedSize(kSize3, 6); - testing_clock_.Advance(timeout_10ms); - task_runner_->RunTasks(); - - mock_transport_.AddExpectedSize(kSize3, 2); - mock_transport_.AddExpectedSize(kSize4, 4); - testing_clock_.Advance(timeout_10ms); - task_runner_->RunTasks(); - - mock_transport_.AddExpectedSize(kSize4, 5); - testing_clock_.Advance(timeout_10ms); - task_runner_->RunTasks(); - - testing_clock_.Advance(timeout_10ms); - task_runner_->RunTasks(); -} - -} // namespace cast -} // namespace media diff --git a/chromium/media/cast/net/rtp_sender/mock_rtp_sender.h b/chromium/media/cast/net/rtp_sender/mock_rtp_sender.h deleted file mode 100644 index 2c3f19f2ae9..00000000000 --- a/chromium/media/cast/net/rtp_sender/mock_rtp_sender.h +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef MEDIA_CAST_RTP_SENDER_MOCK_RTP_SENDER_H_ -#define MEDIA_CAST_RTP_SENDER_MOCK_RTP_SENDER_H_ - -#include <vector> - -#include "media/cast/net/rtp_sender/rtp_sender.h" -#include "testing/gmock/include/gmock/gmock.h" - -namespace media { -namespace cast { - -class MockRtpSender : public RtpSender { - public: - MOCK_METHOD2(IncomingEncodedVideoFrame, - bool(const EncodedVideoFrame& frame, int64 capture_time)); - - MOCK_METHOD2(IncomingEncodedAudioFrame, - bool(const EncodedAudioFrame& frame, int64 recorded_time)); - - MOCK_METHOD3(ResendPacket, - bool(bool is_audio, uint32 frame_id, uint16 packet_id)); - - MOCK_METHOD0(RtpStatistics, void()); -}; - -} // namespace cast -} // namespace media - -#endif // MEDIA_CAST_RTP_SENDER_MOCK_RTP_SENDER_H_ - diff --git a/chromium/media/cast/net/rtp_sender/packet_storage/packet_storage.cc b/chromium/media/cast/net/rtp_sender/packet_storage/packet_storage.cc deleted file mode 100644 index 3bd8f900665..00000000000 --- a/chromium/media/cast/net/rtp_sender/packet_storage/packet_storage.cc +++ /dev/null @@ -1,174 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "media/cast/net/rtp_sender/packet_storage/packet_storage.h" - -#include <string> - -#include "base/logging.h" -#include "media/cast/cast_defines.h" - -namespace media { -namespace cast { - -// Limit the max time delay to avoid frame id wrap around; 256 / 60 fps. -const int kMaxAllowedTimeStoredMs = 4000; - -typedef PacketMap::iterator PacketMapIterator; -typedef TimeToPacketMap::iterator TimeToPacketIterator; - -class StoredPacket { - public: - StoredPacket() { - packet_.reserve(kIpPacketSize); - } - - void Save(const Packet* packet) { - DCHECK_LT(packet->size(), kIpPacketSize) << "Invalid argument"; - packet_.clear(); - packet_.insert(packet_.begin(), packet->begin(), packet->end()); - } - - void GetCopy(PacketList* packets) { - packets->push_back(Packet(packet_.begin(), packet_.end())); - } - - private: - Packet packet_; -}; - -PacketStorage::PacketStorage(base::TickClock* clock, - int max_time_stored_ms) - : clock_(clock) { - max_time_stored_ = base::TimeDelta::FromMilliseconds(max_time_stored_ms); - DCHECK_LE(max_time_stored_ms, kMaxAllowedTimeStoredMs) << "Invalid argument"; -} - -PacketStorage::~PacketStorage() { - time_to_packet_map_.clear(); - - PacketMapIterator store_it = stored_packets_.begin(); - for (; store_it != stored_packets_.end(); - store_it = stored_packets_.begin()) { - stored_packets_.erase(store_it); - } - while (!free_packets_.empty()) { - free_packets_.pop_front(); - } -} - -void PacketStorage::CleanupOldPackets(base::TimeTicks now) { - TimeToPacketIterator time_it = time_to_packet_map_.begin(); - - // Check max size. - while (time_to_packet_map_.size() >= kMaxStoredPackets) { - PacketMapIterator store_it = stored_packets_.find(time_it->second); - - // We should always find the packet. - DCHECK(store_it != stored_packets_.end()) << "Invalid state"; - time_to_packet_map_.erase(time_it); - // Save the pointer. - linked_ptr<StoredPacket> storted_packet = store_it->second; - stored_packets_.erase(store_it); - // Add this packet to the free list for later re-use. - free_packets_.push_back(storted_packet); - time_it = time_to_packet_map_.begin(); - } - - // Time out old packets. - while (time_it != time_to_packet_map_.end()) { - if (now < time_it->first + max_time_stored_) { - break; - } - // Packet too old. - PacketMapIterator store_it = stored_packets_.find(time_it->second); - - // We should always find the packet. - DCHECK(store_it != stored_packets_.end()) << "Invalid state"; - time_to_packet_map_.erase(time_it); - // Save the pointer. - linked_ptr<StoredPacket> storted_packet = store_it->second; - stored_packets_.erase(store_it); - // Add this packet to the free list for later re-use. - free_packets_.push_back(storted_packet); - time_it = time_to_packet_map_.begin(); - } -} - -void PacketStorage::StorePacket(uint32 frame_id, uint16 packet_id, - const Packet* packet) { - base::TimeTicks now = clock_->NowTicks(); - CleanupOldPackets(now); - - // Internally we only use the 8 LSB of the frame id. - uint32 index = ((0xff & frame_id) << 16) + packet_id; - PacketMapIterator it = stored_packets_.find(index); - if (it != stored_packets_.end()) { - // We have already saved this. - DCHECK(false) << "Invalid state"; - return; - } - linked_ptr<StoredPacket> stored_packet; - if (free_packets_.empty()) { - // No previous allocated packets allocate one. - stored_packet.reset(new StoredPacket()); - } else { - // Re-use previous allocated packet. - stored_packet = free_packets_.front(); - free_packets_.pop_front(); - } - stored_packet->Save(packet); - stored_packets_[index] = stored_packet; - time_to_packet_map_.insert(std::make_pair(now, index)); -} - -PacketList PacketStorage::GetPackets( - const MissingFramesAndPacketsMap& missing_frames_and_packets) { - PacketList packets_to_resend; - - // Iterate over all frames in the list. - for (MissingFramesAndPacketsMap::const_iterator it = - missing_frames_and_packets.begin(); - it != missing_frames_and_packets.end(); ++it) { - uint8 frame_id = it->first; - const PacketIdSet& packets_set = it->second; - bool success = false; - - if (packets_set.empty()) { - VLOG(1) << "Missing all packets in frame " << static_cast<int>(frame_id); - - uint16 packet_id = 0; - do { - // Get packet from storage. - success = GetPacket(frame_id, packet_id, &packets_to_resend); - ++packet_id; - } while (success); - } else { - // Iterate over all of the packets in the frame. - for (PacketIdSet::const_iterator set_it = packets_set.begin(); - set_it != packets_set.end(); ++set_it) { - GetPacket(frame_id, *set_it, &packets_to_resend); - } - } - } - return packets_to_resend; -} - -bool PacketStorage::GetPacket(uint8 frame_id, - uint16 packet_id, - PacketList* packets) { - // Internally we only use the 8 LSB of the frame id. - uint32 index = (static_cast<uint32>(frame_id) << 16) + packet_id; - PacketMapIterator it = stored_packets_.find(index); - if (it == stored_packets_.end()) { - return false; - } - it->second->GetCopy(packets); - VLOG(1) << "Resend " << static_cast<int>(frame_id) - << ":" << packet_id; - return true; -} - -} // namespace cast -} // namespace media diff --git a/chromium/media/cast/net/rtp_sender/packet_storage/packet_storage.gyp b/chromium/media/cast/net/rtp_sender/packet_storage/packet_storage.gyp deleted file mode 100644 index f691d9e9b69..00000000000 --- a/chromium/media/cast/net/rtp_sender/packet_storage/packet_storage.gyp +++ /dev/null @@ -1,23 +0,0 @@ -# Copyright 2013 The Chromium Authors. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - -{ - 'targets': [ - { - 'target_name': 'packet_storage', - 'type': 'static_library', - 'include_dirs': [ - '<(DEPTH)/', - ], - 'sources': [ - 'packet_storage.h', - 'packet_storage.cc', - ], # source - 'dependencies': [ - '<(DEPTH)/base/base.gyp:base', - ], - }, - ], -} - diff --git a/chromium/media/cast/net/rtp_sender/packet_storage/packet_storage.h b/chromium/media/cast/net/rtp_sender/packet_storage/packet_storage.h deleted file mode 100644 index 34933ef5f6d..00000000000 --- a/chromium/media/cast/net/rtp_sender/packet_storage/packet_storage.h +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef MEDIA_CAST_NET_RTP_SENDER_PACKET_STORAGE_PACKET_STORAGE_H_ -#define MEDIA_CAST_NET_RTP_SENDER_PACKET_STORAGE_PACKET_STORAGE_H_ - -#include <list> -#include <map> -#include <vector> - -#include "base/basictypes.h" -#include "base/memory/linked_ptr.h" -#include "base/memory/scoped_ptr.h" -#include "base/time/tick_clock.h" -#include "base/time/time.h" -#include "media/cast/cast_config.h" - -namespace media { -namespace cast { - -class StoredPacket; -typedef std::map<uint32, linked_ptr<StoredPacket> > PacketMap; -typedef std::multimap<base::TimeTicks, uint32> TimeToPacketMap; - -class PacketStorage { - public: - static const int kMaxStoredPackets = 1000; - - PacketStorage(base::TickClock* clock, int max_time_stored_ms); - virtual ~PacketStorage(); - - void StorePacket(uint32 frame_id, uint16 packet_id, const Packet* packet); - - // Copies all missing packets into the packet list. - PacketList GetPackets( - const MissingFramesAndPacketsMap& missing_frames_and_packets); - - // Copies packet into the packet list. - bool GetPacket(uint8 frame_id, uint16 packet_id, PacketList* packets); - - private: - void CleanupOldPackets(base::TimeTicks now); - - base::TickClock* const clock_; // Not owned by this class. - base::TimeDelta max_time_stored_; - PacketMap stored_packets_; - TimeToPacketMap time_to_packet_map_; - std::list<linked_ptr<StoredPacket> > free_packets_; -}; - -} // namespace cast -} // namespace media - -#endif // MEDIA_CAST_NET_RTP_SENDER_PACKET_STORAGE_PACKET_STORAGE_H_ diff --git a/chromium/media/cast/net/rtp_sender/packet_storage/packet_storage_unittest.cc b/chromium/media/cast/net/rtp_sender/packet_storage/packet_storage_unittest.cc deleted file mode 100644 index 049d3ae29b6..00000000000 --- a/chromium/media/cast/net/rtp_sender/packet_storage/packet_storage_unittest.cc +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "media/cast/net/rtp_sender/packet_storage/packet_storage.h" - -#include <vector> - -#include "base/test/simple_test_tick_clock.h" -#include "base/time/time.h" -#include "testing/gmock/include/gmock/gmock.h" - -namespace media { -namespace cast { - -static const int kMaxDeltaStoredMs = 500; -static const base::TimeDelta kDeltaBetweenFrames = - base::TimeDelta::FromMilliseconds(33); - -static const int64 kStartMillisecond = GG_INT64_C(12345678900000); - -class PacketStorageTest : public ::testing::Test { - protected: - PacketStorageTest() : packet_storage_(&testing_clock_, kMaxDeltaStoredMs) { - testing_clock_.Advance( - base::TimeDelta::FromMilliseconds(kStartMillisecond)); - } - - base::SimpleTestTickClock testing_clock_; - PacketStorage packet_storage_; -}; - -TEST_F(PacketStorageTest, TimeOut) { - Packet test_123(100, 123); // 100 insertions of the value 123. - PacketList packets; - for (uint32 frame_id = 0; frame_id < 30; ++frame_id) { - for (uint16 packet_id = 0; packet_id < 10; ++packet_id) { - packet_storage_.StorePacket(frame_id, packet_id, &test_123); - } - testing_clock_.Advance(kDeltaBetweenFrames); - } - - // All packets belonging to the first 14 frames is expected to be expired. - for (uint32 frame_id = 0; frame_id < 14; ++frame_id) { - for (uint16 packet_id = 0; packet_id < 10; ++packet_id) { - Packet packet; - EXPECT_FALSE(packet_storage_.GetPacket(frame_id, packet_id, &packets)); - } - } - // All packets belonging to the next 15 frames is expected to be valid. - for (uint32 frame_id = 14; frame_id < 30; ++frame_id) { - for (uint16 packet_id = 0; packet_id < 10; ++packet_id) { - EXPECT_TRUE(packet_storage_.GetPacket(frame_id, packet_id, &packets)); - EXPECT_TRUE(packets.front() == test_123); - } - } -} - -TEST_F(PacketStorageTest, MaxNumberOfPackets) { - Packet test_123(100, 123); // 100 insertions of the value 123. - PacketList packets; - - uint32 frame_id = 0; - for (uint16 packet_id = 0; packet_id <= PacketStorage::kMaxStoredPackets; - ++packet_id) { - packet_storage_.StorePacket(frame_id, packet_id, &test_123); - } - Packet packet; - uint16 packet_id = 0; - EXPECT_FALSE(packet_storage_.GetPacket(frame_id, packet_id, &packets)); - - ++packet_id; - for (; packet_id <= PacketStorage::kMaxStoredPackets; ++packet_id) { - EXPECT_TRUE(packet_storage_.GetPacket(frame_id, packet_id, &packets)); - EXPECT_TRUE(packets.back() == test_123); - } -} - -TEST_F(PacketStorageTest, PacketContent) { - Packet test_123(100, 123); // 100 insertions of the value 123. - Packet test_234(200, 234); // 200 insertions of the value 234. - PacketList packets; - - for (uint32 frame_id = 0; frame_id < 10; ++frame_id) { - for (uint16 packet_id = 0; packet_id < 10; ++packet_id) { - // Every other packet. - if (packet_id % 2 == 0) { - packet_storage_.StorePacket(frame_id, packet_id, &test_123); - } else { - packet_storage_.StorePacket(frame_id, packet_id, &test_234); - } - } - testing_clock_.Advance(kDeltaBetweenFrames); - } - for (uint32 frame_id = 0; frame_id < 10; ++frame_id) { - for (uint16 packet_id = 0; packet_id < 10; ++packet_id) { - EXPECT_TRUE(packet_storage_.GetPacket(frame_id, packet_id, &packets)); - // Every other packet. - if (packet_id % 2 == 0) { - EXPECT_TRUE(packets.back() == test_123); - } else { - EXPECT_TRUE(packets.back() == test_234); - } - } - } -} - -} // namespace cast -} // namespace media - diff --git a/chromium/media/cast/net/rtp_sender/rtp_packetizer/rtp_packetizer.cc b/chromium/media/cast/net/rtp_sender/rtp_packetizer/rtp_packetizer.cc deleted file mode 100644 index 8a50f8a8aad..00000000000 --- a/chromium/media/cast/net/rtp_sender/rtp_packetizer/rtp_packetizer.cc +++ /dev/null @@ -1,153 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "media/cast/net/rtp_sender/rtp_packetizer/rtp_packetizer.h" - -#include "base/logging.h" -#include "media/cast/cast_defines.h" -#include "media/cast/net/pacing/paced_sender.h" -#include "net/base/big_endian.h" - -namespace media { -namespace cast { - -static const uint16 kCommonRtpHeaderLength = 12; -static const uint16 kCastRtpHeaderLength = 7; -static const uint8 kCastKeyFrameBitMask = 0x80; -static const uint8 kCastReferenceFrameIdBitMask = 0x40; - -RtpPacketizer::RtpPacketizer(PacedPacketSender* transport, - PacketStorage* packet_storage, - RtpPacketizerConfig rtp_packetizer_config) - : config_(rtp_packetizer_config), - transport_(transport), - packet_storage_(packet_storage), - sequence_number_(config_.sequence_number), - rtp_timestamp_(config_.rtp_timestamp), - packet_id_(0), - send_packets_count_(0), - send_octet_count_(0) { - DCHECK(transport) << "Invalid argument"; -} - -RtpPacketizer::~RtpPacketizer() {} - -void RtpPacketizer::IncomingEncodedVideoFrame( - const EncodedVideoFrame* video_frame, - const base::TimeTicks& capture_time) { - DCHECK(!config_.audio) << "Invalid state"; - if (config_.audio) return; - - // Timestamp is in 90 KHz for video. - rtp_timestamp_ = GetVideoRtpTimestamp(capture_time); - time_last_sent_rtp_timestamp_ = capture_time; - - Cast(video_frame->key_frame, - video_frame->frame_id, - video_frame->last_referenced_frame_id, - rtp_timestamp_, - video_frame->data); -} - -void RtpPacketizer::IncomingEncodedAudioFrame( - const EncodedAudioFrame* audio_frame, - const base::TimeTicks& recorded_time) { - DCHECK(config_.audio) << "Invalid state"; - if (!config_.audio) return; - - rtp_timestamp_ += audio_frame->samples; // Timestamp is in samples for audio. - time_last_sent_rtp_timestamp_ = recorded_time; - Cast(true, audio_frame->frame_id, 0, rtp_timestamp_, audio_frame->data); -} - -uint16 RtpPacketizer::NextSequenceNumber() { - ++sequence_number_; - return sequence_number_ - 1; -} - -bool RtpPacketizer::LastSentTimestamp(base::TimeTicks* time_sent, - uint32* rtp_timestamp) const { - if (time_last_sent_rtp_timestamp_.is_null()) return false; - - *time_sent = time_last_sent_rtp_timestamp_; - *rtp_timestamp = rtp_timestamp_; - return true; -} - -// TODO(mikhal): Switch to pass data with a const_ref. -void RtpPacketizer::Cast(bool is_key, - uint32 frame_id, - uint32 reference_frame_id, - uint32 timestamp, - const std::string& data) { - uint16 rtp_header_length = kCommonRtpHeaderLength + kCastRtpHeaderLength; - uint16 max_length = config_.max_payload_length - rtp_header_length - 1; - - // Split the payload evenly (round number up). - size_t num_packets = (data.size() + max_length) / max_length; - size_t payload_length = (data.size() + num_packets) / num_packets; - DCHECK_LE(payload_length, max_length) << "Invalid argument"; - - PacketList packets; - - size_t remaining_size = data.size(); - std::string::const_iterator data_iter = data.begin(); - while (remaining_size > 0) { - Packet packet; - - if (remaining_size < payload_length) { - payload_length = remaining_size; - } - remaining_size -= payload_length; - BuildCommonRTPheader(&packet, remaining_size == 0, timestamp); - - // Build Cast header. - packet.push_back( - (is_key ? kCastKeyFrameBitMask : 0) | kCastReferenceFrameIdBitMask); - packet.push_back(frame_id); - size_t start_size = packet.size(); - packet.resize(start_size + 4); - net::BigEndianWriter big_endian_writer(&(packet[start_size]), 4); - big_endian_writer.WriteU16(packet_id_); - big_endian_writer.WriteU16(static_cast<uint16>(num_packets - 1)); - packet.push_back(static_cast<uint8>(reference_frame_id)); - - // Copy payload data. - packet.insert(packet.end(), data_iter, data_iter + payload_length); - - // Store packet. - packet_storage_->StorePacket(frame_id, packet_id_, &packet); - ++packet_id_; - data_iter += payload_length; - - // Update stats. - ++send_packets_count_; - send_octet_count_ += payload_length; - packets.push_back(packet); - } - DCHECK(packet_id_ == num_packets) << "Invalid state"; - - // Send to network. - transport_->SendPackets(packets); - - // Prepare for next frame. - packet_id_ = 0; -} - -void RtpPacketizer::BuildCommonRTPheader( - Packet* packet, bool marker_bit, uint32 time_stamp) { - packet->push_back(0x80); - packet->push_back(static_cast<uint8>(config_.payload_type) | - (marker_bit ? kRtpMarkerBitMask : 0)); - size_t start_size = packet->size(); - packet->resize(start_size + 10); - net::BigEndianWriter big_endian_writer(&((*packet)[start_size]), 10); - big_endian_writer.WriteU16(sequence_number_); - big_endian_writer.WriteU32(time_stamp); - big_endian_writer.WriteU32(config_.ssrc); - ++sequence_number_; -} - -} // namespace cast -} // namespace media diff --git a/chromium/media/cast/net/rtp_sender/rtp_packetizer/rtp_packetizer.gyp b/chromium/media/cast/net/rtp_sender/rtp_packetizer/rtp_packetizer.gyp deleted file mode 100644 index d75d8a66911..00000000000 --- a/chromium/media/cast/net/rtp_sender/rtp_packetizer/rtp_packetizer.gyp +++ /dev/null @@ -1,27 +0,0 @@ -# Copyright 2013 The Chromium Authors. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - -{ - 'targets': [ - { - 'target_name': 'cast_rtp_packetizer', - 'type': 'static_library', - 'include_dirs': [ - '<(DEPTH)/', - '<(DEPTH)/third_party/', - '<(DEPTH)/third_party/webrtc/', - ], - 'sources': [ - 'rtp_packetizer.cc', - 'rtp_packetizer.h', - 'rtp_packetizer_config.cc', - 'rtp_packetizer_config.h', - ], # source - 'dependencies': [ - '<(DEPTH)/base/base.gyp:base', - '<(DEPTH)/net/net.gyp:net', - ], - }, - ], -} diff --git a/chromium/media/cast/net/rtp_sender/rtp_packetizer/rtp_packetizer.h b/chromium/media/cast/net/rtp_sender/rtp_packetizer/rtp_packetizer.h deleted file mode 100644 index 9f9be5fe163..00000000000 --- a/chromium/media/cast/net/rtp_sender/rtp_packetizer/rtp_packetizer.h +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef MEDIA_CAST_NET_RTP_SENDER_RTP_PACKETIZER_RTP_PACKETIZER_H_ -#define MEDIA_CAST_NET_RTP_SENDER_RTP_PACKETIZER_RTP_PACKETIZER_H_ - -#include <cmath> -#include <list> -#include <map> - -#include "base/time/time.h" -#include "media/cast/net/rtp_sender/packet_storage/packet_storage.h" -#include "media/cast/net/rtp_sender/rtp_packetizer/rtp_packetizer_config.h" - -namespace media { -namespace cast { - -class PacedPacketSender; - -// This object is only called from the main cast thread. -// This class break encoded audio and video frames into packets and add an RTP -// header to each packet. -class RtpPacketizer { - public: - RtpPacketizer(PacedPacketSender* transport, - PacketStorage* packet_storage, - RtpPacketizerConfig rtp_packetizer_config); - ~RtpPacketizer(); - - // The video_frame objects ownership is handled by the main cast thread. - void IncomingEncodedVideoFrame(const EncodedVideoFrame* video_frame, - const base::TimeTicks& capture_time); - - // The audio_frame objects ownership is handled by the main cast thread. - void IncomingEncodedAudioFrame(const EncodedAudioFrame* audio_frame, - const base::TimeTicks& recorded_time); - - bool LastSentTimestamp(base::TimeTicks* time_sent, - uint32* rtp_timestamp) const; - - // Return the next sequence number, and increment by one. Enables unique - // incremental sequence numbers for every packet (including retransmissions). - uint16 NextSequenceNumber(); - - int send_packets_count() { return send_packets_count_; } - - size_t send_octet_count() { return send_octet_count_; } - - private: - void Cast(bool is_key, uint32 frame_id, uint32 reference_frame_id, - uint32 timestamp, const std::string& data); - - void BuildCommonRTPheader(std::vector<uint8>* packet, bool marker_bit, - uint32 time_stamp); - - RtpPacketizerConfig config_; - PacedPacketSender* transport_; - PacketStorage* packet_storage_; - - base::TimeTicks time_last_sent_rtp_timestamp_; - uint16 sequence_number_; - uint32 rtp_timestamp_; - uint16 packet_id_; - - int send_packets_count_; - size_t send_octet_count_; -}; - -} // namespace cast -} // namespace media - -#endif // MEDIA_CAST_NET_RTP_SENDER_RTP_PACKETIZER_RTP_PACKETIZER_H_ diff --git a/chromium/media/cast/net/rtp_sender/rtp_packetizer/rtp_packetizer_config.cc b/chromium/media/cast/net/rtp_sender/rtp_packetizer/rtp_packetizer_config.cc deleted file mode 100644 index 5fe3a92b61b..00000000000 --- a/chromium/media/cast/net/rtp_sender/rtp_packetizer/rtp_packetizer_config.cc +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "media/cast/net/rtp_sender/rtp_packetizer/rtp_packetizer_config.h" - -namespace media { -namespace cast { - -RtpPacketizerConfig::RtpPacketizerConfig() - : ssrc(0), - max_payload_length(kIpPacketSize - 28), // Default is IP-v4/UDP. - audio(false), - frequency(8000), - payload_type(-1), - sequence_number(0), - rtp_timestamp(0) { -} - -} // namespace cast -} // namespace media diff --git a/chromium/media/cast/net/rtp_sender/rtp_packetizer/rtp_packetizer_config.h b/chromium/media/cast/net/rtp_sender/rtp_packetizer/rtp_packetizer_config.h deleted file mode 100644 index 1a2549e66b2..00000000000 --- a/chromium/media/cast/net/rtp_sender/rtp_packetizer/rtp_packetizer_config.h +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef CAST_NET_RTP_SENDER_RTP_PACKETIZER_RTP_PACKETIZER_CONFIG_H_ -#define CAST_NET_RTP_SENDER_RTP_PACKETIZER_RTP_PACKETIZER_CONFIG_H_ - -#include "media/cast/cast_config.h" -#include "media/cast/rtp_receiver/rtp_receiver_defines.h" - -namespace media { -namespace cast { - -struct RtpPacketizerConfig { - RtpPacketizerConfig(); - - // General. - bool audio; - int payload_type; - uint16 max_payload_length; - uint16 sequence_number; - uint32 rtp_timestamp; - int frequency; - - // SSRC. - unsigned int ssrc; - - // Video. - VideoCodec video_codec; - - // Audio. - uint8 channels; - AudioCodec audio_codec; -}; - -} // namespace cast -} // namespace media - -#endif // CAST_NET_RTP_SENDER_RTP_PACKETIZER_RTP_PACKETIZER_CONFIG_H_ diff --git a/chromium/media/cast/net/rtp_sender/rtp_packetizer/rtp_packetizer_unittest.cc b/chromium/media/cast/net/rtp_sender/rtp_packetizer/rtp_packetizer_unittest.cc deleted file mode 100644 index defdecf7584..00000000000 --- a/chromium/media/cast/net/rtp_sender/rtp_packetizer/rtp_packetizer_unittest.cc +++ /dev/null @@ -1,153 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "media/cast/net/rtp_sender/rtp_packetizer/rtp_packetizer.h" - -#include "base/memory/scoped_ptr.h" -#include "base/test/simple_test_tick_clock.h" -#include "media/cast/cast_config.h" -#include "media/cast/net/pacing/paced_sender.h" -#include "media/cast/net/rtp_sender/packet_storage/packet_storage.h" -#include "media/cast/net/rtp_sender/rtp_packetizer/test/rtp_header_parser.h" -#include "testing/gmock/include/gmock/gmock.h" - -namespace media { -namespace cast { - -static const int kPayload = 127; -static const uint32 kTimestampMs = 10; -static const uint16 kSeqNum = 33; -static const int kMaxPacketLength = 1500; -static const int kSsrc = 0x12345; -static const unsigned int kFrameSize = 5000; -static const int kMaxPacketStorageTimeMs = 300; - -class TestRtpPacketTransport : public PacedPacketSender { - public: - explicit TestRtpPacketTransport(RtpPacketizerConfig config) - : config_(config), - sequence_number_(kSeqNum), - packets_sent_(0), - expected_number_of_packets_(0), - expected_packet_id_(0), - expected_frame_id_(0) {} - - void VerifyRtpHeader(const RtpCastTestHeader& rtp_header) { - VerifyCommonRtpHeader(rtp_header); - VerifyCastRtpHeader(rtp_header); - } - - void VerifyCommonRtpHeader(const RtpCastTestHeader& rtp_header) { - EXPECT_EQ(expected_number_of_packets_ == packets_sent_, - rtp_header.marker); - EXPECT_EQ(kPayload, rtp_header.payload_type); - EXPECT_EQ(sequence_number_, rtp_header.sequence_number); - EXPECT_EQ(kTimestampMs * 90, rtp_header.rtp_timestamp); - EXPECT_EQ(config_.ssrc, rtp_header.ssrc); - EXPECT_EQ(0, rtp_header.num_csrcs); - } - - void VerifyCastRtpHeader(const RtpCastTestHeader& rtp_header) { - EXPECT_FALSE(rtp_header.is_key_frame); - EXPECT_EQ(expected_frame_id_, rtp_header.frame_id); - EXPECT_EQ(expected_packet_id_, rtp_header.packet_id); - EXPECT_EQ(expected_number_of_packets_ - 1, rtp_header.max_packet_id); - EXPECT_TRUE(rtp_header.is_reference); - EXPECT_EQ(expected_frame_id_ - 1u, rtp_header.reference_frame_id); - } - - virtual bool SendPackets(const PacketList& packets) OVERRIDE { - EXPECT_EQ(expected_number_of_packets_, static_cast<int>(packets.size())); - PacketList::const_iterator it = packets.begin(); - for (; it != packets.end(); ++it) { - ++packets_sent_; - RtpHeaderParser parser(it->data(), it->size()); - RtpCastTestHeader rtp_header; - parser.Parse(&rtp_header); - VerifyRtpHeader(rtp_header); - ++sequence_number_; - ++expected_packet_id_; - } - return true; - } - - virtual bool ResendPackets(const PacketList& packets) OVERRIDE { - EXPECT_TRUE(false); - return false; - } - - virtual bool SendRtcpPacket(const std::vector<uint8>& packet) OVERRIDE { - EXPECT_TRUE(false); - return false; - } - - void SetExpectedNumberOfPackets(int num) { - expected_number_of_packets_ = num; - } - - RtpPacketizerConfig config_; - uint32 sequence_number_; - int packets_sent_; - int expected_number_of_packets_; - // Assuming packets arrive in sequence. - int expected_packet_id_; - uint32 expected_frame_id_; -}; - -class RtpPacketizerTest : public ::testing::Test { - protected: - RtpPacketizerTest() - :video_frame_(), - packet_storage_(&testing_clock_, kMaxPacketStorageTimeMs) { - config_.sequence_number = kSeqNum; - config_.ssrc = kSsrc; - config_.payload_type = kPayload; - config_.max_payload_length = kMaxPacketLength; - transport_.reset(new TestRtpPacketTransport(config_)); - rtp_packetizer_.reset( - new RtpPacketizer(transport_.get(), &packet_storage_, config_)); - } - - virtual ~RtpPacketizerTest() {} - - virtual void SetUp() { - video_frame_.key_frame = false; - video_frame_.frame_id = 0; - video_frame_.last_referenced_frame_id = kStartFrameId; - video_frame_.data.assign(kFrameSize, 123); - } - - base::SimpleTestTickClock testing_clock_; - scoped_ptr<RtpPacketizer> rtp_packetizer_; - RtpPacketizerConfig config_; - scoped_ptr<TestRtpPacketTransport> transport_; - EncodedVideoFrame video_frame_; - PacketStorage packet_storage_; -}; - -TEST_F(RtpPacketizerTest, SendStandardPackets) { - int expected_num_of_packets = kFrameSize / kMaxPacketLength + 1; - transport_->SetExpectedNumberOfPackets(expected_num_of_packets); - - base::TimeTicks time; - time += base::TimeDelta::FromMilliseconds(kTimestampMs); - rtp_packetizer_->IncomingEncodedVideoFrame(&video_frame_, time); -} - -TEST_F(RtpPacketizerTest, Stats) { - EXPECT_FALSE(rtp_packetizer_->send_packets_count()); - EXPECT_FALSE(rtp_packetizer_->send_octet_count()); - // Insert packets at varying lengths. - int expected_num_of_packets = kFrameSize / kMaxPacketLength + 1; - transport_->SetExpectedNumberOfPackets(expected_num_of_packets); - - testing_clock_.Advance(base::TimeDelta::FromMilliseconds(kTimestampMs)); - rtp_packetizer_->IncomingEncodedVideoFrame(&video_frame_, - testing_clock_.NowTicks()); - EXPECT_EQ(expected_num_of_packets, rtp_packetizer_->send_packets_count()); - EXPECT_EQ(kFrameSize, rtp_packetizer_->send_octet_count()); -} - -} // namespace cast -} // namespace media diff --git a/chromium/media/cast/net/rtp_sender/rtp_sender.cc b/chromium/media/cast/net/rtp_sender/rtp_sender.cc deleted file mode 100644 index 2b017bc1784..00000000000 --- a/chromium/media/cast/net/rtp_sender/rtp_sender.cc +++ /dev/null @@ -1,145 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "media/cast/net/rtp_sender/rtp_sender.h" - -#include "base/logging.h" -#include "base/rand_util.h" -#include "media/cast/cast_defines.h" -#include "media/cast/net/pacing/paced_sender.h" -#include "media/cast/rtcp/rtcp_defines.h" -#include "net/base/big_endian.h" - -namespace media { -namespace cast { - -RtpSender::RtpSender(scoped_refptr<CastEnvironment> cast_environment, - const AudioSenderConfig* audio_config, - const VideoSenderConfig* video_config, - PacedPacketSender* transport) - : cast_environment_(cast_environment), - config_(), - transport_(transport) { - // Store generic cast config and create packetizer config. - DCHECK(audio_config || video_config) << "Invalid argument"; - if (audio_config) { - storage_.reset(new PacketStorage(cast_environment->Clock(), - audio_config->rtp_history_ms)); - config_.audio = true; - config_.ssrc = audio_config->sender_ssrc; - config_.payload_type = audio_config->rtp_payload_type; - config_.frequency = audio_config->frequency; - config_.audio_codec = audio_config->codec; - } else { - storage_.reset(new PacketStorage(cast_environment->Clock(), - video_config->rtp_history_ms)); - config_.audio = false; - config_.ssrc = video_config->sender_ssrc; - config_.payload_type = video_config->rtp_payload_type; - config_.frequency = kVideoFrequency; - config_.video_codec = video_config->codec; - } - // Randomly set start values. - config_.sequence_number = base::RandInt(0, 65535); - config_.rtp_timestamp = base::RandInt(0, 65535); - config_.rtp_timestamp += base::RandInt(0, 65535) << 16; - packetizer_.reset(new RtpPacketizer(transport, storage_.get(), config_)); -} - -RtpSender::~RtpSender() {} - -void RtpSender::IncomingEncodedVideoFrame(const EncodedVideoFrame* video_frame, - const base::TimeTicks& capture_time) { - packetizer_->IncomingEncodedVideoFrame(video_frame, capture_time); -} - -void RtpSender::IncomingEncodedAudioFrame(const EncodedAudioFrame* audio_frame, - const base::TimeTicks& recorded_time) { - packetizer_->IncomingEncodedAudioFrame(audio_frame, recorded_time); -} - -void RtpSender::ResendPackets( - const MissingFramesAndPacketsMap& missing_frames_and_packets) { - // Iterate over all frames in the list. - for (MissingFramesAndPacketsMap::const_iterator it = - missing_frames_and_packets.begin(); - it != missing_frames_and_packets.end(); ++it) { - PacketList packets_to_resend; - uint8 frame_id = it->first; - const PacketIdSet& packets_set = it->second; - bool success = false; - - if (packets_set.empty()) { - VLOG(1) << "Missing all packets in frame " << static_cast<int>(frame_id); - - uint16 packet_id = 0; - do { - // Get packet from storage. - success = storage_->GetPacket(frame_id, packet_id, &packets_to_resend); - - // Resend packet to the network. - if (success) { - VLOG(1) << "Resend " << static_cast<int>(frame_id) - << ":" << packet_id; - // Set a unique incremental sequence number for every packet. - Packet& packet = packets_to_resend.back(); - UpdateSequenceNumber(&packet); - // Set the size as correspond to each frame. - ++packet_id; - } - } while (success); - } else { - // Iterate over all of the packets in the frame. - for (PacketIdSet::const_iterator set_it = packets_set.begin(); - set_it != packets_set.end(); ++set_it) { - uint16 packet_id = *set_it; - success = storage_->GetPacket(frame_id, packet_id, &packets_to_resend); - - // Resend packet to the network. - if (success) { - VLOG(1) << "Resend " << static_cast<int>(frame_id) - << ":" << packet_id; - Packet& packet = packets_to_resend.back(); - UpdateSequenceNumber(&packet); - } - } - } - transport_->ResendPackets(packets_to_resend); - } -} - -void RtpSender::UpdateSequenceNumber(Packet* packet) { - uint16 new_sequence_number = packetizer_->NextSequenceNumber(); - int index = 2; - (*packet)[index] = (static_cast<uint8>(new_sequence_number)); - (*packet)[index + 1] =(static_cast<uint8>(new_sequence_number >> 8)); -} - -void RtpSender::RtpStatistics(const base::TimeTicks& now, - RtcpSenderInfo* sender_info) { - // The timestamp of this Rtcp packet should be estimated as the timestamp of - // the frame being captured at this moment. We are calculating that - // timestamp as the last frame's timestamp + the time since the last frame - // was captured. - uint32 ntp_seconds = 0; - uint32 ntp_fraction = 0; - ConvertTimeTicksToNtp(now, &ntp_seconds, &ntp_fraction); - sender_info->ntp_seconds = ntp_seconds; - sender_info->ntp_fraction = ntp_fraction; - - base::TimeTicks time_sent; - uint32 rtp_timestamp; - if (packetizer_->LastSentTimestamp(&time_sent, &rtp_timestamp)) { - base::TimeDelta time_since_last_send = now - time_sent; - sender_info->rtp_timestamp = rtp_timestamp + - time_since_last_send.InMilliseconds() * (config_.frequency / 1000); - } else { - sender_info->rtp_timestamp = 0; - } - sender_info->send_packet_count = packetizer_->send_packets_count(); - sender_info->send_octet_count = packetizer_->send_octet_count(); -} - -} // namespace cast -} // namespace media diff --git a/chromium/media/cast/net/rtp_sender/rtp_sender.gyp b/chromium/media/cast/net/rtp_sender/rtp_sender.gyp deleted file mode 100644 index f689b99b149..00000000000 --- a/chromium/media/cast/net/rtp_sender/rtp_sender.gyp +++ /dev/null @@ -1,26 +0,0 @@ -# Copyright 2013 The Chromium Authors. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - -{ - 'targets': [ - { - 'target_name': 'cast_rtp_sender', - 'type': 'static_library', - 'include_dirs': [ - '<(DEPTH)/', - '<(DEPTH)/third_party/', - '<(DEPTH)/third_party/webrtc/', - ], - 'sources': [ - 'rtp_sender.cc', - 'rtp_sender.h', - ], # source - 'dependencies': [ - '<(DEPTH)/base/base.gyp:base', - 'packet_storage/packet_storage.gyp:*', - 'rtp_packetizer/rtp_packetizer.gyp:*', - ], - }, - ], -} diff --git a/chromium/media/cast/net/rtp_sender/rtp_sender.h b/chromium/media/cast/net/rtp_sender/rtp_sender.h deleted file mode 100644 index 038165992db..00000000000 --- a/chromium/media/cast/net/rtp_sender/rtp_sender.h +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -// This file contains the interface to the cast RTP sender. - -#ifndef MEDIA_CAST_NET_RTP_SENDER_RTP_SENDER_H_ -#define MEDIA_CAST_NET_RTP_SENDER_RTP_SENDER_H_ - -#include <map> -#include <set> - -#include "base/memory/scoped_ptr.h" -#include "base/time/tick_clock.h" -#include "base/time/time.h" -#include "media/cast/cast_config.h" -#include "media/cast/cast_environment.h" -#include "media/cast/net/rtp_sender/packet_storage/packet_storage.h" -#include "media/cast/net/rtp_sender/rtp_packetizer/rtp_packetizer.h" -#include "media/cast/net/rtp_sender/rtp_packetizer/rtp_packetizer_config.h" - -namespace media { -namespace cast { - -class PacedPacketSender; -struct RtcpSenderInfo; - -// This object is only called from the main cast thread. -// This class handles splitting encoded audio and video frames into packets and -// add an RTP header to each packet. The sent packets are stored until they are -// acknowledged by the remote peer or timed out. -class RtpSender { - public: - RtpSender(scoped_refptr<CastEnvironment> cast_environment, - const AudioSenderConfig* audio_config, - const VideoSenderConfig* video_config, - PacedPacketSender* transport); - - ~RtpSender(); - - // The video_frame objects ownership is handled by the main cast thread. - void IncomingEncodedVideoFrame(const EncodedVideoFrame* video_frame, - const base::TimeTicks& capture_time); - - // The audio_frame objects ownership is handled by the main cast thread. - void IncomingEncodedAudioFrame(const EncodedAudioFrame* audio_frame, - const base::TimeTicks& recorded_time); - - void ResendPackets(const MissingFramesAndPacketsMap& missing_packets); - - void RtpStatistics(const base::TimeTicks& now, RtcpSenderInfo* sender_info); - - private: - void UpdateSequenceNumber(std::vector<uint8>* packet); - - scoped_refptr<CastEnvironment> cast_environment_; - RtpPacketizerConfig config_; - scoped_ptr<RtpPacketizer> packetizer_; - scoped_ptr<PacketStorage> storage_; - PacedPacketSender* transport_; -}; - -} // namespace cast -} // namespace media - -#endif // MEDIA_CAST_NET_RTP_SENDER_RTP_SENDER_H_ diff --git a/chromium/media/cast/receiver/audio_decoder.cc b/chromium/media/cast/receiver/audio_decoder.cc new file mode 100644 index 00000000000..a4d18968355 --- /dev/null +++ b/chromium/media/cast/receiver/audio_decoder.cc @@ -0,0 +1,246 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/cast/receiver/audio_decoder.h" + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/memory/ref_counted.h" +#include "base/sys_byteorder.h" +#include "media/cast/cast_defines.h" +#include "third_party/opus/src/include/opus.h" + +namespace media { +namespace cast { + +// Base class that handles the common problem of detecting dropped frames, and +// then invoking the Decode() method implemented by the subclasses to convert +// the encoded payload data into usable audio data. +class AudioDecoder::ImplBase + : public base::RefCountedThreadSafe<AudioDecoder::ImplBase> { + public: + ImplBase(const scoped_refptr<CastEnvironment>& cast_environment, + transport::AudioCodec codec, + int num_channels, + int sampling_rate) + : cast_environment_(cast_environment), + codec_(codec), + num_channels_(num_channels), + cast_initialization_status_(STATUS_AUDIO_UNINITIALIZED), + seen_first_frame_(false) { + if (num_channels_ <= 0 || sampling_rate <= 0 || sampling_rate % 100 != 0) + cast_initialization_status_ = STATUS_INVALID_AUDIO_CONFIGURATION; + } + + CastInitializationStatus InitializationResult() const { + return cast_initialization_status_; + } + + void DecodeFrame(scoped_ptr<transport::EncodedFrame> encoded_frame, + const DecodeFrameCallback& callback) { + DCHECK_EQ(cast_initialization_status_, STATUS_AUDIO_INITIALIZED); + + COMPILE_ASSERT(sizeof(encoded_frame->frame_id) == sizeof(last_frame_id_), + size_of_frame_id_types_do_not_match); + bool is_continuous = true; + if (seen_first_frame_) { + const uint32 frames_ahead = encoded_frame->frame_id - last_frame_id_; + if (frames_ahead > 1) { + RecoverBecauseFramesWereDropped(); + is_continuous = false; + } + } else { + seen_first_frame_ = true; + } + last_frame_id_ = encoded_frame->frame_id; + + scoped_ptr<AudioBus> decoded_audio = Decode( + encoded_frame->mutable_bytes(), + static_cast<int>(encoded_frame->data.size())); + cast_environment_->PostTask(CastEnvironment::MAIN, + FROM_HERE, + base::Bind(callback, + base::Passed(&decoded_audio), + is_continuous)); + } + + protected: + friend class base::RefCountedThreadSafe<ImplBase>; + virtual ~ImplBase() {} + + virtual void RecoverBecauseFramesWereDropped() {} + + // Note: Implementation of Decode() is allowed to mutate |data|. + virtual scoped_ptr<AudioBus> Decode(uint8* data, int len) = 0; + + const scoped_refptr<CastEnvironment> cast_environment_; + const transport::AudioCodec codec_; + const int num_channels_; + + // Subclass' ctor is expected to set this to STATUS_AUDIO_INITIALIZED. + CastInitializationStatus cast_initialization_status_; + + private: + bool seen_first_frame_; + uint32 last_frame_id_; + + DISALLOW_COPY_AND_ASSIGN(ImplBase); +}; + +class AudioDecoder::OpusImpl : public AudioDecoder::ImplBase { + public: + OpusImpl(const scoped_refptr<CastEnvironment>& cast_environment, + int num_channels, + int sampling_rate) + : ImplBase(cast_environment, + transport::kOpus, + num_channels, + sampling_rate), + decoder_memory_(new uint8[opus_decoder_get_size(num_channels)]), + opus_decoder_(reinterpret_cast<OpusDecoder*>(decoder_memory_.get())), + max_samples_per_frame_( + kOpusMaxFrameDurationMillis * sampling_rate / 1000), + buffer_(new float[max_samples_per_frame_ * num_channels]) { + if (ImplBase::cast_initialization_status_ != STATUS_AUDIO_UNINITIALIZED) + return; + if (opus_decoder_init(opus_decoder_, sampling_rate, num_channels) != + OPUS_OK) { + ImplBase::cast_initialization_status_ = + STATUS_INVALID_AUDIO_CONFIGURATION; + return; + } + ImplBase::cast_initialization_status_ = STATUS_AUDIO_INITIALIZED; + } + + private: + virtual ~OpusImpl() {} + + virtual void RecoverBecauseFramesWereDropped() OVERRIDE { + // Passing NULL for the input data notifies the decoder of frame loss. + const opus_int32 result = + opus_decode_float( + opus_decoder_, NULL, 0, buffer_.get(), max_samples_per_frame_, 0); + DCHECK_GE(result, 0); + } + + virtual scoped_ptr<AudioBus> Decode(uint8* data, int len) OVERRIDE { + scoped_ptr<AudioBus> audio_bus; + const opus_int32 num_samples_decoded = opus_decode_float( + opus_decoder_, data, len, buffer_.get(), max_samples_per_frame_, 0); + if (num_samples_decoded <= 0) + return audio_bus.Pass(); // Decode error. + + // Copy interleaved samples from |buffer_| into a new AudioBus (where + // samples are stored in planar format, for each channel). + audio_bus = AudioBus::Create(num_channels_, num_samples_decoded).Pass(); + // TODO(miu): This should be moved into AudioBus::FromInterleaved(). + for (int ch = 0; ch < num_channels_; ++ch) { + const float* src = buffer_.get() + ch; + const float* const src_end = src + num_samples_decoded * num_channels_; + float* dest = audio_bus->channel(ch); + for (; src < src_end; src += num_channels_, ++dest) + *dest = *src; + } + return audio_bus.Pass(); + } + + const scoped_ptr<uint8[]> decoder_memory_; + OpusDecoder* const opus_decoder_; + const int max_samples_per_frame_; + const scoped_ptr<float[]> buffer_; + + // According to documentation in third_party/opus/src/include/opus.h, we must + // provide enough space in |buffer_| to contain 120ms of samples. At 48 kHz, + // then, that means 5760 samples times the number of channels. + static const int kOpusMaxFrameDurationMillis = 120; + + DISALLOW_COPY_AND_ASSIGN(OpusImpl); +}; + +class AudioDecoder::Pcm16Impl : public AudioDecoder::ImplBase { + public: + Pcm16Impl(const scoped_refptr<CastEnvironment>& cast_environment, + int num_channels, + int sampling_rate) + : ImplBase(cast_environment, + transport::kPcm16, + num_channels, + sampling_rate) { + if (ImplBase::cast_initialization_status_ != STATUS_AUDIO_UNINITIALIZED) + return; + ImplBase::cast_initialization_status_ = STATUS_AUDIO_INITIALIZED; + } + + private: + virtual ~Pcm16Impl() {} + + virtual scoped_ptr<AudioBus> Decode(uint8* data, int len) OVERRIDE { + scoped_ptr<AudioBus> audio_bus; + const int num_samples = len / sizeof(int16) / num_channels_; + if (num_samples <= 0) + return audio_bus.Pass(); + + int16* const pcm_data = reinterpret_cast<int16*>(data); +#if defined(ARCH_CPU_LITTLE_ENDIAN) + // Convert endianness. + const int num_elements = num_samples * num_channels_; + for (int i = 0; i < num_elements; ++i) + pcm_data[i] = static_cast<int16>(base::NetToHost16(pcm_data[i])); +#endif + audio_bus = AudioBus::Create(num_channels_, num_samples).Pass(); + audio_bus->FromInterleaved(pcm_data, num_samples, sizeof(int16)); + return audio_bus.Pass(); + } + + DISALLOW_COPY_AND_ASSIGN(Pcm16Impl); +}; + +AudioDecoder::AudioDecoder( + const scoped_refptr<CastEnvironment>& cast_environment, + int channels, + int sampling_rate, + transport::AudioCodec codec) + : cast_environment_(cast_environment) { + switch (codec) { + case transport::kOpus: + impl_ = new OpusImpl(cast_environment, channels, sampling_rate); + break; + case transport::kPcm16: + impl_ = new Pcm16Impl(cast_environment, channels, sampling_rate); + break; + default: + NOTREACHED() << "Unknown or unspecified codec."; + break; + } +} + +AudioDecoder::~AudioDecoder() {} + +CastInitializationStatus AudioDecoder::InitializationResult() const { + if (impl_) + return impl_->InitializationResult(); + return STATUS_UNSUPPORTED_AUDIO_CODEC; +} + +void AudioDecoder::DecodeFrame( + scoped_ptr<transport::EncodedFrame> encoded_frame, + const DecodeFrameCallback& callback) { + DCHECK(encoded_frame.get()); + DCHECK(!callback.is_null()); + if (!impl_ || impl_->InitializationResult() != STATUS_AUDIO_INITIALIZED) { + callback.Run(make_scoped_ptr<AudioBus>(NULL), false); + return; + } + cast_environment_->PostTask(CastEnvironment::AUDIO, + FROM_HERE, + base::Bind(&AudioDecoder::ImplBase::DecodeFrame, + impl_, + base::Passed(&encoded_frame), + callback)); +} + +} // namespace cast +} // namespace media diff --git a/chromium/media/cast/receiver/audio_decoder.h b/chromium/media/cast/receiver/audio_decoder.h new file mode 100644 index 00000000000..c66735e4e64 --- /dev/null +++ b/chromium/media/cast/receiver/audio_decoder.h @@ -0,0 +1,64 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_CAST_RECEIVER_AUDIO_DECODER_H_ +#define MEDIA_CAST_RECEIVER_AUDIO_DECODER_H_ + +#include "base/callback.h" +#include "base/memory/ref_counted.h" +#include "media/base/audio_bus.h" +#include "media/cast/cast_config.h" +#include "media/cast/cast_environment.h" +#include "media/cast/transport/cast_transport_config.h" + +namespace media { +namespace cast { + +class AudioDecoder { + public: + // Callback passed to DecodeFrame, to deliver decoded audio data from the + // decoder. The number of samples in |audio_bus| may vary, and |audio_bus| + // can be NULL when errors occur. |is_continuous| is normally true, but will + // be false if the decoder has detected a frame skip since the last decode + // operation; and the client should take steps to smooth audio discontinuities + // in this case. + typedef base::Callback<void(scoped_ptr<AudioBus> audio_bus, + bool is_continuous)> DecodeFrameCallback; + + AudioDecoder(const scoped_refptr<CastEnvironment>& cast_environment, + int channels, + int sampling_rate, + transport::AudioCodec codec); + virtual ~AudioDecoder(); + + // Returns STATUS_AUDIO_INITIALIZED if the decoder was successfully + // constructed from the given FrameReceiverConfig. If this method returns any + // other value, calls to DecodeFrame() will not succeed. + CastInitializationStatus InitializationResult() const; + + // Decode the payload in |encoded_frame| asynchronously. |callback| will be + // invoked on the CastEnvironment::MAIN thread with the result. + // + // In the normal case, |encoded_frame->frame_id| will be + // monotonically-increasing by 1 for each successive call to this method. + // When it is not, the decoder will assume one or more frames have been + // dropped (e.g., due to packet loss), and will perform recovery actions. + void DecodeFrame(scoped_ptr<transport::EncodedFrame> encoded_frame, + const DecodeFrameCallback& callback); + + private: + class ImplBase; + class OpusImpl; + class Pcm16Impl; + + const scoped_refptr<CastEnvironment> cast_environment_; + scoped_refptr<ImplBase> impl_; + + DISALLOW_COPY_AND_ASSIGN(AudioDecoder); +}; + +} // namespace cast +} // namespace media + +#endif // MEDIA_CAST_RECEIVER_AUDIO_DECODER_H_ diff --git a/chromium/media/cast/receiver/audio_decoder_unittest.cc b/chromium/media/cast/receiver/audio_decoder_unittest.cc new file mode 100644 index 00000000000..6985a694232 --- /dev/null +++ b/chromium/media/cast/receiver/audio_decoder_unittest.cc @@ -0,0 +1,241 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/synchronization/condition_variable.h" +#include "base/synchronization/lock.h" +#include "base/sys_byteorder.h" +#include "base/time/time.h" +#include "media/cast/cast_config.h" +#include "media/cast/receiver/audio_decoder.h" +#include "media/cast/test/utility/audio_utility.h" +#include "media/cast/test/utility/standalone_cast_environment.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/opus/src/include/opus.h" + +namespace media { +namespace cast { + +namespace { +struct TestScenario { + transport::AudioCodec codec; + int num_channels; + int sampling_rate; + + TestScenario(transport::AudioCodec c, int n, int s) + : codec(c), num_channels(n), sampling_rate(s) {} +}; +} // namespace + +class AudioDecoderTest : public ::testing::TestWithParam<TestScenario> { + public: + AudioDecoderTest() + : cast_environment_(new StandaloneCastEnvironment()), + cond_(&lock_) {} + + protected: + virtual void SetUp() OVERRIDE { + audio_decoder_.reset(new AudioDecoder(cast_environment_, + GetParam().num_channels, + GetParam().sampling_rate, + GetParam().codec)); + CHECK_EQ(STATUS_AUDIO_INITIALIZED, audio_decoder_->InitializationResult()); + + audio_bus_factory_.reset( + new TestAudioBusFactory(GetParam().num_channels, + GetParam().sampling_rate, + TestAudioBusFactory::kMiddleANoteFreq, + 0.5f)); + last_frame_id_ = 0; + seen_a_decoded_frame_ = false; + + if (GetParam().codec == transport::kOpus) { + opus_encoder_memory_.reset( + new uint8[opus_encoder_get_size(GetParam().num_channels)]); + OpusEncoder* const opus_encoder = + reinterpret_cast<OpusEncoder*>(opus_encoder_memory_.get()); + CHECK_EQ(OPUS_OK, opus_encoder_init(opus_encoder, + GetParam().sampling_rate, + GetParam().num_channels, + OPUS_APPLICATION_AUDIO)); + CHECK_EQ(OPUS_OK, + opus_encoder_ctl(opus_encoder, OPUS_SET_BITRATE(OPUS_AUTO))); + } + + total_audio_feed_in_ = base::TimeDelta(); + total_audio_decoded_ = base::TimeDelta(); + } + + // Called from the unit test thread to create another EncodedFrame and push it + // into the decoding pipeline. + void FeedMoreAudio(const base::TimeDelta& duration, + int num_dropped_frames) { + // Prepare a simulated EncodedFrame to feed into the AudioDecoder. + scoped_ptr<transport::EncodedFrame> encoded_frame( + new transport::EncodedFrame()); + encoded_frame->dependency = transport::EncodedFrame::KEY; + encoded_frame->frame_id = last_frame_id_ + 1 + num_dropped_frames; + encoded_frame->referenced_frame_id = encoded_frame->frame_id; + last_frame_id_ = encoded_frame->frame_id; + + const scoped_ptr<AudioBus> audio_bus( + audio_bus_factory_->NextAudioBus(duration).Pass()); + + // Encode |audio_bus| into |encoded_frame->data|. + const int num_elements = audio_bus->channels() * audio_bus->frames(); + std::vector<int16> interleaved(num_elements); + audio_bus->ToInterleaved( + audio_bus->frames(), sizeof(int16), &interleaved.front()); + if (GetParam().codec == transport::kPcm16) { + encoded_frame->data.resize(num_elements * sizeof(int16)); + int16* const pcm_data = + reinterpret_cast<int16*>(encoded_frame->mutable_bytes()); + for (size_t i = 0; i < interleaved.size(); ++i) + pcm_data[i] = static_cast<int16>(base::HostToNet16(interleaved[i])); + } else if (GetParam().codec == transport::kOpus) { + OpusEncoder* const opus_encoder = + reinterpret_cast<OpusEncoder*>(opus_encoder_memory_.get()); + const int kOpusEncodeBufferSize = 4000; + encoded_frame->data.resize(kOpusEncodeBufferSize); + const int payload_size = + opus_encode(opus_encoder, + &interleaved.front(), + audio_bus->frames(), + encoded_frame->mutable_bytes(), + encoded_frame->data.size()); + CHECK_GT(payload_size, 1); + encoded_frame->data.resize(payload_size); + } else { + ASSERT_TRUE(false); // Not reached. + } + + { + base::AutoLock auto_lock(lock_); + total_audio_feed_in_ += duration; + } + + cast_environment_->PostTask( + CastEnvironment::MAIN, + FROM_HERE, + base::Bind(&AudioDecoder::DecodeFrame, + base::Unretained(audio_decoder_.get()), + base::Passed(&encoded_frame), + base::Bind(&AudioDecoderTest::OnDecodedFrame, + base::Unretained(this), + num_dropped_frames == 0))); + } + + // Blocks the caller until all audio that has been feed in has been decoded. + void WaitForAllAudioToBeDecoded() { + DCHECK(!cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); + base::AutoLock auto_lock(lock_); + while (total_audio_decoded_ < total_audio_feed_in_) + cond_.Wait(); + EXPECT_EQ(total_audio_feed_in_.InMicroseconds(), + total_audio_decoded_.InMicroseconds()); + } + + private: + // Called by |audio_decoder_| to deliver each frame of decoded audio. + void OnDecodedFrame(bool should_be_continuous, + scoped_ptr<AudioBus> audio_bus, + bool is_continuous) { + DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); + + // A NULL |audio_bus| indicates a decode error, which we don't expect. + ASSERT_FALSE(!audio_bus); + + // Did the decoder detect whether frames were dropped? + EXPECT_EQ(should_be_continuous, is_continuous); + + // Does the audio data seem to be intact? For Opus, we have to ignore the + // first frame seen at the start (and immediately after dropped packet + // recovery) because it introduces a tiny, significant delay. + bool examine_signal = true; + if (GetParam().codec == transport::kOpus) { + examine_signal = seen_a_decoded_frame_ && should_be_continuous; + seen_a_decoded_frame_ = true; + } + if (examine_signal) { + for (int ch = 0; ch < audio_bus->channels(); ++ch) { + EXPECT_NEAR( + TestAudioBusFactory::kMiddleANoteFreq * 2 * audio_bus->frames() / + GetParam().sampling_rate, + CountZeroCrossings(audio_bus->channel(ch), audio_bus->frames()), + 1); + } + } + + // Signal the main test thread that more audio was decoded. + base::AutoLock auto_lock(lock_); + total_audio_decoded_ += base::TimeDelta::FromSeconds(1) * + audio_bus->frames() / GetParam().sampling_rate; + cond_.Signal(); + } + + const scoped_refptr<StandaloneCastEnvironment> cast_environment_; + scoped_ptr<AudioDecoder> audio_decoder_; + scoped_ptr<TestAudioBusFactory> audio_bus_factory_; + uint32 last_frame_id_; + bool seen_a_decoded_frame_; + scoped_ptr<uint8[]> opus_encoder_memory_; + + base::Lock lock_; + base::ConditionVariable cond_; + base::TimeDelta total_audio_feed_in_; + base::TimeDelta total_audio_decoded_; + + DISALLOW_COPY_AND_ASSIGN(AudioDecoderTest); +}; + +TEST_P(AudioDecoderTest, DecodesFramesWithSameDuration) { + const base::TimeDelta kTenMilliseconds = + base::TimeDelta::FromMilliseconds(10); + const int kNumFrames = 10; + for (int i = 0; i < kNumFrames; ++i) + FeedMoreAudio(kTenMilliseconds, 0); + WaitForAllAudioToBeDecoded(); +} + +TEST_P(AudioDecoderTest, DecodesFramesWithVaryingDuration) { + // These are the set of frame durations supported by the Opus encoder. + const int kFrameDurationMs[] = { 5, 10, 20, 40, 60 }; + + const int kNumFrames = 10; + for (size_t i = 0; i < arraysize(kFrameDurationMs); ++i) + for (int j = 0; j < kNumFrames; ++j) + FeedMoreAudio(base::TimeDelta::FromMilliseconds(kFrameDurationMs[i]), 0); + WaitForAllAudioToBeDecoded(); +} + +TEST_P(AudioDecoderTest, RecoversFromDroppedFrames) { + const base::TimeDelta kTenMilliseconds = + base::TimeDelta::FromMilliseconds(10); + const int kNumFrames = 100; + int next_drop_at = 3; + int next_num_dropped = 1; + for (int i = 0; i < kNumFrames; ++i) { + if (i == next_drop_at) { + const int num_dropped = next_num_dropped++; + next_drop_at *= 2; + i += num_dropped; + FeedMoreAudio(kTenMilliseconds, num_dropped); + } else { + FeedMoreAudio(kTenMilliseconds, 0); + } + } + WaitForAllAudioToBeDecoded(); +} + +INSTANTIATE_TEST_CASE_P(AudioDecoderTestScenarios, + AudioDecoderTest, + ::testing::Values( + TestScenario(transport::kPcm16, 1, 8000), + TestScenario(transport::kPcm16, 2, 48000), + TestScenario(transport::kOpus, 1, 8000), + TestScenario(transport::kOpus, 2, 48000))); + +} // namespace cast +} // namespace media diff --git a/chromium/media/cast/receiver/cast_receiver_impl.cc b/chromium/media/cast/receiver/cast_receiver_impl.cc new file mode 100644 index 00000000000..7cff354c146 --- /dev/null +++ b/chromium/media/cast/receiver/cast_receiver_impl.cc @@ -0,0 +1,232 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/cast/receiver/cast_receiver_impl.h" + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/callback.h" +#include "base/debug/trace_event.h" +#include "base/logging.h" +#include "base/message_loop/message_loop.h" +#include "media/cast/receiver/audio_decoder.h" +#include "media/cast/receiver/video_decoder.h" + +namespace media { +namespace cast { + +scoped_ptr<CastReceiver> CastReceiver::Create( + scoped_refptr<CastEnvironment> cast_environment, + const FrameReceiverConfig& audio_config, + const FrameReceiverConfig& video_config, + transport::PacketSender* const packet_sender) { + return scoped_ptr<CastReceiver>(new CastReceiverImpl( + cast_environment, audio_config, video_config, packet_sender)); +} + +CastReceiverImpl::CastReceiverImpl( + scoped_refptr<CastEnvironment> cast_environment, + const FrameReceiverConfig& audio_config, + const FrameReceiverConfig& video_config, + transport::PacketSender* const packet_sender) + : cast_environment_(cast_environment), + pacer_(cast_environment->Clock(), + cast_environment->Logging(), + packet_sender, + cast_environment->GetTaskRunner(CastEnvironment::MAIN)), + audio_receiver_(cast_environment, audio_config, AUDIO_EVENT, &pacer_), + video_receiver_(cast_environment, video_config, VIDEO_EVENT, &pacer_), + ssrc_of_audio_sender_(audio_config.incoming_ssrc), + ssrc_of_video_sender_(video_config.incoming_ssrc), + num_audio_channels_(audio_config.channels), + audio_sampling_rate_(audio_config.frequency), + audio_codec_(audio_config.codec.audio), + video_codec_(video_config.codec.video) {} + +CastReceiverImpl::~CastReceiverImpl() {} + +void CastReceiverImpl::DispatchReceivedPacket(scoped_ptr<Packet> packet) { + const uint8_t* const data = &packet->front(); + const size_t length = packet->size(); + + uint32 ssrc_of_sender; + if (Rtcp::IsRtcpPacket(data, length)) { + ssrc_of_sender = Rtcp::GetSsrcOfSender(data, length); + } else if (!FrameReceiver::ParseSenderSsrc(data, length, &ssrc_of_sender)) { + VLOG(1) << "Invalid RTP packet."; + return; + } + + base::WeakPtr<FrameReceiver> target; + if (ssrc_of_sender == ssrc_of_video_sender_) { + target = video_receiver_.AsWeakPtr(); + } else if (ssrc_of_sender == ssrc_of_audio_sender_) { + target = audio_receiver_.AsWeakPtr(); + } else { + VLOG(1) << "Dropping packet with a non matching sender SSRC: " + << ssrc_of_sender; + return; + } + cast_environment_->PostTask( + CastEnvironment::MAIN, + FROM_HERE, + base::Bind(base::IgnoreResult(&FrameReceiver::ProcessPacket), + target, + base::Passed(&packet))); +} + +transport::PacketReceiverCallback CastReceiverImpl::packet_receiver() { + return base::Bind(&CastReceiverImpl::DispatchReceivedPacket, + // TODO(miu): This code structure is dangerous, since the + // callback could be stored and then invoked after + // destruction of |this|. + base::Unretained(this)); +} + +void CastReceiverImpl::RequestDecodedAudioFrame( + const AudioFrameDecodedCallback& callback) { + DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); + DCHECK(!callback.is_null()); + audio_receiver_.RequestEncodedFrame(base::Bind( + &CastReceiverImpl::DecodeEncodedAudioFrame, + // Note: Use of Unretained is safe since this Closure is guaranteed to be + // invoked or discarded by |audio_receiver_| before destruction of |this|. + base::Unretained(this), + callback)); +} + +void CastReceiverImpl::RequestEncodedAudioFrame( + const ReceiveEncodedFrameCallback& callback) { + DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); + audio_receiver_.RequestEncodedFrame(callback); +} + +void CastReceiverImpl::RequestDecodedVideoFrame( + const VideoFrameDecodedCallback& callback) { + DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); + DCHECK(!callback.is_null()); + video_receiver_.RequestEncodedFrame(base::Bind( + &CastReceiverImpl::DecodeEncodedVideoFrame, + // Note: Use of Unretained is safe since this Closure is guaranteed to be + // invoked or discarded by |video_receiver_| before destruction of |this|. + base::Unretained(this), + callback)); +} + +void CastReceiverImpl::RequestEncodedVideoFrame( + const ReceiveEncodedFrameCallback& callback) { + DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); + video_receiver_.RequestEncodedFrame(callback); +} + +void CastReceiverImpl::DecodeEncodedAudioFrame( + const AudioFrameDecodedCallback& callback, + scoped_ptr<transport::EncodedFrame> encoded_frame) { + DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); + if (!encoded_frame) { + callback.Run(make_scoped_ptr<AudioBus>(NULL), base::TimeTicks(), false); + return; + } + + if (!audio_decoder_) { + audio_decoder_.reset(new AudioDecoder(cast_environment_, + num_audio_channels_, + audio_sampling_rate_, + audio_codec_)); + } + const uint32 frame_id = encoded_frame->frame_id; + const uint32 rtp_timestamp = encoded_frame->rtp_timestamp; + const base::TimeTicks playout_time = encoded_frame->reference_time; + audio_decoder_->DecodeFrame( + encoded_frame.Pass(), + base::Bind(&CastReceiverImpl::EmitDecodedAudioFrame, + cast_environment_, + callback, + frame_id, + rtp_timestamp, + playout_time)); +} + +void CastReceiverImpl::DecodeEncodedVideoFrame( + const VideoFrameDecodedCallback& callback, + scoped_ptr<transport::EncodedFrame> encoded_frame) { + DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); + if (!encoded_frame) { + callback.Run( + make_scoped_refptr<VideoFrame>(NULL), base::TimeTicks(), false); + return; + } + + // Used by chrome/browser/extension/api/cast_streaming/performance_test.cc + TRACE_EVENT_INSTANT2( + "cast_perf_test", "PullEncodedVideoFrame", + TRACE_EVENT_SCOPE_THREAD, + "rtp_timestamp", encoded_frame->rtp_timestamp, + "render_time", encoded_frame->reference_time.ToInternalValue()); + + if (!video_decoder_) + video_decoder_.reset(new VideoDecoder(cast_environment_, video_codec_)); + const uint32 frame_id = encoded_frame->frame_id; + const uint32 rtp_timestamp = encoded_frame->rtp_timestamp; + const base::TimeTicks playout_time = encoded_frame->reference_time; + video_decoder_->DecodeFrame( + encoded_frame.Pass(), + base::Bind(&CastReceiverImpl::EmitDecodedVideoFrame, + cast_environment_, + callback, + frame_id, + rtp_timestamp, + playout_time)); +} + +// static +void CastReceiverImpl::EmitDecodedAudioFrame( + const scoped_refptr<CastEnvironment>& cast_environment, + const AudioFrameDecodedCallback& callback, + uint32 frame_id, + uint32 rtp_timestamp, + const base::TimeTicks& playout_time, + scoped_ptr<AudioBus> audio_bus, + bool is_continuous) { + DCHECK(cast_environment->CurrentlyOn(CastEnvironment::MAIN)); + if (audio_bus.get()) { + const base::TimeTicks now = cast_environment->Clock()->NowTicks(); + cast_environment->Logging()->InsertFrameEvent( + now, FRAME_DECODED, AUDIO_EVENT, rtp_timestamp, frame_id); + cast_environment->Logging()->InsertFrameEventWithDelay( + now, FRAME_PLAYOUT, AUDIO_EVENT, rtp_timestamp, frame_id, + playout_time - now); + } + callback.Run(audio_bus.Pass(), playout_time, is_continuous); +} + +// static +void CastReceiverImpl::EmitDecodedVideoFrame( + const scoped_refptr<CastEnvironment>& cast_environment, + const VideoFrameDecodedCallback& callback, + uint32 frame_id, + uint32 rtp_timestamp, + const base::TimeTicks& playout_time, + const scoped_refptr<VideoFrame>& video_frame, + bool is_continuous) { + DCHECK(cast_environment->CurrentlyOn(CastEnvironment::MAIN)); + if (video_frame) { + const base::TimeTicks now = cast_environment->Clock()->NowTicks(); + cast_environment->Logging()->InsertFrameEvent( + now, FRAME_DECODED, VIDEO_EVENT, rtp_timestamp, frame_id); + cast_environment->Logging()->InsertFrameEventWithDelay( + now, FRAME_PLAYOUT, VIDEO_EVENT, rtp_timestamp, frame_id, + playout_time - now); + + // Used by chrome/browser/extension/api/cast_streaming/performance_test.cc + TRACE_EVENT_INSTANT1( + "cast_perf_test", "FrameDecoded", + TRACE_EVENT_SCOPE_THREAD, + "rtp_timestamp", rtp_timestamp); + } + callback.Run(video_frame, playout_time, is_continuous); +} + +} // namespace cast +} // namespace media diff --git a/chromium/media/cast/receiver/cast_receiver_impl.h b/chromium/media/cast/receiver/cast_receiver_impl.h new file mode 100644 index 00000000000..c0dd5f38d10 --- /dev/null +++ b/chromium/media/cast/receiver/cast_receiver_impl.h @@ -0,0 +1,122 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_CAST_RECEIVER_CAST_RECEIVER_IMPL_H_ +#define MEDIA_CAST_RECEIVER_CAST_RECEIVER_IMPL_H_ + +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "media/cast/cast_config.h" +#include "media/cast/cast_environment.h" +#include "media/cast/cast_receiver.h" +#include "media/cast/receiver/frame_receiver.h" +#include "media/cast/transport/pacing/paced_sender.h" + +namespace media { +namespace cast { + +class AudioDecoder; +class VideoDecoder; + +// This is a pure owner class that groups all required receiver-related objects +// together, such as the paced packet sender, audio/video RTP frame receivers, +// and software decoders (created on-demand). +class CastReceiverImpl : public CastReceiver { + public: + CastReceiverImpl(scoped_refptr<CastEnvironment> cast_environment, + const FrameReceiverConfig& audio_config, + const FrameReceiverConfig& video_config, + transport::PacketSender* const packet_sender); + + virtual ~CastReceiverImpl(); + + // CastReceiver implementation. + virtual transport::PacketReceiverCallback packet_receiver() OVERRIDE; + virtual void RequestDecodedAudioFrame( + const AudioFrameDecodedCallback& callback) OVERRIDE; + virtual void RequestEncodedAudioFrame( + const ReceiveEncodedFrameCallback& callback) OVERRIDE; + virtual void RequestDecodedVideoFrame( + const VideoFrameDecodedCallback& callback) OVERRIDE; + virtual void RequestEncodedVideoFrame( + const ReceiveEncodedFrameCallback& callback) OVERRIDE; + + private: + // Forwards |packet| to a specific RTP frame receiver, or drops it if SSRC + // does not map to one of the receivers. + void DispatchReceivedPacket(scoped_ptr<Packet> packet); + + // Feeds an EncodedFrame into |audio_decoder_|. RequestDecodedAudioFrame() + // uses this as a callback for RequestEncodedAudioFrame(). + void DecodeEncodedAudioFrame( + const AudioFrameDecodedCallback& callback, + scoped_ptr<transport::EncodedFrame> encoded_frame); + + // Feeds an EncodedFrame into |video_decoder_|. RequestDecodedVideoFrame() + // uses this as a callback for RequestEncodedVideoFrame(). + void DecodeEncodedVideoFrame( + const VideoFrameDecodedCallback& callback, + scoped_ptr<transport::EncodedFrame> encoded_frame); + + // Receives an AudioBus from |audio_decoder_|, logs the event, and passes the + // data on by running the given |callback|. This method is static to ensure + // it can be called after a CastReceiverImpl instance is destroyed. + // DecodeEncodedAudioFrame() uses this as a callback for + // AudioDecoder::DecodeFrame(). + static void EmitDecodedAudioFrame( + const scoped_refptr<CastEnvironment>& cast_environment, + const AudioFrameDecodedCallback& callback, + uint32 frame_id, + uint32 rtp_timestamp, + const base::TimeTicks& playout_time, + scoped_ptr<AudioBus> audio_bus, + bool is_continuous); + + // Receives a VideoFrame from |video_decoder_|, logs the event, and passes the + // data on by running the given |callback|. This method is static to ensure + // it can be called after a CastReceiverImpl instance is destroyed. + // DecodeEncodedVideoFrame() uses this as a callback for + // VideoDecoder::DecodeFrame(). + static void EmitDecodedVideoFrame( + const scoped_refptr<CastEnvironment>& cast_environment, + const VideoFrameDecodedCallback& callback, + uint32 frame_id, + uint32 rtp_timestamp, + const base::TimeTicks& playout_time, + const scoped_refptr<VideoFrame>& video_frame, + bool is_continuous); + + const scoped_refptr<CastEnvironment> cast_environment_; + transport::PacedSender pacer_; + FrameReceiver audio_receiver_; + FrameReceiver video_receiver_; + + // Used by DispatchReceivedPacket() to direct packets to the appropriate frame + // receiver. + const uint32 ssrc_of_audio_sender_; + const uint32 ssrc_of_video_sender_; + + // Parameters for the decoders that are created on-demand. The values here + // might be nonsense if the client of CastReceiverImpl never intends to use + // the internal software-based decoders. + const int num_audio_channels_; + const int audio_sampling_rate_; + const transport::AudioCodec audio_codec_; + const transport::VideoCodec video_codec_; + + // Created on-demand to decode frames from |audio_receiver_| into AudioBuses + // for playback. + scoped_ptr<AudioDecoder> audio_decoder_; + + // Created on-demand to decode frames from |video_receiver_| into VideoFrame + // images for playback. + scoped_ptr<VideoDecoder> video_decoder_; + + DISALLOW_COPY_AND_ASSIGN(CastReceiverImpl); +}; + +} // namespace cast +} // namespace media + +#endif // MEDIA_CAST_RECEIVER_CAST_RECEIVER_IMPL_ diff --git a/chromium/media/cast/receiver/frame_receiver.cc b/chromium/media/cast/receiver/frame_receiver.cc new file mode 100644 index 00000000000..e189cc99a7f --- /dev/null +++ b/chromium/media/cast/receiver/frame_receiver.cc @@ -0,0 +1,326 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/cast/receiver/frame_receiver.h" + +#include <algorithm> + +#include "base/big_endian.h" +#include "base/bind.h" +#include "base/logging.h" +#include "base/message_loop/message_loop.h" +#include "media/cast/cast_environment.h" + +namespace { +const int kMinSchedulingDelayMs = 1; +} // namespace + +namespace media { +namespace cast { + +FrameReceiver::FrameReceiver( + const scoped_refptr<CastEnvironment>& cast_environment, + const FrameReceiverConfig& config, + EventMediaType event_media_type, + transport::PacedPacketSender* const packet_sender) + : cast_environment_(cast_environment), + packet_parser_(config.incoming_ssrc, config.rtp_payload_type), + stats_(cast_environment->Clock()), + event_media_type_(event_media_type), + event_subscriber_(kReceiverRtcpEventHistorySize, event_media_type), + rtp_timebase_(config.frequency), + target_playout_delay_( + base::TimeDelta::FromMilliseconds(config.rtp_max_delay_ms)), + expected_frame_duration_( + base::TimeDelta::FromSeconds(1) / config.max_frame_rate), + reports_are_scheduled_(false), + framer_(cast_environment->Clock(), + this, + config.incoming_ssrc, + true, + config.rtp_max_delay_ms * config.max_frame_rate / 1000), + rtcp_(cast_environment_, + NULL, + NULL, + packet_sender, + &stats_, + config.rtcp_mode, + base::TimeDelta::FromMilliseconds(config.rtcp_interval), + config.feedback_ssrc, + config.incoming_ssrc, + config.rtcp_c_name, + event_media_type), + is_waiting_for_consecutive_frame_(false), + lip_sync_drift_(ClockDriftSmoother::GetDefaultTimeConstant()), + weak_factory_(this) { + DCHECK_GT(config.rtp_max_delay_ms, 0); + DCHECK_GT(config.max_frame_rate, 0); + decryptor_.Initialize(config.aes_key, config.aes_iv_mask); + rtcp_.SetTargetDelay(target_playout_delay_); + cast_environment_->Logging()->AddRawEventSubscriber(&event_subscriber_); + memset(frame_id_to_rtp_timestamp_, 0, sizeof(frame_id_to_rtp_timestamp_)); +} + +FrameReceiver::~FrameReceiver() { + DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); + cast_environment_->Logging()->RemoveRawEventSubscriber(&event_subscriber_); +} + +void FrameReceiver::RequestEncodedFrame( + const ReceiveEncodedFrameCallback& callback) { + DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); + frame_request_queue_.push_back(callback); + EmitAvailableEncodedFrames(); +} + +bool FrameReceiver::ProcessPacket(scoped_ptr<Packet> packet) { + DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); + + if (Rtcp::IsRtcpPacket(&packet->front(), packet->size())) { + rtcp_.IncomingRtcpPacket(&packet->front(), packet->size()); + } else { + RtpCastHeader rtp_header; + const uint8* payload_data; + size_t payload_size; + if (!packet_parser_.ParsePacket(&packet->front(), + packet->size(), + &rtp_header, + &payload_data, + &payload_size)) { + return false; + } + + ProcessParsedPacket(rtp_header, payload_data, payload_size); + stats_.UpdateStatistics(rtp_header); + } + + if (!reports_are_scheduled_) { + ScheduleNextRtcpReport(); + ScheduleNextCastMessage(); + reports_are_scheduled_ = true; + } + + return true; +} + +// static +bool FrameReceiver::ParseSenderSsrc(const uint8* packet, + size_t length, + uint32* ssrc) { + base::BigEndianReader big_endian_reader( + reinterpret_cast<const char*>(packet), length); + return big_endian_reader.Skip(8) && big_endian_reader.ReadU32(ssrc); +} + +void FrameReceiver::ProcessParsedPacket(const RtpCastHeader& rtp_header, + const uint8* payload_data, + size_t payload_size) { + DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); + + const base::TimeTicks now = cast_environment_->Clock()->NowTicks(); + + frame_id_to_rtp_timestamp_[rtp_header.frame_id & 0xff] = + rtp_header.rtp_timestamp; + cast_environment_->Logging()->InsertPacketEvent( + now, PACKET_RECEIVED, event_media_type_, rtp_header.rtp_timestamp, + rtp_header.frame_id, rtp_header.packet_id, rtp_header.max_packet_id, + payload_size); + + bool duplicate = false; + const bool complete = + framer_.InsertPacket(payload_data, payload_size, rtp_header, &duplicate); + + // Duplicate packets are ignored. + if (duplicate) + return; + + // Update lip-sync values upon receiving the first packet of each frame, or if + // they have never been set yet. + if (rtp_header.packet_id == 0 || lip_sync_reference_time_.is_null()) { + RtpTimestamp fresh_sync_rtp; + base::TimeTicks fresh_sync_reference; + if (!rtcp_.GetLatestLipSyncTimes(&fresh_sync_rtp, &fresh_sync_reference)) { + // HACK: The sender should have provided Sender Reports before the first + // frame was sent. However, the spec does not currently require this. + // Therefore, when the data is missing, the local clock is used to + // generate reference timestamps. + VLOG(2) << "Lip sync info missing. Falling-back to local clock."; + fresh_sync_rtp = rtp_header.rtp_timestamp; + fresh_sync_reference = now; + } + // |lip_sync_reference_time_| is always incremented according to the time + // delta computed from the difference in RTP timestamps. Then, + // |lip_sync_drift_| accounts for clock drift and also smoothes-out any + // sudden/discontinuous shifts in the series of reference time values. + if (lip_sync_reference_time_.is_null()) { + lip_sync_reference_time_ = fresh_sync_reference; + } else { + lip_sync_reference_time_ += RtpDeltaToTimeDelta( + static_cast<int32>(fresh_sync_rtp - lip_sync_rtp_timestamp_), + rtp_timebase_); + } + lip_sync_rtp_timestamp_ = fresh_sync_rtp; + lip_sync_drift_.Update( + now, fresh_sync_reference - lip_sync_reference_time_); + } + + // Another frame is complete from a non-duplicate packet. Attempt to emit + // more frames to satisfy enqueued requests. + if (complete) + EmitAvailableEncodedFrames(); +} + +void FrameReceiver::CastFeedback(const RtcpCastMessage& cast_message) { + DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); + + base::TimeTicks now = cast_environment_->Clock()->NowTicks(); + RtpTimestamp rtp_timestamp = + frame_id_to_rtp_timestamp_[cast_message.ack_frame_id_ & 0xff]; + cast_environment_->Logging()->InsertFrameEvent( + now, FRAME_ACK_SENT, event_media_type_, + rtp_timestamp, cast_message.ack_frame_id_); + + ReceiverRtcpEventSubscriber::RtcpEventMultiMap rtcp_events; + event_subscriber_.GetRtcpEventsAndReset(&rtcp_events); + rtcp_.SendRtcpFromRtpReceiver(&cast_message, &rtcp_events); +} + +void FrameReceiver::EmitAvailableEncodedFrames() { + DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); + + while (!frame_request_queue_.empty()) { + // Attempt to peek at the next completed frame from the |framer_|. + // TODO(miu): We should only be peeking at the metadata, and not copying the + // payload yet! Or, at least, peek using a StringPiece instead of a copy. + scoped_ptr<transport::EncodedFrame> encoded_frame( + new transport::EncodedFrame()); + bool is_consecutively_next_frame = false; + bool have_multiple_complete_frames = false; + if (!framer_.GetEncodedFrame(encoded_frame.get(), + &is_consecutively_next_frame, + &have_multiple_complete_frames)) { + VLOG(1) << "Wait for more packets to produce a completed frame."; + return; // ProcessParsedPacket() will invoke this method in the future. + } + + const base::TimeTicks now = cast_environment_->Clock()->NowTicks(); + const base::TimeTicks playout_time = + GetPlayoutTime(encoded_frame->rtp_timestamp); + + // If we have multiple decodable frames, and the current frame is + // too old, then skip it and decode the next frame instead. + if (have_multiple_complete_frames && now > playout_time) { + framer_.ReleaseFrame(encoded_frame->frame_id); + continue; + } + + // If |framer_| has a frame ready that is out of sequence, examine the + // playout time to determine whether it's acceptable to continue, thereby + // skipping one or more frames. Skip if the missing frame wouldn't complete + // playing before the start of playback of the available frame. + if (!is_consecutively_next_frame) { + // TODO(miu): Also account for expected decode time here? + const base::TimeTicks earliest_possible_end_time_of_missing_frame = + now + expected_frame_duration_; + if (earliest_possible_end_time_of_missing_frame < playout_time) { + VLOG(1) << "Wait for next consecutive frame instead of skipping."; + if (!is_waiting_for_consecutive_frame_) { + is_waiting_for_consecutive_frame_ = true; + cast_environment_->PostDelayedTask( + CastEnvironment::MAIN, + FROM_HERE, + base::Bind(&FrameReceiver::EmitAvailableEncodedFramesAfterWaiting, + weak_factory_.GetWeakPtr()), + playout_time - now); + } + return; + } + } + + // Decrypt the payload data in the frame, if crypto is being used. + if (decryptor_.initialized()) { + std::string decrypted_data; + if (!decryptor_.Decrypt(encoded_frame->frame_id, + encoded_frame->data, + &decrypted_data)) { + // Decryption failed. Give up on this frame. + framer_.ReleaseFrame(encoded_frame->frame_id); + continue; + } + encoded_frame->data.swap(decrypted_data); + } + + // At this point, we have a decrypted EncodedFrame ready to be emitted. + encoded_frame->reference_time = playout_time; + framer_.ReleaseFrame(encoded_frame->frame_id); + cast_environment_->PostTask(CastEnvironment::MAIN, + FROM_HERE, + base::Bind(frame_request_queue_.front(), + base::Passed(&encoded_frame))); + frame_request_queue_.pop_front(); + } +} + +void FrameReceiver::EmitAvailableEncodedFramesAfterWaiting() { + DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); + DCHECK(is_waiting_for_consecutive_frame_); + is_waiting_for_consecutive_frame_ = false; + EmitAvailableEncodedFrames(); +} + +base::TimeTicks FrameReceiver::GetPlayoutTime(uint32 rtp_timestamp) const { + return lip_sync_reference_time_ + + lip_sync_drift_.Current() + + RtpDeltaToTimeDelta( + static_cast<int32>(rtp_timestamp - lip_sync_rtp_timestamp_), + rtp_timebase_) + + target_playout_delay_; +} + +void FrameReceiver::ScheduleNextCastMessage() { + DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); + base::TimeTicks send_time; + framer_.TimeToSendNextCastMessage(&send_time); + base::TimeDelta time_to_send = + send_time - cast_environment_->Clock()->NowTicks(); + time_to_send = std::max( + time_to_send, base::TimeDelta::FromMilliseconds(kMinSchedulingDelayMs)); + cast_environment_->PostDelayedTask( + CastEnvironment::MAIN, + FROM_HERE, + base::Bind(&FrameReceiver::SendNextCastMessage, + weak_factory_.GetWeakPtr()), + time_to_send); +} + +void FrameReceiver::SendNextCastMessage() { + DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); + framer_.SendCastMessage(); // Will only send a message if it is time. + ScheduleNextCastMessage(); +} + +void FrameReceiver::ScheduleNextRtcpReport() { + DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); + base::TimeDelta time_to_next = rtcp_.TimeToSendNextRtcpReport() - + cast_environment_->Clock()->NowTicks(); + + time_to_next = std::max( + time_to_next, base::TimeDelta::FromMilliseconds(kMinSchedulingDelayMs)); + + cast_environment_->PostDelayedTask( + CastEnvironment::MAIN, + FROM_HERE, + base::Bind(&FrameReceiver::SendNextRtcpReport, + weak_factory_.GetWeakPtr()), + time_to_next); +} + +void FrameReceiver::SendNextRtcpReport() { + DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); + rtcp_.SendRtcpFromRtpReceiver(NULL, NULL); + ScheduleNextRtcpReport(); +} + +} // namespace cast +} // namespace media diff --git a/chromium/media/cast/receiver/frame_receiver.h b/chromium/media/cast/receiver/frame_receiver.h new file mode 100644 index 00000000000..ac14ab1e0fb --- /dev/null +++ b/chromium/media/cast/receiver/frame_receiver.h @@ -0,0 +1,184 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_CAST_RECEIVER_FRAME_RECEIVER_H_ +#define MEDIA_CAST_RECEIVER_FRAME_RECEIVER_H_ + +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/time/time.h" +#include "media/cast/base/clock_drift_smoother.h" +#include "media/cast/cast_config.h" +#include "media/cast/cast_receiver.h" +#include "media/cast/framer/framer.h" +#include "media/cast/logging/logging_defines.h" +#include "media/cast/rtcp/receiver_rtcp_event_subscriber.h" +#include "media/cast/rtcp/rtcp.h" +#include "media/cast/rtp_receiver/receiver_stats.h" +#include "media/cast/rtp_receiver/rtp_parser/rtp_parser.h" +#include "media/cast/rtp_receiver/rtp_receiver_defines.h" +#include "media/cast/transport/utility/transport_encryption_handler.h" + +namespace media { +namespace cast { + +class CastEnvironment; + +// FrameReceiver receives packets out-of-order while clients make requests for +// complete frames in-order. (A frame consists of one or more packets.) +// +// FrameReceiver also includes logic for computing the playout time for each +// frame, accounting for a constant targeted playout delay. The purpose of the +// playout delay is to provide a fixed window of time between the capture event +// on the sender and the playout on the receiver. This is important because +// each step of the pipeline (i.e., encode frame, then transmit/retransmit from +// the sender, then receive and re-order packets on the receiver, then decode +// frame) can vary in duration and is typically very hard to predict. +// +// Each request for a frame includes a callback which FrameReceiver guarantees +// will be called at some point in the future unless the FrameReceiver is +// destroyed. Clients should generally limit the number of outstanding requests +// (perhaps to just one or two). +// +// This class is not thread safe. Should only be called from the Main cast +// thread. +class FrameReceiver : public RtpPayloadFeedback, + public base::SupportsWeakPtr<FrameReceiver> { + public: + FrameReceiver(const scoped_refptr<CastEnvironment>& cast_environment, + const FrameReceiverConfig& config, + EventMediaType event_media_type, + transport::PacedPacketSender* const packet_sender); + + virtual ~FrameReceiver(); + + // Request an encoded frame. + // + // The given |callback| is guaranteed to be run at some point in the future, + // except for those requests still enqueued at destruction time. + void RequestEncodedFrame(const ReceiveEncodedFrameCallback& callback); + + // Called to deliver another packet, possibly a duplicate, and possibly + // out-of-order. Returns true if the parsing of the packet succeeded. + bool ProcessPacket(scoped_ptr<Packet> packet); + + // TODO(miu): This is the wrong place for this, but the (de)serialization + // implementation needs to be consolidated first. + static bool ParseSenderSsrc(const uint8* packet, size_t length, uint32* ssrc); + + protected: + friend class FrameReceiverTest; // Invokes ProcessParsedPacket(). + + void ProcessParsedPacket(const RtpCastHeader& rtp_header, + const uint8* payload_data, + size_t payload_size); + + // RtpPayloadFeedback implementation. + virtual void CastFeedback(const RtcpCastMessage& cast_message) OVERRIDE; + + private: + // Processes ready-to-consume packets from |framer_|, decrypting each packet's + // payload data, and then running the enqueued callbacks in order (one for + // each packet). This method may post a delayed task to re-invoke itself in + // the future to wait for missing/incomplete frames. + void EmitAvailableEncodedFrames(); + + // Clears the |is_waiting_for_consecutive_frame_| flag and invokes + // EmitAvailableEncodedFrames(). + void EmitAvailableEncodedFramesAfterWaiting(); + + // Computes the playout time for a frame with the given |rtp_timestamp|. + // Because lip-sync info is refreshed regularly, calling this method with the + // same argument may return different results. + base::TimeTicks GetPlayoutTime(uint32 rtp_timestamp) const; + + // Schedule timing for the next cast message. + void ScheduleNextCastMessage(); + + // Schedule timing for the next RTCP report. + void ScheduleNextRtcpReport(); + + // Actually send the next cast message. + void SendNextCastMessage(); + + // Actually send the next RTCP report. + void SendNextRtcpReport(); + + const scoped_refptr<CastEnvironment> cast_environment_; + + // Deserializes a packet into a RtpHeader + payload bytes. + RtpParser packet_parser_; + + // Accumulates packet statistics, including packet loss, counts, and jitter. + ReceiverStats stats_; + + // Partitions logged events by the type of media passing through. + EventMediaType event_media_type_; + + // Subscribes to raw events. + // Processes raw events to be sent over to the cast sender via RTCP. + ReceiverRtcpEventSubscriber event_subscriber_; + + // RTP timebase: The number of RTP units advanced per one second. + const int rtp_timebase_; + + // The total amount of time between a frame's capture/recording on the sender + // and its playback on the receiver (i.e., shown to a user). This is fixed as + // a value large enough to give the system sufficient time to encode, + // transmit/retransmit, receive, decode, and render; given its run-time + // environment (sender/receiver hardware performance, network conditions, + // etc.). + const base::TimeDelta target_playout_delay_; + + // Hack: This is used in logic that determines whether to skip frames. + // TODO(miu): Revisit this. Logic needs to also account for expected decode + // time. + const base::TimeDelta expected_frame_duration_; + + // Set to false initially, then set to true after scheduling the periodic + // sending of reports back to the sender. Reports are first scheduled just + // after receiving a first packet (since the first packet identifies the + // sender for the remainder of the session). + bool reports_are_scheduled_; + + // Assembles packets into frames, providing this receiver with complete, + // decodable EncodedFrames. + Framer framer_; + + // Manages sending/receiving of RTCP packets, including sender/receiver + // reports. + Rtcp rtcp_; + + // Decrypts encrypted frames. + transport::TransportEncryptionHandler decryptor_; + + // Outstanding callbacks to run to deliver on client requests for frames. + std::list<ReceiveEncodedFrameCallback> frame_request_queue_; + + // True while there's an outstanding task to re-invoke + // EmitAvailableEncodedFrames(). + bool is_waiting_for_consecutive_frame_; + + // This mapping allows us to log FRAME_ACK_SENT as a frame event. In addition + // it allows the event to be transmitted via RTCP. + RtpTimestamp frame_id_to_rtp_timestamp_[256]; + + // Lip-sync values used to compute the playout time of each frame from its RTP + // timestamp. These are updated each time the first packet of a frame is + // received. + RtpTimestamp lip_sync_rtp_timestamp_; + base::TimeTicks lip_sync_reference_time_; + ClockDriftSmoother lip_sync_drift_; + + // NOTE: Weak pointers must be invalidated before all other member variables. + base::WeakPtrFactory<FrameReceiver> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(FrameReceiver); +}; + +} // namespace cast +} // namespace media + +#endif // MEDIA_CAST_RECEIVER_FRAME_RECEIVER_H_ diff --git a/chromium/media/cast/receiver/frame_receiver_unittest.cc b/chromium/media/cast/receiver/frame_receiver_unittest.cc new file mode 100644 index 00000000000..4d8273e132a --- /dev/null +++ b/chromium/media/cast/receiver/frame_receiver_unittest.cc @@ -0,0 +1,419 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <deque> +#include <utility> + +#include "base/bind.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/test/simple_test_tick_clock.h" +#include "media/cast/cast_defines.h" +#include "media/cast/cast_environment.h" +#include "media/cast/logging/simple_event_subscriber.h" +#include "media/cast/receiver/frame_receiver.h" +#include "media/cast/rtcp/test_rtcp_packet_builder.h" +#include "media/cast/test/fake_single_thread_task_runner.h" +#include "media/cast/test/utility/default_config.h" +#include "media/cast/transport/pacing/mock_paced_packet_sender.h" +#include "testing/gmock/include/gmock/gmock.h" + +using ::testing::_; + +namespace media { +namespace cast { + +namespace { + +const int kPacketSize = 1500; +const uint32 kFirstFrameId = 1234; +const int kPlayoutDelayMillis = 100; + +class FakeFrameClient { + public: + FakeFrameClient() : num_called_(0) {} + virtual ~FakeFrameClient() {} + + void AddExpectedResult(uint32 expected_frame_id, + const base::TimeTicks& expected_playout_time) { + expected_results_.push_back( + std::make_pair(expected_frame_id, expected_playout_time)); + } + + void DeliverEncodedFrame(scoped_ptr<transport::EncodedFrame> frame) { + SCOPED_TRACE(::testing::Message() << "num_called_ is " << num_called_); + ASSERT_FALSE(!frame) + << "If at shutdown: There were unsatisfied requests enqueued."; + ASSERT_FALSE(expected_results_.empty()); + EXPECT_EQ(expected_results_.front().first, frame->frame_id); + EXPECT_EQ(expected_results_.front().second, frame->reference_time); + expected_results_.pop_front(); + ++num_called_; + } + + int number_times_called() const { return num_called_; } + + private: + std::deque<std::pair<uint32, base::TimeTicks> > expected_results_; + int num_called_; + + DISALLOW_COPY_AND_ASSIGN(FakeFrameClient); +}; +} // namespace + +class FrameReceiverTest : public ::testing::Test { + protected: + FrameReceiverTest() { + testing_clock_ = new base::SimpleTestTickClock(); + testing_clock_->Advance(base::TimeTicks::Now() - base::TimeTicks()); + start_time_ = testing_clock_->NowTicks(); + task_runner_ = new test::FakeSingleThreadTaskRunner(testing_clock_); + + cast_environment_ = + new CastEnvironment(scoped_ptr<base::TickClock>(testing_clock_).Pass(), + task_runner_, + task_runner_, + task_runner_); + } + + virtual ~FrameReceiverTest() {} + + virtual void SetUp() { + payload_.assign(kPacketSize, 0); + + // Always start with a key frame. + rtp_header_.is_key_frame = true; + rtp_header_.frame_id = kFirstFrameId; + rtp_header_.packet_id = 0; + rtp_header_.max_packet_id = 0; + rtp_header_.reference_frame_id = rtp_header_.frame_id; + rtp_header_.rtp_timestamp = 0; + } + + void CreateFrameReceiverOfAudio() { + config_ = GetDefaultAudioReceiverConfig(); + config_.rtp_max_delay_ms = kPlayoutDelayMillis; + + receiver_.reset(new FrameReceiver( + cast_environment_, config_, AUDIO_EVENT, &mock_transport_)); + } + + void CreateFrameReceiverOfVideo() { + config_ = GetDefaultVideoReceiverConfig(); + config_.rtp_max_delay_ms = kPlayoutDelayMillis; + // Note: Frame rate must divide 1000 without remainder so the test code + // doesn't have to account for rounding errors. + config_.max_frame_rate = 25; + + receiver_.reset(new FrameReceiver( + cast_environment_, config_, VIDEO_EVENT, &mock_transport_)); + } + + void FeedOneFrameIntoReceiver() { + // Note: For testing purposes, a frame consists of only a single packet. + receiver_->ProcessParsedPacket( + rtp_header_, payload_.data(), payload_.size()); + } + + void FeedLipSyncInfoIntoReceiver() { + const base::TimeTicks now = testing_clock_->NowTicks(); + const int64 rtp_timestamp = (now - start_time_) * + config_.frequency / base::TimeDelta::FromSeconds(1); + CHECK_LE(0, rtp_timestamp); + uint32 ntp_seconds; + uint32 ntp_fraction; + ConvertTimeTicksToNtp(now, &ntp_seconds, &ntp_fraction); + TestRtcpPacketBuilder rtcp_packet; + rtcp_packet.AddSrWithNtp(config_.incoming_ssrc, + ntp_seconds, ntp_fraction, + static_cast<uint32>(rtp_timestamp)); + ASSERT_TRUE(receiver_->ProcessPacket(rtcp_packet.GetPacket().Pass())); + } + + FrameReceiverConfig config_; + std::vector<uint8> payload_; + RtpCastHeader rtp_header_; + base::SimpleTestTickClock* testing_clock_; // Owned by CastEnvironment. + base::TimeTicks start_time_; + transport::MockPacedPacketSender mock_transport_; + scoped_refptr<test::FakeSingleThreadTaskRunner> task_runner_; + scoped_refptr<CastEnvironment> cast_environment_; + FakeFrameClient frame_client_; + + // Important for the FrameReceiver to be declared last, since its dependencies + // must remain alive until after its destruction. + scoped_ptr<FrameReceiver> receiver_; + + DISALLOW_COPY_AND_ASSIGN(FrameReceiverTest); +}; + +TEST_F(FrameReceiverTest, RejectsUnparsablePackets) { + CreateFrameReceiverOfVideo(); + + SimpleEventSubscriber event_subscriber; + cast_environment_->Logging()->AddRawEventSubscriber(&event_subscriber); + + const bool success = receiver_->ProcessPacket( + scoped_ptr<Packet>(new Packet(kPacketSize, 0xff)).Pass()); + EXPECT_FALSE(success); + + // Confirm no log events. + std::vector<FrameEvent> frame_events; + event_subscriber.GetFrameEventsAndReset(&frame_events); + EXPECT_TRUE(frame_events.empty()); + cast_environment_->Logging()->RemoveRawEventSubscriber(&event_subscriber); +} + +TEST_F(FrameReceiverTest, ReceivesOneFrame) { + CreateFrameReceiverOfAudio(); + + SimpleEventSubscriber event_subscriber; + cast_environment_->Logging()->AddRawEventSubscriber(&event_subscriber); + + EXPECT_CALL(mock_transport_, SendRtcpPacket(_, _)) + .WillRepeatedly(testing::Return(true)); + + FeedLipSyncInfoIntoReceiver(); + task_runner_->RunTasks(); + + // Enqueue a request for a frame. + receiver_->RequestEncodedFrame( + base::Bind(&FakeFrameClient::DeliverEncodedFrame, + base::Unretained(&frame_client_))); + + // The request should not be satisfied since no packets have been received. + task_runner_->RunTasks(); + EXPECT_EQ(0, frame_client_.number_times_called()); + + // Deliver one frame to the receiver and expect to get one frame back. + const base::TimeDelta target_playout_delay = + base::TimeDelta::FromMilliseconds(kPlayoutDelayMillis); + frame_client_.AddExpectedResult( + kFirstFrameId, testing_clock_->NowTicks() + target_playout_delay); + FeedOneFrameIntoReceiver(); + task_runner_->RunTasks(); + EXPECT_EQ(1, frame_client_.number_times_called()); + + // Was the frame logged? + std::vector<FrameEvent> frame_events; + event_subscriber.GetFrameEventsAndReset(&frame_events); + ASSERT_TRUE(!frame_events.empty()); + EXPECT_EQ(FRAME_ACK_SENT, frame_events.begin()->type); + EXPECT_EQ(AUDIO_EVENT, frame_events.begin()->media_type); + EXPECT_EQ(rtp_header_.frame_id, frame_events.begin()->frame_id); + EXPECT_EQ(rtp_header_.rtp_timestamp, frame_events.begin()->rtp_timestamp); + cast_environment_->Logging()->RemoveRawEventSubscriber(&event_subscriber); +} + +TEST_F(FrameReceiverTest, ReceivesFramesSkippingWhenAppropriate) { + CreateFrameReceiverOfAudio(); + + SimpleEventSubscriber event_subscriber; + cast_environment_->Logging()->AddRawEventSubscriber(&event_subscriber); + + EXPECT_CALL(mock_transport_, SendRtcpPacket(_, _)) + .WillRepeatedly(testing::Return(true)); + + const uint32 rtp_advance_per_frame = + config_.frequency / config_.max_frame_rate; + const base::TimeDelta time_advance_per_frame = + base::TimeDelta::FromSeconds(1) / config_.max_frame_rate; + + // Feed and process lip sync in receiver. + FeedLipSyncInfoIntoReceiver(); + task_runner_->RunTasks(); + const base::TimeTicks first_frame_capture_time = testing_clock_->NowTicks(); + + // Enqueue a request for a frame. + const ReceiveEncodedFrameCallback frame_encoded_callback = + base::Bind(&FakeFrameClient::DeliverEncodedFrame, + base::Unretained(&frame_client_)); + receiver_->RequestEncodedFrame(frame_encoded_callback); + task_runner_->RunTasks(); + EXPECT_EQ(0, frame_client_.number_times_called()); + + // Receive one frame and expect to see the first request satisfied. + const base::TimeDelta target_playout_delay = + base::TimeDelta::FromMilliseconds(kPlayoutDelayMillis); + frame_client_.AddExpectedResult( + kFirstFrameId, first_frame_capture_time + target_playout_delay); + rtp_header_.rtp_timestamp = 0; + FeedOneFrameIntoReceiver(); // Frame 1 + task_runner_->RunTasks(); + EXPECT_EQ(1, frame_client_.number_times_called()); + + // Enqueue a second request for a frame, but it should not be fulfilled yet. + receiver_->RequestEncodedFrame(frame_encoded_callback); + task_runner_->RunTasks(); + EXPECT_EQ(1, frame_client_.number_times_called()); + + // Receive one frame out-of-order: Make sure that we are not continuous and + // that the RTP timestamp represents a time in the future. + rtp_header_.frame_id = kFirstFrameId + 2; // "Frame 3" + rtp_header_.reference_frame_id = rtp_header_.frame_id; + rtp_header_.rtp_timestamp += 2 * rtp_advance_per_frame; + frame_client_.AddExpectedResult( + kFirstFrameId + 2, + first_frame_capture_time + 2 * time_advance_per_frame + + target_playout_delay); + FeedOneFrameIntoReceiver(); // Frame 3 + + // Frame 2 should not come out at this point in time. + task_runner_->RunTasks(); + EXPECT_EQ(1, frame_client_.number_times_called()); + + // Enqueue a third request for a frame. + receiver_->RequestEncodedFrame(frame_encoded_callback); + task_runner_->RunTasks(); + EXPECT_EQ(1, frame_client_.number_times_called()); + + // Now, advance time forward such that the receiver is convinced it should + // skip Frame 2. Frame 3 is emitted (to satisfy the second request) because a + // decision was made to skip over the no-show Frame 2. + testing_clock_->Advance(2 * time_advance_per_frame + target_playout_delay); + task_runner_->RunTasks(); + EXPECT_EQ(2, frame_client_.number_times_called()); + + // Receive Frame 4 and expect it to fulfill the third request immediately. + rtp_header_.frame_id = kFirstFrameId + 3; // "Frame 4" + rtp_header_.reference_frame_id = rtp_header_.frame_id; + rtp_header_.rtp_timestamp += rtp_advance_per_frame; + frame_client_.AddExpectedResult( + kFirstFrameId + 3, first_frame_capture_time + 3 * time_advance_per_frame + + target_playout_delay); + FeedOneFrameIntoReceiver(); // Frame 4 + task_runner_->RunTasks(); + EXPECT_EQ(3, frame_client_.number_times_called()); + + // Move forward to the playout time of an unreceived Frame 5. Expect no + // additional frames were emitted. + testing_clock_->Advance(3 * time_advance_per_frame); + task_runner_->RunTasks(); + EXPECT_EQ(3, frame_client_.number_times_called()); + + // Were only non-skipped frames logged? + std::vector<FrameEvent> frame_events; + event_subscriber.GetFrameEventsAndReset(&frame_events); + ASSERT_TRUE(!frame_events.empty()); + for (size_t i = 0; i < frame_events.size(); ++i) { + EXPECT_EQ(FRAME_ACK_SENT, frame_events[i].type); + EXPECT_EQ(AUDIO_EVENT, frame_events[i].media_type); + EXPECT_LE(kFirstFrameId, frame_events[i].frame_id); + EXPECT_GE(kFirstFrameId + 4, frame_events[i].frame_id); + const int frame_offset = frame_events[i].frame_id - kFirstFrameId; + EXPECT_NE(frame_offset, 1); // Frame 2 never received. + EXPECT_EQ(frame_offset * rtp_advance_per_frame, + frame_events[i].rtp_timestamp); + } + cast_environment_->Logging()->RemoveRawEventSubscriber(&event_subscriber); +} + +TEST_F(FrameReceiverTest, ReceivesFramesRefusingToSkipAny) { + CreateFrameReceiverOfVideo(); + + SimpleEventSubscriber event_subscriber; + cast_environment_->Logging()->AddRawEventSubscriber(&event_subscriber); + + EXPECT_CALL(mock_transport_, SendRtcpPacket(_, _)) + .WillRepeatedly(testing::Return(true)); + + const uint32 rtp_advance_per_frame = + config_.frequency / config_.max_frame_rate; + const base::TimeDelta time_advance_per_frame = + base::TimeDelta::FromSeconds(1) / config_.max_frame_rate; + + // Feed and process lip sync in receiver. + FeedLipSyncInfoIntoReceiver(); + task_runner_->RunTasks(); + const base::TimeTicks first_frame_capture_time = testing_clock_->NowTicks(); + + // Enqueue a request for a frame. + const ReceiveEncodedFrameCallback frame_encoded_callback = + base::Bind(&FakeFrameClient::DeliverEncodedFrame, + base::Unretained(&frame_client_)); + receiver_->RequestEncodedFrame(frame_encoded_callback); + task_runner_->RunTasks(); + EXPECT_EQ(0, frame_client_.number_times_called()); + + // Receive one frame and expect to see the first request satisfied. + const base::TimeDelta target_playout_delay = + base::TimeDelta::FromMilliseconds(kPlayoutDelayMillis); + frame_client_.AddExpectedResult( + kFirstFrameId, first_frame_capture_time + target_playout_delay); + rtp_header_.rtp_timestamp = 0; + FeedOneFrameIntoReceiver(); // Frame 1 + task_runner_->RunTasks(); + EXPECT_EQ(1, frame_client_.number_times_called()); + + // Enqueue a second request for a frame, but it should not be fulfilled yet. + receiver_->RequestEncodedFrame(frame_encoded_callback); + task_runner_->RunTasks(); + EXPECT_EQ(1, frame_client_.number_times_called()); + + // Receive one frame out-of-order: Make sure that we are not continuous and + // that the RTP timestamp represents a time in the future. + rtp_header_.is_key_frame = false; + rtp_header_.frame_id = kFirstFrameId + 2; // "Frame 3" + rtp_header_.reference_frame_id = kFirstFrameId + 1; // "Frame 2" + rtp_header_.rtp_timestamp += 2 * rtp_advance_per_frame; + FeedOneFrameIntoReceiver(); // Frame 3 + + // Frame 2 should not come out at this point in time. + task_runner_->RunTasks(); + EXPECT_EQ(1, frame_client_.number_times_called()); + + // Enqueue a third request for a frame. + receiver_->RequestEncodedFrame(frame_encoded_callback); + task_runner_->RunTasks(); + EXPECT_EQ(1, frame_client_.number_times_called()); + + // Now, advance time forward such that Frame 2 is now too late for playback. + // Regardless, the receiver must NOT emit Frame 3 yet because it is not + // allowed to skip frames when dependencies are not satisfied. In other + // words, Frame 3 is not decodable without Frame 2. + testing_clock_->Advance(2 * time_advance_per_frame + target_playout_delay); + task_runner_->RunTasks(); + EXPECT_EQ(1, frame_client_.number_times_called()); + + // Now receive Frame 2 and expect both the second and third requests to be + // fulfilled immediately. + frame_client_.AddExpectedResult( + kFirstFrameId + 1, // "Frame 2" + first_frame_capture_time + 1 * time_advance_per_frame + + target_playout_delay); + frame_client_.AddExpectedResult( + kFirstFrameId + 2, // "Frame 3" + first_frame_capture_time + 2 * time_advance_per_frame + + target_playout_delay); + --rtp_header_.frame_id; // "Frame 2" + --rtp_header_.reference_frame_id; // "Frame 1" + rtp_header_.rtp_timestamp -= rtp_advance_per_frame; + FeedOneFrameIntoReceiver(); // Frame 2 + task_runner_->RunTasks(); + EXPECT_EQ(3, frame_client_.number_times_called()); + + // Move forward to the playout time of an unreceived Frame 5. Expect no + // additional frames were emitted. + testing_clock_->Advance(3 * time_advance_per_frame); + task_runner_->RunTasks(); + EXPECT_EQ(3, frame_client_.number_times_called()); + + // Sanity-check logging results. + std::vector<FrameEvent> frame_events; + event_subscriber.GetFrameEventsAndReset(&frame_events); + ASSERT_TRUE(!frame_events.empty()); + for (size_t i = 0; i < frame_events.size(); ++i) { + EXPECT_EQ(FRAME_ACK_SENT, frame_events[i].type); + EXPECT_EQ(VIDEO_EVENT, frame_events[i].media_type); + EXPECT_LE(kFirstFrameId, frame_events[i].frame_id); + EXPECT_GE(kFirstFrameId + 3, frame_events[i].frame_id); + const int frame_offset = frame_events[i].frame_id - kFirstFrameId; + EXPECT_EQ(frame_offset * rtp_advance_per_frame, + frame_events[i].rtp_timestamp); + } + cast_environment_->Logging()->RemoveRawEventSubscriber(&event_subscriber); +} + +} // namespace cast +} // namespace media diff --git a/chromium/media/cast/receiver/video_decoder.cc b/chromium/media/cast/receiver/video_decoder.cc new file mode 100644 index 00000000000..6db3fd35f39 --- /dev/null +++ b/chromium/media/cast/receiver/video_decoder.cc @@ -0,0 +1,259 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/cast/receiver/video_decoder.h" + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/json/json_reader.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/values.h" +#include "media/base/video_util.h" +#include "media/cast/cast_defines.h" +#include "media/cast/cast_environment.h" +// VPX_CODEC_DISABLE_COMPAT excludes parts of the libvpx API that provide +// backwards compatibility for legacy applications using the library. +#define VPX_CODEC_DISABLE_COMPAT 1 +#include "third_party/libvpx/source/libvpx/vpx/vp8dx.h" +#include "third_party/libvpx/source/libvpx/vpx/vpx_decoder.h" +#include "ui/gfx/size.h" + +namespace media { +namespace cast { + +// Base class that handles the common problem of detecting dropped frames, and +// then invoking the Decode() method implemented by the subclasses to convert +// the encoded payload data into a usable video frame. +class VideoDecoder::ImplBase + : public base::RefCountedThreadSafe<VideoDecoder::ImplBase> { + public: + ImplBase(const scoped_refptr<CastEnvironment>& cast_environment, + transport::VideoCodec codec) + : cast_environment_(cast_environment), + codec_(codec), + cast_initialization_status_(STATUS_VIDEO_UNINITIALIZED), + seen_first_frame_(false) {} + + CastInitializationStatus InitializationResult() const { + return cast_initialization_status_; + } + + void DecodeFrame(scoped_ptr<transport::EncodedFrame> encoded_frame, + const DecodeFrameCallback& callback) { + DCHECK_EQ(cast_initialization_status_, STATUS_VIDEO_INITIALIZED); + + COMPILE_ASSERT(sizeof(encoded_frame->frame_id) == sizeof(last_frame_id_), + size_of_frame_id_types_do_not_match); + bool is_continuous = true; + if (seen_first_frame_) { + const uint32 frames_ahead = encoded_frame->frame_id - last_frame_id_; + if (frames_ahead > 1) { + RecoverBecauseFramesWereDropped(); + is_continuous = false; + } + } else { + seen_first_frame_ = true; + } + last_frame_id_ = encoded_frame->frame_id; + + const scoped_refptr<VideoFrame> decoded_frame = Decode( + encoded_frame->mutable_bytes(), + static_cast<int>(encoded_frame->data.size())); + cast_environment_->PostTask( + CastEnvironment::MAIN, + FROM_HERE, + base::Bind(callback, decoded_frame, is_continuous)); + } + + protected: + friend class base::RefCountedThreadSafe<ImplBase>; + virtual ~ImplBase() {} + + virtual void RecoverBecauseFramesWereDropped() {} + + // Note: Implementation of Decode() is allowed to mutate |data|. + virtual scoped_refptr<VideoFrame> Decode(uint8* data, int len) = 0; + + const scoped_refptr<CastEnvironment> cast_environment_; + const transport::VideoCodec codec_; + + // Subclass' ctor is expected to set this to STATUS_VIDEO_INITIALIZED. + CastInitializationStatus cast_initialization_status_; + + private: + bool seen_first_frame_; + uint32 last_frame_id_; + + DISALLOW_COPY_AND_ASSIGN(ImplBase); +}; + +class VideoDecoder::Vp8Impl : public VideoDecoder::ImplBase { + public: + explicit Vp8Impl(const scoped_refptr<CastEnvironment>& cast_environment) + : ImplBase(cast_environment, transport::kVp8) { + if (ImplBase::cast_initialization_status_ != STATUS_VIDEO_UNINITIALIZED) + return; + + vpx_codec_dec_cfg_t cfg = {0}; + // TODO(miu): Revisit this for typical multi-core desktop use case. This + // feels like it should be 4 or 8. + cfg.threads = 1; + + DCHECK(vpx_codec_get_caps(vpx_codec_vp8_dx()) & VPX_CODEC_CAP_POSTPROC); + if (vpx_codec_dec_init(&context_, + vpx_codec_vp8_dx(), + &cfg, + VPX_CODEC_USE_POSTPROC) != VPX_CODEC_OK) { + ImplBase::cast_initialization_status_ = + STATUS_INVALID_VIDEO_CONFIGURATION; + return; + } + ImplBase::cast_initialization_status_ = STATUS_VIDEO_INITIALIZED; + } + + private: + virtual ~Vp8Impl() { + if (ImplBase::cast_initialization_status_ == STATUS_VIDEO_INITIALIZED) + CHECK_EQ(VPX_CODEC_OK, vpx_codec_destroy(&context_)); + } + + virtual scoped_refptr<VideoFrame> Decode(uint8* data, int len) OVERRIDE { + if (len <= 0 || vpx_codec_decode(&context_, + data, + static_cast<unsigned int>(len), + NULL, + 0) != VPX_CODEC_OK) { + return NULL; + } + + vpx_codec_iter_t iter = NULL; + vpx_image_t* const image = vpx_codec_get_frame(&context_, &iter); + if (!image) + return NULL; + if (image->fmt != VPX_IMG_FMT_I420 && image->fmt != VPX_IMG_FMT_YV12) { + NOTREACHED(); + return NULL; + } + DCHECK(vpx_codec_get_frame(&context_, &iter) == NULL) + << "Should have only decoded exactly one frame."; + + const gfx::Size frame_size(image->d_w, image->d_h); + // Note: Timestamp for the VideoFrame will be set in VideoReceiver. + const scoped_refptr<VideoFrame> decoded_frame = + VideoFrame::CreateFrame(VideoFrame::YV12, + frame_size, + gfx::Rect(frame_size), + frame_size, + base::TimeDelta()); + CopyYPlane(image->planes[VPX_PLANE_Y], + image->stride[VPX_PLANE_Y], + image->d_h, + decoded_frame); + CopyUPlane(image->planes[VPX_PLANE_U], + image->stride[VPX_PLANE_U], + (image->d_h + 1) / 2, + decoded_frame); + CopyVPlane(image->planes[VPX_PLANE_V], + image->stride[VPX_PLANE_V], + (image->d_h + 1) / 2, + decoded_frame); + return decoded_frame; + } + + // VPX decoder context (i.e., an instantiation). + vpx_codec_ctx_t context_; + + DISALLOW_COPY_AND_ASSIGN(Vp8Impl); +}; + +#ifndef OFFICIAL_BUILD +// A fake video decoder that always output 2x2 black frames. +class VideoDecoder::FakeImpl : public VideoDecoder::ImplBase { + public: + explicit FakeImpl(const scoped_refptr<CastEnvironment>& cast_environment) + : ImplBase(cast_environment, transport::kFakeSoftwareVideo), + last_decoded_id_(-1) { + if (ImplBase::cast_initialization_status_ != STATUS_VIDEO_UNINITIALIZED) + return; + ImplBase::cast_initialization_status_ = STATUS_VIDEO_INITIALIZED; + } + + private: + virtual ~FakeImpl() {} + + virtual scoped_refptr<VideoFrame> Decode(uint8* data, int len) OVERRIDE { + base::JSONReader reader; + scoped_ptr<base::Value> values( + reader.Read(base::StringPiece(reinterpret_cast<char*>(data)))); + base::DictionaryValue* dict = NULL; + values->GetAsDictionary(&dict); + + bool key = false; + int id = 0; + int ref = 0; + dict->GetBoolean("key", &key); + dict->GetInteger("id", &id); + dict->GetInteger("ref", &ref); + DCHECK(id == last_decoded_id_ + 1); + last_decoded_id_ = id; + return media::VideoFrame::CreateBlackFrame(gfx::Size(2, 2)); + } + + int last_decoded_id_; + + DISALLOW_COPY_AND_ASSIGN(FakeImpl); +}; +#endif + +VideoDecoder::VideoDecoder( + const scoped_refptr<CastEnvironment>& cast_environment, + transport::VideoCodec codec) + : cast_environment_(cast_environment) { + switch (codec) { +#ifndef OFFICIAL_BUILD + case transport::kFakeSoftwareVideo: + impl_ = new FakeImpl(cast_environment); + break; +#endif + case transport::kVp8: + impl_ = new Vp8Impl(cast_environment); + break; + case transport::kH264: + // TODO(miu): Need implementation. + NOTIMPLEMENTED(); + break; + default: + NOTREACHED() << "Unknown or unspecified codec."; + break; + } +} + +VideoDecoder::~VideoDecoder() {} + +CastInitializationStatus VideoDecoder::InitializationResult() const { + if (impl_) + return impl_->InitializationResult(); + return STATUS_UNSUPPORTED_VIDEO_CODEC; +} + +void VideoDecoder::DecodeFrame( + scoped_ptr<transport::EncodedFrame> encoded_frame, + const DecodeFrameCallback& callback) { + DCHECK(encoded_frame.get()); + DCHECK(!callback.is_null()); + if (!impl_ || impl_->InitializationResult() != STATUS_VIDEO_INITIALIZED) { + callback.Run(make_scoped_refptr<VideoFrame>(NULL), false); + return; + } + cast_environment_->PostTask(CastEnvironment::VIDEO, + FROM_HERE, + base::Bind(&VideoDecoder::ImplBase::DecodeFrame, + impl_, + base::Passed(&encoded_frame), + callback)); +} + +} // namespace cast +} // namespace media diff --git a/chromium/media/cast/receiver/video_decoder.h b/chromium/media/cast/receiver/video_decoder.h new file mode 100644 index 00000000000..66dc36bb2ac --- /dev/null +++ b/chromium/media/cast/receiver/video_decoder.h @@ -0,0 +1,63 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_CAST_RECEIVER_VIDEO_DECODER_H_ +#define MEDIA_CAST_RECEIVER_VIDEO_DECODER_H_ + +#include "base/callback.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "media/base/video_frame.h" +#include "media/cast/cast_config.h" +#include "media/cast/transport/cast_transport_config.h" + +namespace media { +namespace cast { + +class CastEnvironment; + +class VideoDecoder { + public: + // Callback passed to DecodeFrame, to deliver a decoded video frame from the + // decoder. |frame| can be NULL when errors occur. |is_continuous| is + // normally true, but will be false if the decoder has detected a frame skip + // since the last decode operation; and the client might choose to take steps + // to smooth/interpolate video discontinuities in this case. + typedef base::Callback<void(const scoped_refptr<VideoFrame>& frame, + bool is_continuous)> DecodeFrameCallback; + + VideoDecoder(const scoped_refptr<CastEnvironment>& cast_environment, + transport::VideoCodec codec); + virtual ~VideoDecoder(); + + // Returns STATUS_VIDEO_INITIALIZED if the decoder was successfully + // constructed from the given FrameReceiverConfig. If this method returns any + // other value, calls to DecodeFrame() will not succeed. + CastInitializationStatus InitializationResult() const; + + // Decode the payload in |encoded_frame| asynchronously. |callback| will be + // invoked on the CastEnvironment::MAIN thread with the result. + // + // In the normal case, |encoded_frame->frame_id| will be + // monotonically-increasing by 1 for each successive call to this method. + // When it is not, the decoder will assume one or more frames have been + // dropped (e.g., due to packet loss), and will perform recovery actions. + void DecodeFrame(scoped_ptr<transport::EncodedFrame> encoded_frame, + const DecodeFrameCallback& callback); + + private: + class FakeImpl; + class ImplBase; + class Vp8Impl; + + const scoped_refptr<CastEnvironment> cast_environment_; + scoped_refptr<ImplBase> impl_; + + DISALLOW_COPY_AND_ASSIGN(VideoDecoder); +}; + +} // namespace cast +} // namespace media + +#endif // MEDIA_CAST_RECEIVER_VIDEO_DECODER_H_ diff --git a/chromium/media/cast/receiver/video_decoder_unittest.cc b/chromium/media/cast/receiver/video_decoder_unittest.cc new file mode 100644 index 00000000000..1d16534b968 --- /dev/null +++ b/chromium/media/cast/receiver/video_decoder_unittest.cc @@ -0,0 +1,183 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <cstdlib> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/synchronization/condition_variable.h" +#include "base/synchronization/lock.h" +#include "base/time/time.h" +#include "media/cast/cast_config.h" +#include "media/cast/receiver/video_decoder.h" +#include "media/cast/test/utility/standalone_cast_environment.h" +#include "media/cast/test/utility/video_utility.h" +#include "media/cast/video_sender/codecs/vp8/vp8_encoder.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace media { +namespace cast { + +namespace { + +const int kWidth = 360; +const int kHeight = 240; +const int kFrameRate = 10; + +VideoSenderConfig GetVideoSenderConfigForTest() { + VideoSenderConfig config; + config.width = kWidth; + config.height = kHeight; + config.max_frame_rate = kFrameRate; + return config; +} + +} // namespace + +class VideoDecoderTest + : public ::testing::TestWithParam<transport::VideoCodec> { + public: + VideoDecoderTest() + : cast_environment_(new StandaloneCastEnvironment()), + vp8_encoder_(GetVideoSenderConfigForTest(), 0), + cond_(&lock_) { + vp8_encoder_.Initialize(); + } + + protected: + virtual void SetUp() OVERRIDE { + video_decoder_.reset(new VideoDecoder(cast_environment_, GetParam())); + CHECK_EQ(STATUS_VIDEO_INITIALIZED, video_decoder_->InitializationResult()); + + next_frame_timestamp_ = base::TimeDelta(); + last_frame_id_ = 0; + seen_a_decoded_frame_ = false; + + total_video_frames_feed_in_ = 0; + total_video_frames_decoded_ = 0; + } + + // Called from the unit test thread to create another EncodedFrame and push it + // into the decoding pipeline. + void FeedMoreVideo(int num_dropped_frames) { + // Prepare a simulated EncodedFrame to feed into the VideoDecoder. + + const gfx::Size frame_size(kWidth, kHeight); + const scoped_refptr<VideoFrame> video_frame = + VideoFrame::CreateFrame(VideoFrame::YV12, + frame_size, + gfx::Rect(frame_size), + frame_size, + next_frame_timestamp_); + next_frame_timestamp_ += base::TimeDelta::FromSeconds(1) / kFrameRate; + PopulateVideoFrame(video_frame, 0); + + // Encode |frame| into |encoded_frame->data|. + scoped_ptr<transport::EncodedFrame> encoded_frame( + new transport::EncodedFrame()); + CHECK_EQ(transport::kVp8, GetParam()); // Only support VP8 test currently. + vp8_encoder_.Encode(video_frame, encoded_frame.get()); + encoded_frame->frame_id = last_frame_id_ + 1 + num_dropped_frames; + last_frame_id_ = encoded_frame->frame_id; + + { + base::AutoLock auto_lock(lock_); + ++total_video_frames_feed_in_; + } + + cast_environment_->PostTask( + CastEnvironment::MAIN, + FROM_HERE, + base::Bind(&VideoDecoder::DecodeFrame, + base::Unretained(video_decoder_.get()), + base::Passed(&encoded_frame), + base::Bind(&VideoDecoderTest::OnDecodedFrame, + base::Unretained(this), + video_frame, + num_dropped_frames == 0))); + } + + // Blocks the caller until all video that has been feed in has been decoded. + void WaitForAllVideoToBeDecoded() { + DCHECK(!cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); + base::AutoLock auto_lock(lock_); + while (total_video_frames_decoded_ < total_video_frames_feed_in_) + cond_.Wait(); + EXPECT_EQ(total_video_frames_feed_in_, total_video_frames_decoded_); + } + + private: + // Called by |vp8_decoder_| to deliver each frame of decoded video. + void OnDecodedFrame(const scoped_refptr<VideoFrame>& expected_video_frame, + bool should_be_continuous, + const scoped_refptr<VideoFrame>& video_frame, + bool is_continuous) { + DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); + + // A NULL |video_frame| indicates a decode error, which we don't expect. + ASSERT_FALSE(!video_frame); + + // Did the decoder detect whether frames were dropped? + EXPECT_EQ(should_be_continuous, is_continuous); + + // Does the video data seem to be intact? + EXPECT_EQ(expected_video_frame->coded_size().width(), + video_frame->coded_size().width()); + EXPECT_EQ(expected_video_frame->coded_size().height(), + video_frame->coded_size().height()); + EXPECT_LT(40.0, I420PSNR(expected_video_frame, video_frame)); + // TODO(miu): Once we start using VideoFrame::timestamp_, check that here. + + // Signal the main test thread that more video was decoded. + base::AutoLock auto_lock(lock_); + ++total_video_frames_decoded_; + cond_.Signal(); + } + + const scoped_refptr<StandaloneCastEnvironment> cast_environment_; + scoped_ptr<VideoDecoder> video_decoder_; + base::TimeDelta next_frame_timestamp_; + uint32 last_frame_id_; + bool seen_a_decoded_frame_; + + Vp8Encoder vp8_encoder_; + + base::Lock lock_; + base::ConditionVariable cond_; + int total_video_frames_feed_in_; + int total_video_frames_decoded_; + + DISALLOW_COPY_AND_ASSIGN(VideoDecoderTest); +}; + +TEST_P(VideoDecoderTest, DecodesFrames) { + const int kNumFrames = 10; + for (int i = 0; i < kNumFrames; ++i) + FeedMoreVideo(0); + WaitForAllVideoToBeDecoded(); +} + +TEST_P(VideoDecoderTest, RecoversFromDroppedFrames) { + const int kNumFrames = 100; + int next_drop_at = 3; + int next_num_dropped = 1; + for (int i = 0; i < kNumFrames; ++i) { + if (i == next_drop_at) { + const int num_dropped = next_num_dropped++; + next_drop_at *= 2; + i += num_dropped; + FeedMoreVideo(num_dropped); + } else { + FeedMoreVideo(0); + } + } + WaitForAllVideoToBeDecoded(); +} + +INSTANTIATE_TEST_CASE_P(VideoDecoderTestScenarios, + VideoDecoderTest, + ::testing::Values(transport::kVp8)); + +} // namespace cast +} // namespace media diff --git a/chromium/media/cast/rtcp/mock_rtcp_receiver_feedback.cc b/chromium/media/cast/rtcp/mock_rtcp_receiver_feedback.cc index daaa1ad0883..9ff2d48f03c 100644 --- a/chromium/media/cast/rtcp/mock_rtcp_receiver_feedback.cc +++ b/chromium/media/cast/rtcp/mock_rtcp_receiver_feedback.cc @@ -7,17 +7,13 @@ namespace media { namespace cast { -MockRtcpReceiverFeedback::MockRtcpReceiverFeedback() { -} +MockRtcpReceiverFeedback::MockRtcpReceiverFeedback() {} -MockRtcpReceiverFeedback::~MockRtcpReceiverFeedback() { -} +MockRtcpReceiverFeedback::~MockRtcpReceiverFeedback() {} -MockRtcpRttFeedback::MockRtcpRttFeedback() { -} +MockRtcpRttFeedback::MockRtcpRttFeedback() {} -MockRtcpRttFeedback::~MockRtcpRttFeedback() { -} +MockRtcpRttFeedback::~MockRtcpRttFeedback() {} } // namespace cast } // namespace media diff --git a/chromium/media/cast/rtcp/mock_rtcp_receiver_feedback.h b/chromium/media/cast/rtcp/mock_rtcp_receiver_feedback.h index 0316d9819f2..56fe1ca6995 100644 --- a/chromium/media/cast/rtcp/mock_rtcp_receiver_feedback.h +++ b/chromium/media/cast/rtcp/mock_rtcp_receiver_feedback.h @@ -7,7 +7,9 @@ #include <vector> +#include "media/cast/rtcp/rtcp_defines.h" #include "media/cast/rtcp/rtcp_receiver.h" +#include "media/cast/transport/cast_transport_defines.h" #include "testing/gmock/include/gmock/gmock.h" namespace media { @@ -19,7 +21,7 @@ class MockRtcpReceiverFeedback : public RtcpReceiverFeedback { virtual ~MockRtcpReceiverFeedback(); MOCK_METHOD1(OnReceivedSenderReport, - void(const RtcpSenderInfo& remote_sender_info)); + void(const transport::RtcpSenderInfo& remote_sender_info)); MOCK_METHOD1(OnReceiverReferenceTimeReport, void(const RtcpReceiverReferenceTimeReport& remote_time_report)); @@ -28,8 +30,6 @@ class MockRtcpReceiverFeedback : public RtcpReceiverFeedback { MOCK_METHOD1(OnReceivedReceiverLog, void(const RtcpReceiverLogMessage& receiver_log)); - MOCK_METHOD1(OnReceivedSenderLog, - void(const RtcpSenderLogMessage& sender_log)); }; class MockRtcpRttFeedback : public RtcpRttFeedback { diff --git a/chromium/media/cast/rtcp/mock_rtcp_sender_feedback.cc b/chromium/media/cast/rtcp/mock_rtcp_sender_feedback.cc index 65c630148c2..e44e0bfdef4 100644 --- a/chromium/media/cast/rtcp/mock_rtcp_sender_feedback.cc +++ b/chromium/media/cast/rtcp/mock_rtcp_sender_feedback.cc @@ -7,11 +7,9 @@ namespace media { namespace cast { -MockRtcpSenderFeedback::MockRtcpSenderFeedback() { -} +MockRtcpSenderFeedback::MockRtcpSenderFeedback() {} -MockRtcpSenderFeedback::~MockRtcpSenderFeedback() { -} +MockRtcpSenderFeedback::~MockRtcpSenderFeedback() {} } // namespace cast } // namespace media diff --git a/chromium/media/cast/rtcp/receiver_rtcp_event_subscriber.cc b/chromium/media/cast/rtcp/receiver_rtcp_event_subscriber.cc new file mode 100644 index 00000000000..9a9c0aeeb74 --- /dev/null +++ b/chromium/media/cast/rtcp/receiver_rtcp_event_subscriber.cc @@ -0,0 +1,96 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/cast/rtcp/receiver_rtcp_event_subscriber.h" + +#include <utility> + +namespace media { +namespace cast { + +ReceiverRtcpEventSubscriber::ReceiverRtcpEventSubscriber( + const size_t max_size_to_retain, EventMediaType type) + : max_size_to_retain_(max_size_to_retain), type_(type) { + DCHECK(max_size_to_retain_ > 0u); + DCHECK(type_ == AUDIO_EVENT || type_ == VIDEO_EVENT); +} + +ReceiverRtcpEventSubscriber::~ReceiverRtcpEventSubscriber() { + DCHECK(thread_checker_.CalledOnValidThread()); +} + +void ReceiverRtcpEventSubscriber::OnReceiveFrameEvent( + const FrameEvent& frame_event) { + DCHECK(thread_checker_.CalledOnValidThread()); + + if (ShouldProcessEvent(frame_event.type, frame_event.media_type)) { + RtcpEvent rtcp_event; + switch (frame_event.type) { + case FRAME_PLAYOUT: + rtcp_event.delay_delta = frame_event.delay_delta; + case FRAME_ACK_SENT: + case FRAME_DECODED: + rtcp_event.type = frame_event.type; + rtcp_event.timestamp = frame_event.timestamp; + rtcp_events_.insert( + std::make_pair(frame_event.rtp_timestamp, rtcp_event)); + break; + default: + break; + } + } + + TruncateMapIfNeeded(); + + DCHECK(rtcp_events_.size() <= max_size_to_retain_); +} + +void ReceiverRtcpEventSubscriber::OnReceivePacketEvent( + const PacketEvent& packet_event) { + DCHECK(thread_checker_.CalledOnValidThread()); + + if (ShouldProcessEvent(packet_event.type, packet_event.media_type)) { + RtcpEvent rtcp_event; + if (packet_event.type == PACKET_RECEIVED) { + rtcp_event.type = packet_event.type; + rtcp_event.timestamp = packet_event.timestamp; + rtcp_event.packet_id = packet_event.packet_id; + rtcp_events_.insert( + std::make_pair(packet_event.rtp_timestamp, rtcp_event)); + } + } + + TruncateMapIfNeeded(); + + DCHECK(rtcp_events_.size() <= max_size_to_retain_); +} + +void ReceiverRtcpEventSubscriber::GetRtcpEventsAndReset( + RtcpEventMultiMap* rtcp_events) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(rtcp_events); + rtcp_events->swap(rtcp_events_); + rtcp_events_.clear(); +} + +void ReceiverRtcpEventSubscriber::TruncateMapIfNeeded() { + // If map size has exceeded |max_size_to_retain_|, remove entry with + // the smallest RTP timestamp. + if (rtcp_events_.size() > max_size_to_retain_) { + DVLOG(3) << "RTCP event map exceeded size limit; " + << "removing oldest entry"; + // This is fine since we only insert elements one at a time. + rtcp_events_.erase(rtcp_events_.begin()); + } +} + +bool ReceiverRtcpEventSubscriber::ShouldProcessEvent( + CastLoggingEvent event_type, EventMediaType event_media_type) { + return type_ == event_media_type && + (event_type == FRAME_ACK_SENT || event_type == FRAME_DECODED || + event_type == FRAME_PLAYOUT || event_type == PACKET_RECEIVED); +} + +} // namespace cast +} // namespace media diff --git a/chromium/media/cast/rtcp/receiver_rtcp_event_subscriber.h b/chromium/media/cast/rtcp/receiver_rtcp_event_subscriber.h new file mode 100644 index 00000000000..84af7cbaf3f --- /dev/null +++ b/chromium/media/cast/rtcp/receiver_rtcp_event_subscriber.h @@ -0,0 +1,79 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_CAST_RTCP_RECEIVER_RTCP_EVENT_SUBSCRIBER_H_ +#define MEDIA_CAST_RTCP_RECEIVER_RTCP_EVENT_SUBSCRIBER_H_ + +#include <map> + +#include "base/threading/thread_checker.h" +#include "media/cast/logging/logging_defines.h" +#include "media/cast/logging/raw_event_subscriber.h" +#include "media/cast/rtcp/rtcp_defines.h" + +namespace media { +namespace cast { + +// A RawEventSubscriber implementation with the following properties: +// - Only processes raw event types that are relevant for sending from cast +// receiver to cast sender via RTCP. +// - Captures information to be sent over to RTCP from raw event logs into the +// more compact RtcpEvent struct. +// - Orders events by RTP timestamp with a multimap. +// - Internally, the map is capped at a maximum size configurable by the caller. +// The subscriber only keeps the most recent events (determined by RTP +// timestamp) up to the size limit. +class ReceiverRtcpEventSubscriber : public RawEventSubscriber { + public: + typedef std::multimap<RtpTimestamp, RtcpEvent> RtcpEventMultiMap; + + // |max_size_to_retain|: The object will keep up to |max_size_to_retain| + // events + // in the map. Once threshold has been reached, an event with the smallest + // RTP timestamp will be removed. + // |type|: Determines whether the subscriber will process only audio or video + // events. + ReceiverRtcpEventSubscriber(const size_t max_size_to_retain, + EventMediaType type); + + virtual ~ReceiverRtcpEventSubscriber(); + + // RawEventSubscriber implementation. + virtual void OnReceiveFrameEvent(const FrameEvent& frame_event) OVERRIDE; + virtual void OnReceivePacketEvent(const PacketEvent& packet_event) OVERRIDE; + + // Assigns events collected to |rtcp_events| and clears them from this + // object. + void GetRtcpEventsAndReset(RtcpEventMultiMap* rtcp_events); + + private: + // If |rtcp_events_.size()| exceeds |max_size_to_retain_|, remove an oldest + // entry (determined by RTP timestamp) so its size no greater than + // |max_size_to_retain_|. + void TruncateMapIfNeeded(); + + // Returns |true| if events of |event_type| and |media_type| + // should be processed. + bool ShouldProcessEvent(CastLoggingEvent event_type, + EventMediaType media_type); + + const size_t max_size_to_retain_; + EventMediaType type_; + + // The key should really be something more than just a RTP timestamp in order + // to differentiate between video and audio frames, but since the + // implementation doesn't mix audio and video frame events, RTP timestamp + // only as key is fine. + RtcpEventMultiMap rtcp_events_; + + // Ensures methods are only called on the main thread. + base::ThreadChecker thread_checker_; + + DISALLOW_COPY_AND_ASSIGN(ReceiverRtcpEventSubscriber); +}; + +} // namespace cast +} // namespace media + +#endif // MEDIA_CAST_RTCP_RECEIVER_RTCP_EVENT_SUBSCRIBER_H_ diff --git a/chromium/media/cast/rtcp/receiver_rtcp_event_subscriber_unittest.cc b/chromium/media/cast/rtcp/receiver_rtcp_event_subscriber_unittest.cc new file mode 100644 index 00000000000..e0d0f172160 --- /dev/null +++ b/chromium/media/cast/rtcp/receiver_rtcp_event_subscriber_unittest.cc @@ -0,0 +1,131 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/test/simple_test_tick_clock.h" +#include "base/time/tick_clock.h" +#include "media/cast/cast_environment.h" +#include "media/cast/logging/logging_defines.h" +#include "media/cast/rtcp/receiver_rtcp_event_subscriber.h" +#include "media/cast/test/fake_single_thread_task_runner.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace media { +namespace cast { + +namespace { + +const size_t kMaxEventEntries = 10u; +const int64 kDelayMs = 20L; + +} // namespace + +class ReceiverRtcpEventSubscriberTest : public ::testing::Test { + protected: + ReceiverRtcpEventSubscriberTest() + : testing_clock_(new base::SimpleTestTickClock()), + task_runner_(new test::FakeSingleThreadTaskRunner(testing_clock_)), + cast_environment_(new CastEnvironment( + scoped_ptr<base::TickClock>(testing_clock_).Pass(), + task_runner_, + task_runner_, + task_runner_)) {} + + virtual ~ReceiverRtcpEventSubscriberTest() {} + + virtual void TearDown() OVERRIDE { + if (event_subscriber_) { + cast_environment_->Logging()->RemoveRawEventSubscriber( + event_subscriber_.get()); + } + } + + void Init(EventMediaType type) { + event_subscriber_.reset( + new ReceiverRtcpEventSubscriber(kMaxEventEntries, type)); + cast_environment_->Logging()->AddRawEventSubscriber( + event_subscriber_.get()); + } + + void InsertEvents() { + // Video events + cast_environment_->Logging()->InsertFrameEventWithDelay( + testing_clock_->NowTicks(), FRAME_PLAYOUT, VIDEO_EVENT, + /*rtp_timestamp*/ 100u, /*frame_id*/ 2u, + base::TimeDelta::FromMilliseconds(kDelayMs)); + cast_environment_->Logging()->InsertFrameEvent( + testing_clock_->NowTicks(), FRAME_DECODED, VIDEO_EVENT, + /*rtp_timestamp*/ 200u, /*frame_id*/ 1u); + cast_environment_->Logging()->InsertPacketEvent( + testing_clock_->NowTicks(), PACKET_RECEIVED, VIDEO_EVENT, + /*rtp_timestamp */ 200u, /*frame_id*/ 2u, /*packet_id*/ 1u, + /*max_packet_id*/ 10u, /*size*/ 1024u); + + // Audio events + cast_environment_->Logging()->InsertFrameEventWithDelay( + testing_clock_->NowTicks(), FRAME_PLAYOUT, AUDIO_EVENT, + /*rtp_timestamp*/ 300u, /*frame_id*/ 4u, + base::TimeDelta::FromMilliseconds(kDelayMs)); + cast_environment_->Logging()->InsertFrameEvent( + testing_clock_->NowTicks(), FRAME_DECODED, AUDIO_EVENT, + /*rtp_timestamp*/ 400u, /*frame_id*/ 3u); + cast_environment_->Logging()->InsertPacketEvent( + testing_clock_->NowTicks(), PACKET_RECEIVED, AUDIO_EVENT, + /*rtp_timestamp */ 400u, /*frame_id*/ 5u, /*packet_id*/ 1u, + /*max_packet_id*/ 10u, /*size*/ 128u); + + // Unrelated events + cast_environment_->Logging()->InsertFrameEvent(testing_clock_->NowTicks(), + FRAME_CAPTURE_END, + VIDEO_EVENT, + /*rtp_timestamp*/ 100u, + /*frame_id*/ 1u); + cast_environment_->Logging()->InsertFrameEvent(testing_clock_->NowTicks(), + FRAME_CAPTURE_END, + AUDIO_EVENT, + /*rtp_timestamp*/ 100u, + /*frame_id*/ 1u); + } + + base::SimpleTestTickClock* testing_clock_; // Owned by CastEnvironment. + scoped_refptr<test::FakeSingleThreadTaskRunner> task_runner_; + scoped_refptr<CastEnvironment> cast_environment_; + scoped_ptr<ReceiverRtcpEventSubscriber> event_subscriber_; +}; + +TEST_F(ReceiverRtcpEventSubscriberTest, LogVideoEvents) { + Init(VIDEO_EVENT); + + InsertEvents(); + ReceiverRtcpEventSubscriber::RtcpEventMultiMap rtcp_events; + event_subscriber_->GetRtcpEventsAndReset(&rtcp_events); + EXPECT_EQ(3u, rtcp_events.size()); +} + +TEST_F(ReceiverRtcpEventSubscriberTest, LogAudioEvents) { + Init(AUDIO_EVENT); + + InsertEvents(); + ReceiverRtcpEventSubscriber::RtcpEventMultiMap rtcp_events; + event_subscriber_->GetRtcpEventsAndReset(&rtcp_events); + EXPECT_EQ(3u, rtcp_events.size()); +} + +TEST_F(ReceiverRtcpEventSubscriberTest, DropEventsWhenSizeExceeded) { + Init(VIDEO_EVENT); + + for (uint32 i = 1u; i <= 10u; ++i) { + cast_environment_->Logging()->InsertFrameEvent( + testing_clock_->NowTicks(), FRAME_DECODED, VIDEO_EVENT, + /*rtp_timestamp*/ i * 10, /*frame_id*/ i); + } + + ReceiverRtcpEventSubscriber::RtcpEventMultiMap rtcp_events; + event_subscriber_->GetRtcpEventsAndReset(&rtcp_events); + EXPECT_EQ(10u, rtcp_events.size()); +} + +} // namespace cast +} // namespace media diff --git a/chromium/media/cast/rtcp/rtcp.cc b/chromium/media/cast/rtcp/rtcp.cc index 4ea4bc99ba9..480b2ac3990 100644 --- a/chromium/media/cast/rtcp/rtcp.cc +++ b/chromium/media/cast/rtcp/rtcp.cc @@ -4,6 +4,7 @@ #include "media/cast/rtcp/rtcp.h" +#include "base/big_endian.h" #include "base/rand_util.h" #include "media/cast/cast_config.h" #include "media/cast/cast_defines.h" @@ -12,28 +13,22 @@ #include "media/cast/rtcp/rtcp_receiver.h" #include "media/cast/rtcp/rtcp_sender.h" #include "media/cast/rtcp/rtcp_utility.h" -#include "net/base/big_endian.h" +#include "media/cast/transport/cast_transport_defines.h" namespace media { namespace cast { static const int kMaxRttMs = 10000; // 10 seconds. - -// Time limit for received RTCP messages when we stop using it for lip-sync. -static const int64 kMaxDiffSinceReceivedRtcpMs = 100000; // 100 seconds. +static const int kMaxDelay = 2000; class LocalRtcpRttFeedback : public RtcpRttFeedback { public: - explicit LocalRtcpRttFeedback(Rtcp* rtcp) - : rtcp_(rtcp) { - } + explicit LocalRtcpRttFeedback(Rtcp* rtcp) : rtcp_(rtcp) {} virtual void OnReceivedDelaySinceLastReport( - uint32 receivers_ssrc, - uint32 last_report, + uint32 receivers_ssrc, uint32 last_report, uint32 delay_since_last_report) OVERRIDE { - rtcp_->OnReceivedDelaySinceLastReport(receivers_ssrc, - last_report, + rtcp_->OnReceivedDelaySinceLastReport(receivers_ssrc, last_report, delay_since_last_report); } @@ -41,31 +36,14 @@ class LocalRtcpRttFeedback : public RtcpRttFeedback { Rtcp* rtcp_; }; -RtcpCastMessage::RtcpCastMessage(uint32 media_ssrc) - : media_ssrc_(media_ssrc) {} - -RtcpCastMessage::~RtcpCastMessage() {} - -RtcpNackMessage::RtcpNackMessage() {} -RtcpNackMessage::~RtcpNackMessage() {} - -RtcpRembMessage::RtcpRembMessage() {} -RtcpRembMessage::~RtcpRembMessage() {} - -RtcpReceiverFrameLogMessage::RtcpReceiverFrameLogMessage(uint32 timestamp) - : rtp_timestamp_(timestamp) {} - -RtcpReceiverFrameLogMessage::~RtcpReceiverFrameLogMessage() {} - class LocalRtcpReceiverFeedback : public RtcpReceiverFeedback { public: LocalRtcpReceiverFeedback(Rtcp* rtcp, - scoped_refptr<CastEnvironment> cast_environment) - : rtcp_(rtcp), cast_environment_(cast_environment) { - } + scoped_refptr<CastEnvironment> cast_environment) + : rtcp_(rtcp), cast_environment_(cast_environment) {} virtual void OnReceivedSenderReport( - const RtcpSenderInfo& remote_sender_info) OVERRIDE { + const transport::RtcpSenderInfo& remote_sender_info) OVERRIDE { rtcp_->OnReceivedNtp(remote_sender_info.ntp_seconds, remote_sender_info.ntp_fraction); if (remote_sender_info.send_packet_count != 0) { @@ -85,80 +63,9 @@ class LocalRtcpReceiverFeedback : public RtcpReceiverFeedback { rtcp_->OnReceivedSendReportRequest(); } - virtual void OnReceivedReceiverLog( - const RtcpReceiverLogMessage& receiver_log) OVERRIDE { - // Add received log messages into our log system. - RtcpReceiverLogMessage::const_iterator it = receiver_log.begin(); - - for (; it != receiver_log.end(); ++it) { - uint32 rtp_timestamp = it->rtp_timestamp_; - - RtcpReceiverEventLogMessages::const_iterator event_it = - it->event_log_messages_.begin(); - for (; event_it != it->event_log_messages_.end(); ++event_it) { - // TODO(pwestin): we need to send in the event_it->event_timestamp to - // the log system too. - switch (event_it->type) { - case kPacketReceived: - cast_environment_->Logging()->InsertPacketEvent(kPacketReceived, - rtp_timestamp, kFrameIdUnknown, event_it->packet_id, 0, 0); - break; - case kAckSent: - case kAudioFrameDecoded: - case kVideoFrameDecoded: - cast_environment_->Logging()->InsertFrameEvent(event_it->type, - rtp_timestamp, kFrameIdUnknown); - break; - case kAudioPlayoutDelay: - case kVideoRenderDelay: - cast_environment_->Logging()->InsertFrameEventWithDelay( - event_it->type, rtp_timestamp, kFrameIdUnknown, - event_it->delay_delta); - break; - default: - VLOG(2) << "Received log message via RTCP that we did not expect: " - << static_cast<int>(event_it->type); - break; - } - } - } - } - - virtual void OnReceivedSenderLog( - const RtcpSenderLogMessage& sender_log) OVERRIDE { - RtcpSenderLogMessage::const_iterator it = sender_log.begin(); - - for (; it != sender_log.end(); ++it) { - uint32 rtp_timestamp = it->rtp_timestamp; - CastLoggingEvent log_event = kUnknown; - - // These events are provided to know the status of frames that never - // reached the receiver. The timing information for these events are not - // relevant and is not sent over the wire. - switch (it->frame_status) { - case kRtcpSenderFrameStatusDroppedByFlowControl: - // A frame that have been dropped by the flow control would have - // kVideoFrameCaptured as its last event in the log. - log_event = kVideoFrameCaptured; - break; - case kRtcpSenderFrameStatusDroppedByEncoder: - // A frame that have been dropped by the encoder would have - // kVideoFrameSentToEncoder as its last event in the log. - log_event = kVideoFrameSentToEncoder; - break; - case kRtcpSenderFrameStatusSentToNetwork: - // A frame that have be encoded is always sent to the network. We - // do not add a new log entry for this. - log_event = kVideoFrameEncoded; - break; - default: - continue; - } - // TODO(pwestin): how do we handle the truncated rtp_timestamp? - // Add received log messages into our log system. - cast_environment_->Logging()->InsertFrameEvent(log_event, rtp_timestamp, - kFrameIdUnknown); - } + virtual void OnReceivedReceiverLog(const RtcpReceiverLogMessage& receiver_log) + OVERRIDE { + rtcp_->OnReceivedReceiverLog(receiver_log); } private: @@ -168,36 +75,34 @@ class LocalRtcpReceiverFeedback : public RtcpReceiverFeedback { Rtcp::Rtcp(scoped_refptr<CastEnvironment> cast_environment, RtcpSenderFeedback* sender_feedback, - PacedPacketSender* paced_packet_sender, - RtpSenderStatistics* rtp_sender_statistics, - RtpReceiverStatistics* rtp_receiver_statistics, - RtcpMode rtcp_mode, - const base::TimeDelta& rtcp_interval, - uint32 local_ssrc, - uint32 remote_ssrc, - const std::string& c_name) - : rtcp_interval_(rtcp_interval), + transport::CastTransportSender* const transport_sender, + transport::PacedPacketSender* paced_packet_sender, + RtpReceiverStatistics* rtp_receiver_statistics, RtcpMode rtcp_mode, + const base::TimeDelta& rtcp_interval, uint32 local_ssrc, + uint32 remote_ssrc, const std::string& c_name, + EventMediaType event_media_type) + : cast_environment_(cast_environment), + transport_sender_(transport_sender), + rtcp_interval_(rtcp_interval), rtcp_mode_(rtcp_mode), local_ssrc_(local_ssrc), remote_ssrc_(remote_ssrc), - rtp_sender_statistics_(rtp_sender_statistics), + c_name_(c_name), + event_media_type_(event_media_type), rtp_receiver_statistics_(rtp_receiver_statistics), - receiver_feedback_(new LocalRtcpReceiverFeedback(this, cast_environment)), rtt_feedback_(new LocalRtcpRttFeedback(this)), + receiver_feedback_(new LocalRtcpReceiverFeedback(this, cast_environment)), rtcp_sender_(new RtcpSender(cast_environment, paced_packet_sender, local_ssrc, c_name)), - last_report_received_(0), - last_received_rtp_timestamp_(0), - last_received_ntp_seconds_(0), - last_received_ntp_fraction_(0), + last_report_truncated_ntp_(0), + local_clock_ahead_by_(ClockDriftSmoother::GetDefaultTimeConstant()), + lip_sync_rtp_timestamp_(0), + lip_sync_ntp_timestamp_(0), min_rtt_(base::TimeDelta::FromMilliseconds(kMaxRttMs)), - number_of_rtt_in_avg_(0), - cast_environment_(cast_environment) { - rtcp_receiver_.reset(new RtcpReceiver(cast_environment, - sender_feedback, + number_of_rtt_in_avg_(0) { + rtcp_receiver_.reset(new RtcpReceiver(cast_environment, sender_feedback, receiver_feedback_.get(), - rtt_feedback_.get(), - local_ssrc)); + rtt_feedback_.get(), local_ssrc)); rtcp_receiver_->SetRemoteSSRC(remote_ssrc); } @@ -209,7 +114,8 @@ bool Rtcp::IsRtcpPacket(const uint8* packet, size_t length) { if (length < kMinLengthOfRtcp) return false; uint8 packet_type = packet[1]; - if (packet_type >= kPacketTypeLow && packet_type <= kPacketTypeHigh) { + if (packet_type >= transport::kPacketTypeLow && + packet_type <= transport::kPacketTypeHigh) { return true; } return false; @@ -219,7 +125,8 @@ bool Rtcp::IsRtcpPacket(const uint8* packet, size_t length) { uint32 Rtcp::GetSsrcOfSender(const uint8* rtcp_buffer, size_t length) { DCHECK_GE(length, kMinLengthOfRtcp) << "Invalid RTCP packet"; uint32 ssrc_of_sender; - net::BigEndianReader big_endian_reader(rtcp_buffer, length); + base::BigEndianReader big_endian_reader( + reinterpret_cast<const char*>(rtcp_buffer), length); big_endian_reader.Skip(4); // Skip header big_endian_reader.ReadU32(&ssrc_of_sender); return ssrc_of_sender; @@ -242,117 +149,145 @@ void Rtcp::IncomingRtcpPacket(const uint8* rtcp_buffer, size_t length) { rtcp_receiver_->IncomingRtcpPacket(&rtcp_parser); } -void Rtcp::SendRtcpFromRtpReceiver(const RtcpCastMessage* cast_message, - RtcpReceiverLogMessage* receiver_log) { +void Rtcp::SendRtcpFromRtpReceiver( + const RtcpCastMessage* cast_message, + const ReceiverRtcpEventSubscriber::RtcpEventMultiMap* rtcp_events) { + DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); uint32 packet_type_flags = 0; base::TimeTicks now = cast_environment_->Clock()->NowTicks(); - RtcpReportBlock report_block; + transport::RtcpReportBlock report_block; RtcpReceiverReferenceTimeReport rrtr; + // Attach our NTP to all RTCP packets; with this information a "smart" sender + // can make decisions based on how old the RTCP message is. + packet_type_flags |= transport::kRtcpRrtr; + ConvertTimeTicksToNtp(now, &rrtr.ntp_seconds, &rrtr.ntp_fraction); + SaveLastSentNtpTime(now, rrtr.ntp_seconds, rrtr.ntp_fraction); + if (cast_message) { - packet_type_flags |= RtcpSender::kRtcpCast; - cast_environment_->Logging()->InsertGenericEvent(kAckSent, - cast_message->ack_frame_id_); + packet_type_flags |= transport::kRtcpCast; } - if (receiver_log) { - packet_type_flags |= RtcpSender::kRtcpReceiverLog; + if (rtcp_events) { + packet_type_flags |= transport::kRtcpReceiverLog; } if (rtcp_mode_ == kRtcpCompound || now >= next_time_to_send_rtcp_) { - packet_type_flags |= RtcpSender::kRtcpRr; + packet_type_flags |= transport::kRtcpRr; - report_block.remote_ssrc = 0; // Not needed to set send side. + report_block.remote_ssrc = 0; // Not needed to set send side. report_block.media_ssrc = remote_ssrc_; // SSRC of the RTP packet sender. if (rtp_receiver_statistics_) { rtp_receiver_statistics_->GetStatistics( - &report_block.fraction_lost, - &report_block.cumulative_lost, - &report_block.extended_high_sequence_number, - &report_block.jitter); - cast_environment_->Logging()->InsertGenericEvent(kJitterMs, - report_block.jitter); - cast_environment_->Logging()->InsertGenericEvent(kPacketLoss, - report_block.fraction_lost); - + &report_block.fraction_lost, &report_block.cumulative_lost, + &report_block.extended_high_sequence_number, &report_block.jitter); } - report_block.last_sr = last_report_received_; + report_block.last_sr = last_report_truncated_ntp_; if (!time_last_report_received_.is_null()) { uint32 delay_seconds = 0; uint32 delay_fraction = 0; base::TimeDelta delta = now - time_last_report_received_; - ConvertTimeToFractions(delta.InMicroseconds(), - &delay_seconds, + ConvertTimeToFractions(delta.InMicroseconds(), &delay_seconds, &delay_fraction); report_block.delay_since_last_sr = ConvertToNtpDiff(delay_seconds, delay_fraction); } else { report_block.delay_since_last_sr = 0; } - - packet_type_flags |= RtcpSender::kRtcpRrtr; - ConvertTimeTicksToNtp(now, &rrtr.ntp_seconds, &rrtr.ntp_fraction); - SaveLastSentNtpTime(now, rrtr.ntp_seconds, rrtr.ntp_fraction); UpdateNextTimeToSendRtcp(); } rtcp_sender_->SendRtcpFromRtpReceiver(packet_type_flags, &report_block, &rrtr, cast_message, - receiver_log); + rtcp_events, + target_delay_ms_); } -void Rtcp::SendRtcpFromRtpSender( - RtcpSenderLogMessage* sender_log_message) { - uint32 packet_type_flags = RtcpSender::kRtcpSr; - base::TimeTicks now = cast_environment_->Clock()->NowTicks(); - - if (sender_log_message) { - packet_type_flags |= RtcpSender::kRtcpSenderLog; - } - - RtcpSenderInfo sender_info; - if (rtp_sender_statistics_) { - rtp_sender_statistics_->GetStatistics(now, &sender_info); - } else { - memset(&sender_info, 0, sizeof(sender_info)); - } - SaveLastSentNtpTime(now, sender_info.ntp_seconds, sender_info.ntp_fraction); - - RtcpDlrrReportBlock dlrr; +void Rtcp::SendRtcpFromRtpSender(base::TimeTicks current_time, + uint32 current_time_as_rtp_timestamp) { + DCHECK(transport_sender_); + uint32 packet_type_flags = transport::kRtcpSr; + uint32 current_ntp_seconds = 0; + uint32 current_ntp_fractions = 0; + ConvertTimeTicksToNtp(current_time, ¤t_ntp_seconds, + ¤t_ntp_fractions); + SaveLastSentNtpTime(current_time, current_ntp_seconds, + current_ntp_fractions); + + transport::RtcpDlrrReportBlock dlrr; if (!time_last_report_received_.is_null()) { - packet_type_flags |= RtcpSender::kRtcpDlrr; - dlrr.last_rr = last_report_received_; + packet_type_flags |= transport::kRtcpDlrr; + dlrr.last_rr = last_report_truncated_ntp_; uint32 delay_seconds = 0; uint32 delay_fraction = 0; - base::TimeDelta delta = now - time_last_report_received_; - ConvertTimeToFractions(delta.InMicroseconds(), - &delay_seconds, + base::TimeDelta delta = current_time - time_last_report_received_; + ConvertTimeToFractions(delta.InMicroseconds(), &delay_seconds, &delay_fraction); dlrr.delay_since_last_rr = ConvertToNtpDiff(delay_seconds, delay_fraction); } - rtcp_sender_->SendRtcpFromRtpSender(packet_type_flags, - &sender_info, - &dlrr, - sender_log_message); + transport_sender_->SendRtcpFromRtpSender( + packet_type_flags, current_ntp_seconds, current_ntp_fractions, + current_time_as_rtp_timestamp, dlrr, local_ssrc_, c_name_); UpdateNextTimeToSendRtcp(); } void Rtcp::OnReceivedNtp(uint32 ntp_seconds, uint32 ntp_fraction) { - last_report_received_ = (ntp_seconds << 16) + (ntp_fraction >> 16); + last_report_truncated_ntp_ = ConvertToNtpDiff(ntp_seconds, ntp_fraction); - base::TimeTicks now = cast_environment_->Clock()->NowTicks(); + const base::TimeTicks now = cast_environment_->Clock()->NowTicks(); time_last_report_received_ = now; + + // TODO(miu): This clock offset calculation does not account for packet + // transit time over the network. End2EndTest.EvilNetwork confirms that this + // contributes a very significant source of error here. Fix this along with + // the RTT clean-up. + const base::TimeDelta measured_offset = + now - ConvertNtpToTimeTicks(ntp_seconds, ntp_fraction); + local_clock_ahead_by_.Update(now, measured_offset); + if (measured_offset < local_clock_ahead_by_.Current()) { + // Logically, the minimum offset between the clocks has to be the correct + // one. For example, the time it took to transmit the current report may + // have been lower than usual, and so some of the error introduced by the + // transmission time can be eliminated. + local_clock_ahead_by_.Reset(now, measured_offset); + } + VLOG(1) << "Local clock is ahead of the remote clock by: " + << "measured=" << measured_offset.InMicroseconds() << " usec, " + << "filtered=" << local_clock_ahead_by_.Current().InMicroseconds() + << " usec."; } -void Rtcp::OnReceivedLipSyncInfo(uint32 rtp_timestamp, - uint32 ntp_seconds, +void Rtcp::OnReceivedLipSyncInfo(uint32 rtp_timestamp, uint32 ntp_seconds, uint32 ntp_fraction) { - last_received_rtp_timestamp_ = rtp_timestamp; - last_received_ntp_seconds_ = ntp_seconds; - last_received_ntp_fraction_ = ntp_fraction; + if (ntp_seconds == 0) { + NOTREACHED(); + return; + } + lip_sync_rtp_timestamp_ = rtp_timestamp; + lip_sync_ntp_timestamp_ = + (static_cast<uint64>(ntp_seconds) << 32) | ntp_fraction; +} + +bool Rtcp::GetLatestLipSyncTimes(uint32* rtp_timestamp, + base::TimeTicks* reference_time) const { + if (!lip_sync_ntp_timestamp_) + return false; + + const base::TimeTicks local_reference_time = + ConvertNtpToTimeTicks(static_cast<uint32>(lip_sync_ntp_timestamp_ >> 32), + static_cast<uint32>(lip_sync_ntp_timestamp_)) + + local_clock_ahead_by_.Current(); + + // Sanity-check: Getting regular lip sync updates? + DCHECK((cast_environment_->Clock()->NowTicks() - local_reference_time) < + base::TimeDelta::FromMinutes(1)); + + *rtp_timestamp = lip_sync_rtp_timestamp_; + *reference_time = local_reference_time; + return true; } void Rtcp::OnReceivedSendReportRequest() { @@ -362,35 +297,13 @@ void Rtcp::OnReceivedSendReportRequest() { next_time_to_send_rtcp_ = now; } -bool Rtcp::RtpTimestampInSenderTime(int frequency, uint32 rtp_timestamp, - base::TimeTicks* rtp_timestamp_in_ticks) const { - if (last_received_ntp_seconds_ == 0) return false; - - int wrap = CheckForWrapAround(rtp_timestamp, last_received_rtp_timestamp_); - int64 rtp_timestamp_int64 = rtp_timestamp; - int64 last_received_rtp_timestamp_int64 = last_received_rtp_timestamp_; - - if (wrap == 1) { - rtp_timestamp_int64 += (1LL << 32); - } else if (wrap == -1) { - last_received_rtp_timestamp_int64 += (1LL << 32); - } - // Time since the last RTCP message. - // Note that this can be negative since we can compare a rtp timestamp from - // a frame older than the last received RTCP message. - int64 rtp_timestamp_diff = - rtp_timestamp_int64 - last_received_rtp_timestamp_int64; - - int frequency_khz = frequency / 1000; - int64 rtp_time_diff_ms = rtp_timestamp_diff / frequency_khz; - - // Sanity check. - if (abs(rtp_time_diff_ms) > kMaxDiffSinceReceivedRtcpMs) return false; +void Rtcp::SetCastReceiverEventHistorySize(size_t size) { + rtcp_receiver_->SetCastReceiverEventHistorySize(size); +} - *rtp_timestamp_in_ticks = ConvertNtpToTimeTicks(last_received_ntp_seconds_, - last_received_ntp_fraction_) + - base::TimeDelta::FromMilliseconds(rtp_time_diff_ms); - return true; +void Rtcp::SetTargetDelay(base::TimeDelta target_delay) { + DCHECK(target_delay.InMilliseconds() < kMaxDelay); + target_delay_ms_ = static_cast<uint16>(target_delay.InMilliseconds()); } void Rtcp::OnReceivedDelaySinceLastReport(uint32 receivers_ssrc, @@ -401,8 +314,8 @@ void Rtcp::OnReceivedDelaySinceLastReport(uint32 receivers_ssrc, return; // Feedback on another report. } - base::TimeDelta sender_delay = cast_environment_->Clock()->NowTicks() - - it->second; + base::TimeDelta sender_delay = + cast_environment_->Clock()->NowTicks() - it->second; UpdateRtt(sender_delay, ConvertFromNtpDiff(delay_since_last_report)); } @@ -411,9 +324,8 @@ void Rtcp::SaveLastSentNtpTime(const base::TimeTicks& now, uint32 last_ntp_fraction) { // Make sure |now| is always greater than the last element in // |last_reports_sent_queue_|. - if (!last_reports_sent_queue_.empty()) { + if (!last_reports_sent_queue_.empty()) DCHECK(now >= last_reports_sent_queue_.back().second); - } uint32 last_report = ConvertToNtpDiff(last_ntp_seconds, last_ntp_fraction); last_reports_sent_map_[last_report] = now; @@ -436,66 +348,85 @@ void Rtcp::SaveLastSentNtpTime(const base::TimeTicks& now, void Rtcp::UpdateRtt(const base::TimeDelta& sender_delay, const base::TimeDelta& receiver_delay) { base::TimeDelta rtt = sender_delay - receiver_delay; + // TODO(miu): Find out why this must be >= 1 ms, and remove the fudge if it's + // bogus. rtt = std::max(rtt, base::TimeDelta::FromMilliseconds(1)); rtt_ = rtt; min_rtt_ = std::min(min_rtt_, rtt); max_rtt_ = std::max(max_rtt_, rtt); + // TODO(miu): Replace "average for all time" with an EWMA, or suitable + // "average over recent past" mechanism. if (number_of_rtt_in_avg_ != 0) { - float ac = static_cast<float>(number_of_rtt_in_avg_); - avg_rtt_ms_= ((ac / (ac + 1.0)) * avg_rtt_ms_) + - ((1.0 / (ac + 1.0)) * rtt.InMilliseconds()); + const double ac = static_cast<double>(number_of_rtt_in_avg_); + avg_rtt_ms_ = ((ac / (ac + 1.0)) * avg_rtt_ms_) + + ((1.0 / (ac + 1.0)) * rtt.InMillisecondsF()); } else { - avg_rtt_ms_ = rtt.InMilliseconds(); + avg_rtt_ms_ = rtt.InMillisecondsF(); } number_of_rtt_in_avg_++; } -bool Rtcp::Rtt(base::TimeDelta* rtt, - base::TimeDelta* avg_rtt, - base::TimeDelta* min_rtt, - base::TimeDelta* max_rtt) const { +bool Rtcp::Rtt(base::TimeDelta* rtt, base::TimeDelta* avg_rtt, + base::TimeDelta* min_rtt, base::TimeDelta* max_rtt) const { DCHECK(rtt) << "Invalid argument"; DCHECK(avg_rtt) << "Invalid argument"; DCHECK(min_rtt) << "Invalid argument"; DCHECK(max_rtt) << "Invalid argument"; if (number_of_rtt_in_avg_ == 0) return false; - cast_environment_->Logging()->InsertGenericEvent(kRttMs, - rtt->InMilliseconds()); *rtt = rtt_; - *avg_rtt = base::TimeDelta::FromMilliseconds(avg_rtt_ms_); + *avg_rtt = base::TimeDelta::FromMillisecondsD(avg_rtt_ms_); *min_rtt = min_rtt_; *max_rtt = max_rtt_; return true; } -int Rtcp::CheckForWrapAround(uint32 new_timestamp, - uint32 old_timestamp) const { - if (new_timestamp < old_timestamp) { - // This difference should be less than -2^31 if we have had a wrap around - // (e.g. |new_timestamp| = 1, |rtcp_rtp_timestamp| = 2^32 - 1). Since it is - // cast to a int32_t, it should be positive. - if (static_cast<int32>(new_timestamp - old_timestamp) > 0) { - return 1; // Forward wrap around. - } - } else if (static_cast<int32>(old_timestamp - new_timestamp) > 0) { - // This difference should be less than -2^31 if we have had a backward wrap - // around. Since it is cast to a int32, it should be positive. - return -1; - } - return 0; -} - void Rtcp::UpdateNextTimeToSendRtcp() { int random = base::RandInt(0, 999); - base::TimeDelta time_to_next = (rtcp_interval_ / 2) + - (rtcp_interval_ * random / 1000); + base::TimeDelta time_to_next = + (rtcp_interval_ / 2) + (rtcp_interval_ * random / 1000); base::TimeTicks now = cast_environment_->Clock()->NowTicks(); next_time_to_send_rtcp_ = now + time_to_next; } +void Rtcp::OnReceivedReceiverLog(const RtcpReceiverLogMessage& receiver_log) { + // Add received log messages into our log system. + RtcpReceiverLogMessage::const_iterator it = receiver_log.begin(); + for (; it != receiver_log.end(); ++it) { + uint32 rtp_timestamp = it->rtp_timestamp_; + + RtcpReceiverEventLogMessages::const_iterator event_it = + it->event_log_messages_.begin(); + for (; event_it != it->event_log_messages_.end(); ++event_it) { + switch (event_it->type) { + case PACKET_RECEIVED: + cast_environment_->Logging()->InsertPacketEvent( + event_it->event_timestamp, event_it->type, + event_media_type_, rtp_timestamp, + kFrameIdUnknown, event_it->packet_id, 0, 0); + break; + case FRAME_ACK_SENT: + case FRAME_DECODED: + cast_environment_->Logging()->InsertFrameEvent( + event_it->event_timestamp, event_it->type, event_media_type_, + rtp_timestamp, kFrameIdUnknown); + break; + case FRAME_PLAYOUT: + cast_environment_->Logging()->InsertFrameEventWithDelay( + event_it->event_timestamp, event_it->type, event_media_type_, + rtp_timestamp, kFrameIdUnknown, event_it->delay_delta); + break; + default: + VLOG(2) << "Received log message via RTCP that we did not expect: " + << static_cast<int>(event_it->type); + break; + } + } + } +} + } // namespace cast } // namespace media diff --git a/chromium/media/cast/rtcp/rtcp.gyp b/chromium/media/cast/rtcp/rtcp.gyp deleted file mode 100644 index 14119988c8e..00000000000 --- a/chromium/media/cast/rtcp/rtcp.gyp +++ /dev/null @@ -1,46 +0,0 @@ -# Copyright 2013 The Chromium Authors. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - -{ - 'targets': [ - { - 'target_name': 'cast_rtcp', - 'type': 'static_library', - 'include_dirs': [ - '<(DEPTH)/', - ], - 'sources': [ - 'rtcp_defines.h', - 'rtcp.h', - 'rtcp.cc', - 'rtcp_receiver.cc', - 'rtcp_receiver.h', - 'rtcp_sender.cc', - 'rtcp_sender.h', - 'rtcp_utility.cc', - 'rtcp_utility.h', - ], # source - 'dependencies': [ - '<(DEPTH)/base/base.gyp:base', - '<(DEPTH)/net/net.gyp:net', - ], - }, - { - 'target_name': 'cast_rtcp_test', - 'type': 'static_library', - 'include_dirs': [ - '<(DEPTH)/', - ], - 'sources': [ - 'test_rtcp_packet_builder.cc', - 'test_rtcp_packet_builder.h', - ], # source - 'dependencies': [ - 'cast_rtcp', - '<(DEPTH)/testing/gtest.gyp:gtest', - ], - }, - ], -} - diff --git a/chromium/media/cast/rtcp/rtcp.h b/chromium/media/cast/rtcp/rtcp.h index aa083a5a4dd..9d0184f9033 100644 --- a/chromium/media/cast/rtcp/rtcp.h +++ b/chromium/media/cast/rtcp/rtcp.h @@ -5,20 +5,23 @@ #ifndef MEDIA_CAST_RTCP_RTCP_H_ #define MEDIA_CAST_RTCP_RTCP_H_ -#include <list> #include <map> #include <queue> -#include <set> #include <string> #include "base/basictypes.h" #include "base/memory/scoped_ptr.h" #include "base/time/tick_clock.h" #include "base/time/time.h" +#include "media/cast/base/clock_drift_smoother.h" #include "media/cast/cast_config.h" #include "media/cast/cast_defines.h" #include "media/cast/cast_environment.h" +#include "media/cast/rtcp/receiver_rtcp_event_subscriber.h" #include "media/cast/rtcp/rtcp_defines.h" +#include "media/cast/transport/cast_transport_defines.h" +#include "media/cast/transport/cast_transport_sender.h" +#include "media/cast/transport/pacing/paced_sender.h" namespace media { namespace cast { @@ -40,14 +43,6 @@ class RtcpSenderFeedback { virtual ~RtcpSenderFeedback() {} }; -class RtpSenderStatistics { - public: - virtual void GetStatistics(const base::TimeTicks& now, - RtcpSenderInfo* sender_info) = 0; - - virtual ~RtpSenderStatistics() {} -}; - class RtpReceiverStatistics { public: virtual void GetStatistics(uint8* fraction_lost, @@ -60,16 +55,20 @@ class RtpReceiverStatistics { class Rtcp { public: + // Rtcp accepts two transports, one to be used by Cast senders + // (CastTransportSender) only, and the other (PacedPacketSender) should only + // be used by the Cast receivers and test applications. Rtcp(scoped_refptr<CastEnvironment> cast_environment, RtcpSenderFeedback* sender_feedback, - PacedPacketSender* paced_packet_sender, - RtpSenderStatistics* rtp_sender_statistics, + transport::CastTransportSender* const transport_sender, // Send-side. + transport::PacedPacketSender* paced_packet_sender, // Receive side. RtpReceiverStatistics* rtp_receiver_statistics, RtcpMode rtcp_mode, const base::TimeDelta& rtcp_interval, uint32 local_ssrc, uint32 remote_ssrc, - const std::string& c_name); + const std::string& c_name, + EventMediaType event_media_type); virtual ~Rtcp(); @@ -78,35 +77,55 @@ class Rtcp { static uint32 GetSsrcOfSender(const uint8* rtcp_buffer, size_t length); base::TimeTicks TimeToSendNextRtcpReport(); - // |sender_log_message| is optional; without it no log messages will be - // attached to the RTCP report; instead a normal RTCP send report will be - // sent. - // Additionally if all messages in |sender_log_message| does - // not fit in the packet the |sender_log_message| will contain the remaining - // unsent messages. - void SendRtcpFromRtpSender(RtcpSenderLogMessage* sender_log_message); - - // |cast_message| and |receiver_log| is optional; if |cast_message| is + + // Send a RTCP sender report. + // |current_time| is the current time reported by a tick clock. + // |current_time_as_rtp_timestamp| is the corresponding RTP timestamp. + void SendRtcpFromRtpSender(base::TimeTicks current_time, + uint32 current_time_as_rtp_timestamp); + + // |cast_message| and |rtcp_events| is optional; if |cast_message| is // provided the RTCP receiver report will append a Cast message containing - // Acks and Nacks; if |receiver_log| is provided the RTCP receiver report will - // append the log messages. If no argument is set a normal RTCP receiver - // report will be sent. Additionally if all messages in |receiver_log| does - // not fit in the packet the |receiver_log| will contain the remaining unsent - // messages. - void SendRtcpFromRtpReceiver(const RtcpCastMessage* cast_message, - RtcpReceiverLogMessage* receiver_log); + // Acks and Nacks; if |rtcp_events| is provided the RTCP receiver report + // will append the log messages. + void SendRtcpFromRtpReceiver( + const RtcpCastMessage* cast_message, + const ReceiverRtcpEventSubscriber::RtcpEventMultiMap* rtcp_events); void IncomingRtcpPacket(const uint8* rtcp_buffer, size_t length); - bool Rtt(base::TimeDelta* rtt, base::TimeDelta* avg_rtt, - base::TimeDelta* min_rtt, base::TimeDelta* max_rtt) const; - bool RtpTimestampInSenderTime(int frequency, - uint32 rtp_timestamp, - base::TimeTicks* rtp_timestamp_in_ticks) const; - protected: - int CheckForWrapAround(uint32 new_timestamp, - uint32 old_timestamp) const; + // TODO(miu): Clean up this method and downstream code: Only VideoSender uses + // this (for congestion control), and only the |rtt| and |avg_rtt| values, and + // it's not clear that any of the downstream code is doing the right thing + // with this data. + bool Rtt(base::TimeDelta* rtt, + base::TimeDelta* avg_rtt, + base::TimeDelta* min_rtt, + base::TimeDelta* max_rtt) const; + + bool is_rtt_available() const { return number_of_rtt_in_avg_ > 0; } + + // If available, returns true and sets the output arguments to the latest + // lip-sync timestamps gleaned from the sender reports. While the sender + // provides reference NTP times relative to its own wall clock, the + // |reference_time| returned here has been translated to the local + // CastEnvironment clock. + bool GetLatestLipSyncTimes(uint32* rtp_timestamp, + base::TimeTicks* reference_time) const; + + // Set the history size to record Cast receiver events. The event history is + // used to remove duplicates. The history will store at most |size| events. + void SetCastReceiverEventHistorySize(size_t size); + // Update the target delay. Will be added to every report sent back to the + // sender. + // TODO(miu): Remove this deprecated functionality. The sender ignores this. + void SetTargetDelay(base::TimeDelta target_delay); + + void OnReceivedReceiverLog(const RtcpReceiverLogMessage& receiver_log); + + protected: + void OnReceivedNtp(uint32 ntp_seconds, uint32 ntp_fraction); void OnReceivedLipSyncInfo(uint32 rtp_timestamp, uint32 ntp_seconds, uint32 ntp_fraction); @@ -115,13 +134,6 @@ class Rtcp { friend class LocalRtcpRttFeedback; friend class LocalRtcpReceiverFeedback; - void SendRtcp(const base::TimeTicks& now, - uint32 packet_type_flags, - uint32 media_ssrc, - const RtcpCastMessage* cast_message); - - void OnReceivedNtp(uint32 ntp_seconds, uint32 ntp_fraction); - void OnReceivedDelaySinceLastReport(uint32 receivers_ssrc, uint32 last_report, uint32 delay_since_last_report); @@ -133,17 +145,20 @@ class Rtcp { void UpdateNextTimeToSendRtcp(); - void SaveLastSentNtpTime(const base::TimeTicks& now, uint32 last_ntp_seconds, + void SaveLastSentNtpTime(const base::TimeTicks& now, + uint32 last_ntp_seconds, uint32 last_ntp_fraction); scoped_refptr<CastEnvironment> cast_environment_; + transport::CastTransportSender* const transport_sender_; const base::TimeDelta rtcp_interval_; const RtcpMode rtcp_mode_; const uint32 local_ssrc_; const uint32 remote_ssrc_; + const std::string c_name_; + const EventMediaType event_media_type_; // Not owned by this class. - RtpSenderStatistics* const rtp_sender_statistics_; RtpReceiverStatistics* const rtp_receiver_statistics_; scoped_ptr<LocalRtcpRttFeedback> rtt_feedback_; @@ -154,18 +169,33 @@ class Rtcp { base::TimeTicks next_time_to_send_rtcp_; RtcpSendTimeMap last_reports_sent_map_; RtcpSendTimeQueue last_reports_sent_queue_; + + // The truncated (i.e., 64-->32-bit) NTP timestamp provided in the last report + // from the remote peer, along with the local time at which the report was + // received. These values are used for ping-pong'ing NTP timestamps between + // the peers so that they can estimate the network's round-trip time. + uint32 last_report_truncated_ntp_; base::TimeTicks time_last_report_received_; - uint32 last_report_received_; - uint32 last_received_rtp_timestamp_; - uint32 last_received_ntp_seconds_; - uint32 last_received_ntp_fraction_; + // Maintains a smoothed offset between the local clock and the remote clock. + // Calling this member's Current() method is only valid if + // |time_last_report_received_| is not "null." + ClockDriftSmoother local_clock_ahead_by_; + + // Latest "lip sync" info from the sender. The sender provides the RTP + // timestamp of some frame of its choosing and also a corresponding reference + // NTP timestamp sampled from a clock common to all media streams. It is + // expected that the sender will update this data regularly and in a timely + // manner (e.g., about once per second). + uint32 lip_sync_rtp_timestamp_; + uint64 lip_sync_ntp_timestamp_; base::TimeDelta rtt_; base::TimeDelta min_rtt_; base::TimeDelta max_rtt_; int number_of_rtt_in_avg_; - float avg_rtt_ms_; + double avg_rtt_ms_; + uint16 target_delay_ms_; DISALLOW_COPY_AND_ASSIGN(Rtcp); }; diff --git a/chromium/media/cast/rtcp/rtcp_defines.cc b/chromium/media/cast/rtcp/rtcp_defines.cc new file mode 100644 index 00000000000..214100d4d9e --- /dev/null +++ b/chromium/media/cast/rtcp/rtcp_defines.cc @@ -0,0 +1,49 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/cast/rtcp/rtcp_defines.h" + +#include "media/cast/logging/logging_defines.h" + +namespace media { +namespace cast { + +RtcpCastMessage::RtcpCastMessage(uint32 media_ssrc) + : media_ssrc_(media_ssrc), ack_frame_id_(0u), target_delay_ms_(0) {} +RtcpCastMessage::~RtcpCastMessage() {} + +void RtcpCastMessage::Copy(const RtcpCastMessage& cast_message) { + media_ssrc_ = cast_message.media_ssrc_; + ack_frame_id_ = cast_message.ack_frame_id_; + target_delay_ms_ = cast_message.target_delay_ms_; + missing_frames_and_packets_ = cast_message.missing_frames_and_packets_; +} + +RtcpReceiverEventLogMessage::RtcpReceiverEventLogMessage() + : type(UNKNOWN), packet_id(0u) {} +RtcpReceiverEventLogMessage::~RtcpReceiverEventLogMessage() {} + +RtcpReceiverFrameLogMessage::RtcpReceiverFrameLogMessage(uint32 timestamp) + : rtp_timestamp_(timestamp) {} +RtcpReceiverFrameLogMessage::~RtcpReceiverFrameLogMessage() {} + +RtcpRpsiMessage::RtcpRpsiMessage() + : remote_ssrc(0u), payload_type(0u), picture_id(0u) {} +RtcpRpsiMessage::~RtcpRpsiMessage() {} + +RtcpNackMessage::RtcpNackMessage() : remote_ssrc(0u) {} +RtcpNackMessage::~RtcpNackMessage() {} + +RtcpRembMessage::RtcpRembMessage() : remb_bitrate(0u) {} +RtcpRembMessage::~RtcpRembMessage() {} + +RtcpReceiverReferenceTimeReport::RtcpReceiverReferenceTimeReport() + : remote_ssrc(0u), ntp_seconds(0u), ntp_fraction(0u) {} +RtcpReceiverReferenceTimeReport::~RtcpReceiverReferenceTimeReport() {} + +RtcpEvent::RtcpEvent() : type(UNKNOWN), packet_id(0u) {} +RtcpEvent::~RtcpEvent() {} + +} // namespace cast +} // namespace media diff --git a/chromium/media/cast/rtcp/rtcp_defines.h b/chromium/media/cast/rtcp/rtcp_defines.h index 0277bd1feaf..31795648c64 100644 --- a/chromium/media/cast/rtcp/rtcp_defines.h +++ b/chromium/media/cast/rtcp/rtcp_defines.h @@ -5,45 +5,42 @@ #ifndef MEDIA_CAST_RTCP_RTCP_DEFINES_H_ #define MEDIA_CAST_RTCP_RTCP_DEFINES_H_ -#include <list> #include <map> #include <set> #include "media/cast/cast_config.h" #include "media/cast/cast_defines.h" #include "media/cast/logging/logging_defines.h" +#include "media/cast/transport/cast_transport_defines.h" namespace media { namespace cast { +static const size_t kRtcpCastLogHeaderSize = 12; +static const size_t kRtcpReceiverFrameLogSize = 8; +static const size_t kRtcpReceiverEventLogSize = 4; + // Handle the per frame ACK and NACK messages. class RtcpCastMessage { public: explicit RtcpCastMessage(uint32 media_ssrc); ~RtcpCastMessage(); + void Copy(const RtcpCastMessage& cast_message); + uint32 media_ssrc_; uint32 ack_frame_id_; + uint16 target_delay_ms_; MissingFramesAndPacketsMap missing_frames_and_packets_; -}; - -// Log messages form sender to receiver. -enum RtcpSenderFrameStatus { - kRtcpSenderFrameStatusUnknown = 0, - kRtcpSenderFrameStatusDroppedByEncoder = 1, - kRtcpSenderFrameStatusDroppedByFlowControl = 2, - kRtcpSenderFrameStatusSentToNetwork = 3, -}; -struct RtcpSenderFrameLogMessage { - RtcpSenderFrameStatus frame_status; - uint32 rtp_timestamp; + DISALLOW_COPY_AND_ASSIGN(RtcpCastMessage); }; -typedef std::list<RtcpSenderFrameLogMessage> RtcpSenderLogMessage; - // Log messages from receiver to sender. struct RtcpReceiverEventLogMessage { + RtcpReceiverEventLogMessage(); + ~RtcpReceiverEventLogMessage(); + CastLoggingEvent type; base::TimeTicks event_timestamp; base::TimeDelta delay_delta; @@ -52,99 +49,84 @@ struct RtcpReceiverEventLogMessage { typedef std::list<RtcpReceiverEventLogMessage> RtcpReceiverEventLogMessages; -class RtcpReceiverFrameLogMessage { - public: +struct RtcpReceiverFrameLogMessage { explicit RtcpReceiverFrameLogMessage(uint32 rtp_timestamp); ~RtcpReceiverFrameLogMessage(); uint32 rtp_timestamp_; RtcpReceiverEventLogMessages event_log_messages_; -}; -typedef std::list<RtcpReceiverFrameLogMessage> RtcpReceiverLogMessage; - -struct RtcpSenderInfo { - // First three members are used for lipsync. - // First two members are used for rtt. - uint32 ntp_seconds; - uint32 ntp_fraction; - uint32 rtp_timestamp; - uint32 send_packet_count; - size_t send_octet_count; + // TODO(mikhal): Investigate what's the best way to allow adding + // DISALLOW_COPY_AND_ASSIGN, as currently it contradicts the implementation + // and possible changes have a big impact on design. }; -struct RtcpReportBlock { - uint32 remote_ssrc; // SSRC of sender of this report. - uint32 media_ssrc; // SSRC of the RTP packet sender. - uint8 fraction_lost; - uint32 cumulative_lost; // 24 bits valid. - uint32 extended_high_sequence_number; - uint32 jitter; - uint32 last_sr; - uint32 delay_since_last_sr; -}; +typedef std::list<RtcpReceiverFrameLogMessage> RtcpReceiverLogMessage; struct RtcpRpsiMessage { + RtcpRpsiMessage(); + ~RtcpRpsiMessage(); + uint32 remote_ssrc; uint8 payload_type; uint64 picture_id; }; -class RtcpNackMessage { - public: +struct RtcpNackMessage { RtcpNackMessage(); ~RtcpNackMessage(); uint32 remote_ssrc; std::list<uint16> nack_list; + + DISALLOW_COPY_AND_ASSIGN(RtcpNackMessage); }; -class RtcpRembMessage { - public: +struct RtcpRembMessage { RtcpRembMessage(); ~RtcpRembMessage(); uint32 remb_bitrate; std::list<uint32> remb_ssrcs; + + DISALLOW_COPY_AND_ASSIGN(RtcpRembMessage); }; struct RtcpReceiverReferenceTimeReport { + RtcpReceiverReferenceTimeReport(); + ~RtcpReceiverReferenceTimeReport(); + uint32 remote_ssrc; uint32 ntp_seconds; uint32 ntp_fraction; }; -struct RtcpDlrrReportBlock { - uint32 last_rr; - uint32 delay_since_last_rr; -}; - -inline bool operator==(RtcpReportBlock lhs, RtcpReportBlock rhs) { - return lhs.remote_ssrc == rhs.remote_ssrc && - lhs.media_ssrc == rhs.media_ssrc && - lhs.fraction_lost == rhs.fraction_lost && - lhs.cumulative_lost == rhs.cumulative_lost && - lhs.extended_high_sequence_number == rhs.extended_high_sequence_number && - lhs.jitter == rhs.jitter && - lhs.last_sr == rhs.last_sr && - lhs.delay_since_last_sr == rhs.delay_since_last_sr; -} - -inline bool operator==(RtcpSenderInfo lhs, RtcpSenderInfo rhs) { - return lhs.ntp_seconds == rhs.ntp_seconds && - lhs.ntp_fraction == rhs.ntp_fraction && - lhs.rtp_timestamp == rhs.rtp_timestamp && - lhs.send_packet_count == rhs.send_packet_count && - lhs.send_octet_count == rhs.send_octet_count; -} - inline bool operator==(RtcpReceiverReferenceTimeReport lhs, RtcpReceiverReferenceTimeReport rhs) { return lhs.remote_ssrc == rhs.remote_ssrc && - lhs.ntp_seconds == rhs.ntp_seconds && - lhs.ntp_fraction == rhs.ntp_fraction; + lhs.ntp_seconds == rhs.ntp_seconds && + lhs.ntp_fraction == rhs.ntp_fraction; } +// Struct used by raw event subscribers as an intermediate format before +// sending off to the other side via RTCP. +// (i.e., {Sender,Receiver}RtcpEventSubscriber) +struct RtcpEvent { + RtcpEvent(); + ~RtcpEvent(); + + CastLoggingEvent type; + + // Time of event logged. + base::TimeTicks timestamp; + + // Render/playout delay. Only set for FRAME_PLAYOUT events. + base::TimeDelta delay_delta; + + // Only set for packet events. + uint16 packet_id; +}; + } // namespace cast } // namespace media diff --git a/chromium/media/cast/rtcp/rtcp_receiver.cc b/chromium/media/cast/rtcp/rtcp_receiver.cc index 152ebc00d7b..3be8e921c46 100644 --- a/chromium/media/cast/rtcp/rtcp_receiver.cc +++ b/chromium/media/cast/rtcp/rtcp_receiver.cc @@ -6,50 +6,31 @@ #include "base/logging.h" #include "media/cast/rtcp/rtcp_utility.h" +#include "media/cast/transport/cast_transport_defines.h" namespace { -media::cast::CastLoggingEvent TranslateToLogEventFromWireFormat(uint8 event) { - switch (event) { - case 1: - return media::cast::kAckSent; - case 2: - return media::cast::kAudioPlayoutDelay; - case 3: - return media::cast::kAudioFrameDecoded; - case 4: - return media::cast::kVideoFrameDecoded; - case 5: - return media::cast::kVideoRenderDelay; - case 6: - return media::cast::kPacketReceived; - default: - // If the sender adds new log messages we will end up here until we add - // the new messages in the receiver. - VLOG(1) << "Unexpected log message received: " << static_cast<int>(event); - NOTREACHED(); - return media::cast::kUnknown; - } -} - -media::cast::RtcpSenderFrameStatus TranslateToFrameStatusFromWireFormat( - uint8 status) { - switch (status) { - case 0: - return media::cast::kRtcpSenderFrameStatusUnknown; - case 1: - return media::cast::kRtcpSenderFrameStatusDroppedByEncoder; - case 2: - return media::cast::kRtcpSenderFrameStatusDroppedByFlowControl; - case 3: - return media::cast::kRtcpSenderFrameStatusSentToNetwork; - default: - // If the sender adds new log messages we will end up here until we add - // the new messages in the receiver. - NOTREACHED(); - VLOG(1) << "Unexpected status received: " << static_cast<int>(status); - return media::cast::kRtcpSenderFrameStatusUnknown; - } +// A receiver frame event is identified by frame RTP timestamp, event timestamp +// and event type. +// A receiver packet event is identified by all of the above plus packet id. +// The key format is as follows: +// First uint64: +// bits 0-11: zeroes (unused). +// bits 12-15: event type ID. +// bits 16-31: packet ID if packet event, 0 otherwise. +// bits 32-63: RTP timestamp. +// Second uint64: +// bits 0-63: event TimeTicks internal value. +std::pair<uint64, uint64> GetReceiverEventKey( + uint32 frame_rtp_timestamp, const base::TimeTicks& event_timestamp, + uint8 event_type, uint16 packet_id_or_zero) { + uint64 value1 = event_type; + value1 <<= 16; + value1 |= packet_id_or_zero; + value1 <<= 32; + value1 |= frame_rtp_timestamp; + return std::make_pair( + value1, static_cast<uint64>(event_timestamp.ToInternalValue())); } } // namespace @@ -67,12 +48,15 @@ RtcpReceiver::RtcpReceiver(scoped_refptr<CastEnvironment> cast_environment, sender_feedback_(sender_feedback), receiver_feedback_(receiver_feedback), rtt_feedback_(rtt_feedback), - cast_environment_(cast_environment) {} + cast_environment_(cast_environment), + receiver_event_history_size_(0) {} RtcpReceiver::~RtcpReceiver() {} -void RtcpReceiver::SetRemoteSSRC(uint32 ssrc) { - remote_ssrc_ = ssrc; +void RtcpReceiver::SetRemoteSSRC(uint32 ssrc) { remote_ssrc_ = ssrc; } + +void RtcpReceiver::SetCastReceiverEventHistorySize(size_t size) { + receiver_event_history_size_ = size; } void RtcpReceiver::IncomingRtcpPacket(RtcpParser* rtcp_parser) { @@ -117,9 +101,6 @@ void RtcpReceiver::IncomingRtcpPacket(RtcpParser* rtcp_parser) { case kRtcpApplicationSpecificCastReceiverLogCode: HandleApplicationSpecificCastReceiverLog(rtcp_parser); break; - case kRtcpApplicationSpecificCastSenderLogCode: - HandleApplicationSpecificCastSenderLog(rtcp_parser); - break; case kRtcpPayloadSpecificRembCode: case kRtcpPayloadSpecificRembItemCode: case kRtcpPayloadSpecificCastCode: @@ -151,16 +132,15 @@ void RtcpReceiver::HandleSenderReport(RtcpParser* rtcp_parser) { // Synchronization source identifier for the originator of this SR packet. uint32 remote_ssrc = rtcp_field.sender_report.sender_ssrc; - VLOG(1) << "Cast RTCP received SR from SSRC " << remote_ssrc; + VLOG(2) << "Cast RTCP received SR from SSRC " << remote_ssrc; if (remote_ssrc_ == remote_ssrc) { - RtcpSenderInfo remote_sender_info; + transport::RtcpSenderInfo remote_sender_info; remote_sender_info.ntp_seconds = rtcp_field.sender_report.ntp_most_significant; remote_sender_info.ntp_fraction = rtcp_field.sender_report.ntp_least_significant; - remote_sender_info.rtp_timestamp = - rtcp_field.sender_report.rtp_timestamp; + remote_sender_info.rtp_timestamp = rtcp_field.sender_report.rtp_timestamp; remote_sender_info.send_packet_count = rtcp_field.sender_report.sender_packet_count; remote_sender_info.send_octet_count = @@ -184,7 +164,7 @@ void RtcpReceiver::HandleReceiverReport(RtcpParser* rtcp_parser) { uint32 remote_ssrc = rtcp_field.receiver_report.sender_ssrc; - VLOG(1) << "Cast RTCP received RR from SSRC " << remote_ssrc; + VLOG(2) << "Cast RTCP received RR from SSRC " << remote_ssrc; rtcp_field_type = rtcp_parser->Iterate(); while (rtcp_field_type == kRtcpReportBlockItemCode) { @@ -211,13 +191,9 @@ void RtcpReceiver::HandleReportBlock(const RtcpField* rtcp_field, // This block is not for us ignore it. return; } - VLOG(1) << "Cast RTCP received RB from SSRC " << remote_ssrc; - cast_environment_->Logging()->InsertGenericEvent(kPacketLoss, - rb.fraction_lost); - cast_environment_->Logging()->InsertGenericEvent(kJitterMs, - rb.jitter); + VLOG(2) << "Cast RTCP received RB from SSRC " << remote_ssrc; - RtcpReportBlock report_block; + transport::RtcpReportBlock report_block; report_block.remote_ssrc = remote_ssrc; report_block.media_ssrc = rb.ssrc; report_block.fraction_lost = rb.fraction_lost; @@ -229,9 +205,8 @@ void RtcpReceiver::HandleReportBlock(const RtcpField* rtcp_field, report_block.delay_since_last_sr = rb.delay_last_sender_report; if (rtt_feedback_) { - rtt_feedback_->OnReceivedDelaySinceLastReport(rb.ssrc, - rb.last_sender_report, - rb.delay_last_sender_report); + rtt_feedback_->OnReceivedDelaySinceLastReport( + rb.ssrc, rb.last_sender_report, rb.delay_last_sender_report); } } @@ -245,7 +220,7 @@ void RtcpReceiver::HandleSDES(RtcpParser* rtcp_parser) { void RtcpReceiver::HandleSDESChunk(RtcpParser* rtcp_parser) { const RtcpField& rtcp_field = rtcp_parser->Field(); - VLOG(1) << "Cast RTCP received SDES with cname " << rtcp_field.c_name.name; + VLOG(2) << "Cast RTCP received SDES with cname " << rtcp_field.c_name.name; } void RtcpReceiver::HandleXr(RtcpParser* rtcp_parser) { @@ -337,7 +312,7 @@ void RtcpReceiver::HandleBYE(RtcpParser* rtcp_parser) { const RtcpField& rtcp_field = rtcp_parser->Field(); uint32 remote_ssrc = rtcp_field.bye.sender_ssrc; if (remote_ssrc_ == remote_ssrc) { - VLOG(1) << "Cast RTCP received BYE from SSRC " << remote_ssrc; + VLOG(2) << "Cast RTCP received BYE from SSRC " << remote_ssrc; } rtcp_parser->Iterate(); } @@ -346,7 +321,7 @@ void RtcpReceiver::HandlePLI(RtcpParser* rtcp_parser) { const RtcpField& rtcp_field = rtcp_parser->Field(); if (ssrc_ == rtcp_field.pli.media_ssrc) { // Received a signal that we need to send a new key frame. - VLOG(1) << "Cast RTCP received PLI on our SSRC " << ssrc_; + VLOG(2) << "Cast RTCP received PLI on our SSRC " << ssrc_; } rtcp_parser->Iterate(); } @@ -377,7 +352,7 @@ void RtcpReceiver::HandleRpsi(RtcpParser* rtcp_parser) { } rpsi_picture_id += (rtcp_field.rpsi.native_bit_string[bytes - 1] & 0x7f); - VLOG(1) << "Cast RTCP received RPSI with picture_id " << rpsi_picture_id; + VLOG(2) << "Cast RTCP received RPSI with picture_id " << rpsi_picture_id; } void RtcpReceiver::HandlePayloadSpecificApp(RtcpParser* rtcp_parser) { @@ -421,7 +396,7 @@ void RtcpReceiver::HandlePayloadSpecificRembItem(RtcpParser* rtcp_parser) { for (int i = 0; i < rtcp_field.remb_item.number_of_ssrcs; ++i) { if (rtcp_field.remb_item.ssrcs[i] == ssrc_) { // Found matching ssrc. - VLOG(1) << "Cast RTCP received REMB with received_bitrate " + VLOG(2) << "Cast RTCP received REMB with received_bitrate " << rtcp_field.remb_item.bitrate; return; } @@ -450,11 +425,15 @@ void RtcpReceiver::HandleApplicationSpecificCastReceiverLog( field_type = rtcp_parser->Iterate(); while (field_type == kRtcpApplicationSpecificCastReceiverLogEventCode) { - HandleApplicationSpecificCastReceiverEventLog(rtcp_parser, + HandleApplicationSpecificCastReceiverEventLog( + rtcp_field.cast_receiver_log.rtp_timestamp, + rtcp_parser, &frame_log.event_log_messages_); field_type = rtcp_parser->Iterate(); } - receiver_log.push_back(frame_log); + + if (!frame_log.event_log_messages_.empty()) + receiver_log.push_back(frame_log); } if (receiver_feedback_ && !receiver_log.empty()) { @@ -463,52 +442,50 @@ void RtcpReceiver::HandleApplicationSpecificCastReceiverLog( } void RtcpReceiver::HandleApplicationSpecificCastReceiverEventLog( + uint32 frame_rtp_timestamp, RtcpParser* rtcp_parser, RtcpReceiverEventLogMessages* event_log_messages) { const RtcpField& rtcp_field = rtcp_parser->Field(); - RtcpReceiverEventLogMessage event_log; - event_log.type = TranslateToLogEventFromWireFormat( - rtcp_field.cast_receiver_log.event); - event_log.event_timestamp = base::TimeTicks() + + const uint8 event = rtcp_field.cast_receiver_log.event; + const CastLoggingEvent event_type = TranslateToLogEventFromWireFormat(event); + uint16 packet_id = event_type == PACKET_RECEIVED ? + rtcp_field.cast_receiver_log.delay_delta_or_packet_id.packet_id : 0; + const base::TimeTicks event_timestamp = + base::TimeTicks() + base::TimeDelta::FromMilliseconds( rtcp_field.cast_receiver_log.event_timestamp_base + rtcp_field.cast_receiver_log.event_timestamp_delta); - event_log.delay_delta = base::TimeDelta::FromMilliseconds( - rtcp_field.cast_receiver_log.delay_delta_or_packet_id); - event_log.packet_id = - rtcp_field.cast_receiver_log.delay_delta_or_packet_id; - event_log_messages->push_back(event_log); -} - -void RtcpReceiver::HandleApplicationSpecificCastSenderLog( - RtcpParser* rtcp_parser) { - const RtcpField& rtcp_field = rtcp_parser->Field(); - uint32 remote_ssrc = rtcp_field.cast_sender_log.sender_ssrc; - if (remote_ssrc_ != remote_ssrc) { - RtcpFieldTypes field_type; - // Message not to us. Iterate until we have passed this message. - do { - field_type = rtcp_parser->Iterate(); - } while (field_type == kRtcpApplicationSpecificCastSenderLogCode); + // The following code checks to see if we have already seen this event. + // The algorithm works by maintaining a sliding window of events. We have + // a queue and a set of events. We enqueue every new event and insert it + // into the set. When the queue becomes too big we remove the oldest event + // from both the queue and the set. + ReceiverEventKey key = + GetReceiverEventKey( + frame_rtp_timestamp, event_timestamp, event, packet_id); + if (receiver_event_key_set_.find(key) != receiver_event_key_set_.end()) { return; + } else { + receiver_event_key_set_.insert(key); + receiver_event_key_queue_.push(key); + + if (receiver_event_key_queue_.size() > receiver_event_history_size_) { + const ReceiverEventKey oldest_key = receiver_event_key_queue_.front(); + receiver_event_key_queue_.pop(); + receiver_event_key_set_.erase(oldest_key); + } } - RtcpSenderLogMessage sender_log; - RtcpFieldTypes field_type = rtcp_parser->Iterate(); - while (field_type == kRtcpApplicationSpecificCastSenderLogCode) { - const RtcpField& rtcp_field = rtcp_parser->Field(); - RtcpSenderFrameLogMessage frame_log; - frame_log.frame_status = - TranslateToFrameStatusFromWireFormat(rtcp_field.cast_sender_log.status); - frame_log.rtp_timestamp = rtcp_field.cast_sender_log.rtp_timestamp; - sender_log.push_back(frame_log); - field_type = rtcp_parser->Iterate(); - } - if (receiver_feedback_) { - receiver_feedback_->OnReceivedSenderLog(sender_log); - } + RtcpReceiverEventLogMessage event_log; + event_log.type = event_type; + event_log.event_timestamp = event_timestamp; + event_log.delay_delta = base::TimeDelta::FromMilliseconds( + rtcp_field.cast_receiver_log.delay_delta_or_packet_id.delay_delta); + event_log.packet_id = + rtcp_field.cast_receiver_log.delay_delta_or_packet_id.packet_id; + event_log_messages->push_back(event_log); } void RtcpReceiver::HandlePayloadSpecificCastItem(RtcpParser* rtcp_parser) { @@ -516,6 +493,7 @@ void RtcpReceiver::HandlePayloadSpecificCastItem(RtcpParser* rtcp_parser) { RtcpCastMessage cast_message(remote_ssrc_); cast_message.ack_frame_id_ = ack_frame_id_wrap_helper_.MapTo32bitsFrameId( rtcp_field.cast_item.last_frame_id); + cast_message.target_delay_ms_ = rtcp_field.cast_item.target_delay_ms; RtcpFieldTypes packet_type = rtcp_parser->Iterate(); while (packet_type == kRtcpPayloadSpecificCastNackItemCode) { @@ -545,15 +523,15 @@ void RtcpReceiver::HandlePayloadSpecificCastNackItem( frame_it = ret.first; DCHECK(frame_it != missing_frames_and_packets->end()) << "Invalid state"; } - if (rtcp_field->cast_nack_item.packet_id == kRtcpCastAllPacketsLost) { + uint16 packet_id = rtcp_field->cast_nack_item.packet_id; + frame_it->second.insert(packet_id); + + if (packet_id == kRtcpCastAllPacketsLost) { // Special case all packets in a frame is missing. return; } - uint16 packet_id = rtcp_field->cast_nack_item.packet_id; uint8 bitmask = rtcp_field->cast_nack_item.bitmask; - frame_it->second.insert(packet_id); - if (bitmask) { for (int i = 1; i <= 8; ++i) { if (bitmask & 1) { @@ -576,9 +554,10 @@ void RtcpReceiver::HandleFIR(RtcpParser* rtcp_parser) { void RtcpReceiver::HandleFIRItem(const RtcpField* rtcp_field) { // Is it our sender that is requested to generate a new keyframe. - if (ssrc_ != rtcp_field->fir_item.ssrc) return; + if (ssrc_ != rtcp_field->fir_item.ssrc) + return; - VLOG(1) << "Cast RTCP received FIR on our SSRC " << ssrc_; + VLOG(2) << "Cast RTCP received FIR on our SSRC " << ssrc_; } } // namespace cast diff --git a/chromium/media/cast/rtcp/rtcp_receiver.h b/chromium/media/cast/rtcp/rtcp_receiver.h index 81383c4ec10..d3cef9e57b4 100644 --- a/chromium/media/cast/rtcp/rtcp_receiver.h +++ b/chromium/media/cast/rtcp/rtcp_receiver.h @@ -5,10 +5,13 @@ #ifndef MEDIA_CAST_RTCP_RTCP_RECEIVER_H_ #define MEDIA_CAST_RTCP_RTCP_RECEIVER_H_ -#include "media/cast/net/cast_net_defines.h" +#include <queue> + +#include "base/containers/hash_tables.h" #include "media/cast/rtcp/rtcp.h" #include "media/cast/rtcp/rtcp_defines.h" #include "media/cast/rtcp/rtcp_utility.h" +#include "media/cast/transport/cast_transport_defines.h" namespace media { namespace cast { @@ -16,7 +19,7 @@ namespace cast { class RtcpReceiverFeedback { public: virtual void OnReceivedSenderReport( - const RtcpSenderInfo& remote_sender_info) = 0; + const transport::RtcpSenderInfo& remote_sender_info) = 0; virtual void OnReceiverReferenceTimeReport( const RtcpReceiverReferenceTimeReport& remote_time_report) = 0; @@ -26,9 +29,6 @@ class RtcpReceiverFeedback { virtual void OnReceivedReceiverLog( const RtcpReceiverLogMessage& receiver_log) = 0; - virtual void OnReceivedSenderLog( - const RtcpSenderLogMessage& sender_log) = 0; - virtual ~RtcpReceiverFeedback() {} }; @@ -53,6 +53,10 @@ class RtcpReceiver { void SetRemoteSSRC(uint32 ssrc); + // Set the history size to record Cast receiver events. Event history is + // used to remove duplicates. The history has no more than |size| events. + void SetCastReceiverEventHistorySize(size_t size); + void IncomingRtcpPacket(RtcpParser* rtcp_parser); private: @@ -60,8 +64,7 @@ class RtcpReceiver { void HandleReceiverReport(RtcpParser* rtcp_parser); - void HandleReportBlock(const RtcpField* rtcp_field, - uint32 remote_ssrc); + void HandleReportBlock(const RtcpField* rtcp_field, uint32 remote_ssrc); void HandleSDES(RtcpParser* rtcp_parser); void HandleSDESChunk(RtcpParser* rtcp_parser); @@ -100,6 +103,7 @@ class RtcpReceiver { void HandleApplicationSpecificCastReceiverLog(RtcpParser* rtcp_parser); void HandleApplicationSpecificCastSenderLog(RtcpParser* rtcp_parser); void HandleApplicationSpecificCastReceiverEventLog( + uint32 frame_rtp_timestamp, RtcpParser* rtcp_parser, RtcpReceiverEventLogMessages* event_log_messages); @@ -112,7 +116,13 @@ class RtcpReceiver { RtcpRttFeedback* const rtt_feedback_; scoped_refptr<CastEnvironment> cast_environment_; - FrameIdWrapHelper ack_frame_id_wrap_helper_; + transport::FrameIdWrapHelper ack_frame_id_wrap_helper_; + + // Maintains a history of receiver events. + size_t receiver_event_history_size_; + typedef std::pair<uint64, uint64> ReceiverEventKey; + base::hash_set<ReceiverEventKey> receiver_event_key_set_; + std::queue<ReceiverEventKey> receiver_event_key_queue_; DISALLOW_COPY_AND_ASSIGN(RtcpReceiver); }; diff --git a/chromium/media/cast/rtcp/rtcp_receiver_unittest.cc b/chromium/media/cast/rtcp/rtcp_receiver_unittest.cc index b5c5d2d3889..51026d1554b 100644 --- a/chromium/media/cast/rtcp/rtcp_receiver_unittest.cc +++ b/chromium/media/cast/rtcp/rtcp_receiver_unittest.cc @@ -10,7 +10,8 @@ #include "media/cast/rtcp/rtcp_receiver.h" #include "media/cast/rtcp/rtcp_utility.h" #include "media/cast/rtcp/test_rtcp_packet_builder.h" -#include "media/cast/test/fake_task_runner.h" +#include "media/cast/test/fake_single_thread_task_runner.h" +#include "media/cast/transport/cast_transport_defines.h" #include "testing/gmock/include/gmock/gmock.h" namespace media { @@ -21,6 +22,7 @@ using testing::_; static const uint32 kSenderSsrc = 0x10203; static const uint32 kSourceSsrc = 0x40506; static const uint32 kUnknownSsrc = 0xDEAD; +static const uint16 kTargetDelayMs = 100; static const std::string kCName("test@10.1.1.1"); namespace { @@ -28,8 +30,8 @@ class SenderFeedbackCastVerification : public RtcpSenderFeedback { public: SenderFeedbackCastVerification() : called_(false) {} - virtual void OnReceivedCastFeedback( - const RtcpCastMessage& cast_feedback) OVERRIDE { + virtual void OnReceivedCastFeedback(const RtcpCastMessage& cast_feedback) + OVERRIDE { EXPECT_EQ(cast_feedback.media_ssrc_, kSenderSsrc); EXPECT_EQ(cast_feedback.ack_frame_id_, kAckFrameId); @@ -38,7 +40,8 @@ class SenderFeedbackCastVerification : public RtcpSenderFeedback { EXPECT_TRUE(frame_it != cast_feedback.missing_frames_and_packets_.end()); EXPECT_EQ(kLostFrameId, frame_it->first); - EXPECT_TRUE(frame_it->second.empty()); + EXPECT_EQ(frame_it->second.size(), 1UL); + EXPECT_EQ(*frame_it->second.begin(), kRtcpCastAllPacketsLost); ++frame_it; EXPECT_TRUE(frame_it != cast_feedback.missing_frames_and_packets_.end()); EXPECT_EQ(kFrameIdWithLostPackets, frame_it->first); @@ -58,6 +61,8 @@ class SenderFeedbackCastVerification : public RtcpSenderFeedback { private: bool called_; + + DISALLOW_COPY_AND_ASSIGN(SenderFeedbackCastVerification); }; class RtcpReceiverCastLogVerification : public RtcpReceiverFeedback { @@ -67,18 +72,18 @@ class RtcpReceiverCastLogVerification : public RtcpReceiverFeedback { called_on_received_receiver_log_(false) {} virtual void OnReceivedSenderReport( - const RtcpSenderInfo& remote_sender_info) OVERRIDE {}; + const transport::RtcpSenderInfo& remote_sender_info) OVERRIDE{}; virtual void OnReceiverReferenceTimeReport( - const RtcpReceiverReferenceTimeReport& remote_time_report) OVERRIDE {}; + const RtcpReceiverReferenceTimeReport& remote_time_report) OVERRIDE{}; - virtual void OnReceivedSendReportRequest() OVERRIDE {}; + virtual void OnReceivedSendReportRequest() OVERRIDE{}; - virtual void OnReceivedReceiverLog( - const RtcpReceiverLogMessage& receiver_log) OVERRIDE { + virtual void OnReceivedReceiverLog(const RtcpReceiverLogMessage& receiver_log) + OVERRIDE { EXPECT_EQ(expected_receiver_log_.size(), receiver_log.size()); RtcpReceiverLogMessage::const_iterator expected_it = - expected_receiver_log_.begin(); + expected_receiver_log_.begin(); RtcpReceiverLogMessage::const_iterator incoming_it = receiver_log.begin(); for (; incoming_it != receiver_log.end(); ++incoming_it) { EXPECT_EQ(expected_it->rtp_timestamp_, incoming_it->rtp_timestamp_); @@ -94,7 +99,7 @@ class RtcpReceiverCastLogVerification : public RtcpReceiverFeedback { EXPECT_EQ(event_expected_it->type, event_incoming_it->type); EXPECT_EQ(event_expected_it->event_timestamp, event_incoming_it->event_timestamp); - if (event_expected_it->type == kPacketReceived) { + if (event_expected_it->type == PACKET_RECEIVED) { EXPECT_EQ(event_expected_it->packet_id, event_incoming_it->packet_id); } else { EXPECT_EQ(event_expected_it->delay_delta, @@ -107,26 +112,6 @@ class RtcpReceiverCastLogVerification : public RtcpReceiverFeedback { called_on_received_receiver_log_ = true; } - virtual void OnReceivedSenderLog( - const RtcpSenderLogMessage& sender_log) OVERRIDE { - EXPECT_EQ(expected_sender_log_.size(), sender_log.size()); - - RtcpSenderLogMessage::const_iterator expected_it = - expected_sender_log_.begin(); - RtcpSenderLogMessage::const_iterator incoming_it = sender_log.begin(); - for (; expected_it != expected_sender_log_.end(); - ++expected_it, ++incoming_it) { - EXPECT_EQ(expected_it->frame_status, incoming_it->frame_status); - EXPECT_EQ(0xffffff & expected_it->rtp_timestamp, - incoming_it->rtp_timestamp); - } - called_on_received_sender_log_ = true; - } - - bool OnReceivedSenderLogCalled() { - return called_on_received_sender_log_; - } - bool OnReceivedReceiverLogCalled() { return called_on_received_receiver_log_ && expected_receiver_log_.empty(); } @@ -135,15 +120,12 @@ class RtcpReceiverCastLogVerification : public RtcpReceiverFeedback { expected_receiver_log_ = receiver_log; } - void SetExpectedSenderLog(const RtcpSenderLogMessage& sender_log) { - expected_sender_log_ = sender_log; - } - private: RtcpReceiverLogMessage expected_receiver_log_; - RtcpSenderLogMessage expected_sender_log_; bool called_on_received_sender_log_; bool called_on_received_receiver_log_; + + DISALLOW_COPY_AND_ASSIGN(RtcpReceiverCastLogVerification); }; } // namespace @@ -151,28 +133,26 @@ class RtcpReceiverCastLogVerification : public RtcpReceiverFeedback { class RtcpReceiverTest : public ::testing::Test { protected: RtcpReceiverTest() - : task_runner_(new test::FakeTaskRunner(&testing_clock_)), - cast_environment_(new CastEnvironment(&testing_clock_, task_runner_, - task_runner_, task_runner_, task_runner_, task_runner_, - GetDefaultCastLoggingConfig())), + : testing_clock_(new base::SimpleTestTickClock()), + task_runner_(new test::FakeSingleThreadTaskRunner(testing_clock_)), + cast_environment_(new CastEnvironment( + scoped_ptr<base::TickClock>(testing_clock_).Pass(), + task_runner_, + task_runner_, + task_runner_)), rtcp_receiver_(new RtcpReceiver(cast_environment_, &mock_sender_feedback_, &mock_receiver_feedback_, &mock_rtt_feedback_, kSourceSsrc)) { - } - - virtual ~RtcpReceiverTest() {} - - virtual void SetUp() OVERRIDE { EXPECT_CALL(mock_receiver_feedback_, OnReceivedSenderReport(_)).Times(0); - EXPECT_CALL(mock_receiver_feedback_, - OnReceiverReferenceTimeReport(_)).Times(0); - EXPECT_CALL(mock_receiver_feedback_, - OnReceivedSendReportRequest()).Times(0); + EXPECT_CALL(mock_receiver_feedback_, OnReceiverReferenceTimeReport(_)) + .Times(0); + EXPECT_CALL(mock_receiver_feedback_, OnReceivedSendReportRequest()) + .Times(0); EXPECT_CALL(mock_sender_feedback_, OnReceivedCastFeedback(_)).Times(0); - EXPECT_CALL(mock_rtt_feedback_, - OnReceivedDelaySinceLastReport(_, _, _)).Times(0); + EXPECT_CALL(mock_rtt_feedback_, OnReceivedDelaySinceLastReport(_, _, _)) + .Times(0); expected_sender_info_.ntp_seconds = kNtpHigh; expected_sender_info_.ntp_fraction = kNtpLow; @@ -193,22 +173,26 @@ class RtcpReceiverTest : public ::testing::Test { expected_receiver_reference_report_.ntp_fraction = kNtpLow; } + virtual ~RtcpReceiverTest() {} + // Injects an RTCP packet into the receiver. void InjectRtcpPacket(const uint8* packet, uint16 length) { RtcpParser rtcp_parser(packet, length); rtcp_receiver_->IncomingRtcpPacket(&rtcp_parser); } - base::SimpleTestTickClock testing_clock_; - scoped_refptr<test::FakeTaskRunner> task_runner_; + base::SimpleTestTickClock* testing_clock_; // Owned by CastEnvironment. + scoped_refptr<test::FakeSingleThreadTaskRunner> task_runner_; scoped_refptr<CastEnvironment> cast_environment_; MockRtcpReceiverFeedback mock_receiver_feedback_; MockRtcpRttFeedback mock_rtt_feedback_; MockRtcpSenderFeedback mock_sender_feedback_; scoped_ptr<RtcpReceiver> rtcp_receiver_; - RtcpSenderInfo expected_sender_info_; - RtcpReportBlock expected_report_block_; + transport::RtcpSenderInfo expected_sender_info_; + transport::RtcpReportBlock expected_report_block_; RtcpReceiverReferenceTimeReport expected_receiver_reference_report_; + + DISALLOW_COPY_AND_ASSIGN(RtcpReceiverTest); }; TEST_F(RtcpReceiverTest, BrokenPacketIsIgnored) { @@ -222,14 +206,14 @@ TEST_F(RtcpReceiverTest, InjectSenderReportPacket) { // Expected to be ignored since the sender ssrc does not match our // remote ssrc. - InjectRtcpPacket(p.Packet(), p.Length()); + InjectRtcpPacket(p.Data(), p.Length()); EXPECT_CALL(mock_receiver_feedback_, OnReceivedSenderReport(expected_sender_info_)).Times(1); rtcp_receiver_->SetRemoteSSRC(kSenderSsrc); // Expected to be pass through since the sender ssrc match our remote ssrc. - InjectRtcpPacket(p.Packet(), p.Length()); + InjectRtcpPacket(p.Data(), p.Length()); } TEST_F(RtcpReceiverTest, InjectReceiveReportPacket) { @@ -239,19 +223,18 @@ TEST_F(RtcpReceiverTest, InjectReceiveReportPacket) { // Expected to be ignored since the source ssrc does not match our // local ssrc. - InjectRtcpPacket(p1.Packet(), p1.Length()); + InjectRtcpPacket(p1.Data(), p1.Length()); EXPECT_CALL(mock_rtt_feedback_, - OnReceivedDelaySinceLastReport(kSourceSsrc, - kLastSr, - kDelayLastSr)).Times(1); + OnReceivedDelaySinceLastReport( + kSourceSsrc, kLastSr, kDelayLastSr)).Times(1); TestRtcpPacketBuilder p2; p2.AddRr(kSenderSsrc, 1); p2.AddRb(kSourceSsrc); // Expected to be pass through since the sender ssrc match our local ssrc. - InjectRtcpPacket(p2.Packet(), p2.Length()); + InjectRtcpPacket(p2.Data(), p2.Length()); } TEST_F(RtcpReceiverTest, InjectSenderReportWithReportBlockPacket) { @@ -263,7 +246,7 @@ TEST_F(RtcpReceiverTest, InjectSenderReportWithReportBlockPacket) { // our remote ssrc. // Report block expected to be ignored since the source ssrc does not match // our local ssrc. - InjectRtcpPacket(p1.Packet(), p1.Length()); + InjectRtcpPacket(p1.Data(), p1.Length()); EXPECT_CALL(mock_receiver_feedback_, OnReceivedSenderReport(expected_sender_info_)).Times(1); @@ -273,13 +256,12 @@ TEST_F(RtcpReceiverTest, InjectSenderReportWithReportBlockPacket) { // remote ssrc. // Report block expected to be ignored since the source ssrc does not match // our local ssrc. - InjectRtcpPacket(p1.Packet(), p1.Length()); + InjectRtcpPacket(p1.Data(), p1.Length()); EXPECT_CALL(mock_receiver_feedback_, OnReceivedSenderReport(_)).Times(0); EXPECT_CALL(mock_rtt_feedback_, - OnReceivedDelaySinceLastReport(kSourceSsrc, - kLastSr, - kDelayLastSr)).Times(1); + OnReceivedDelaySinceLastReport( + kSourceSsrc, kLastSr, kDelayLastSr)).Times(1); rtcp_receiver_->SetRemoteSSRC(0); @@ -291,14 +273,13 @@ TEST_F(RtcpReceiverTest, InjectSenderReportWithReportBlockPacket) { // our remote ssrc. // Receiver report expected to be pass through since the sender ssrc match // our local ssrc. - InjectRtcpPacket(p2.Packet(), p2.Length()); + InjectRtcpPacket(p2.Data(), p2.Length()); EXPECT_CALL(mock_receiver_feedback_, OnReceivedSenderReport(expected_sender_info_)).Times(1); EXPECT_CALL(mock_rtt_feedback_, - OnReceivedDelaySinceLastReport(kSourceSsrc, - kLastSr, - kDelayLastSr)).Times(1); + OnReceivedDelaySinceLastReport( + kSourceSsrc, kLastSr, kDelayLastSr)).Times(1); rtcp_receiver_->SetRemoteSSRC(kSenderSsrc); @@ -306,7 +287,7 @@ TEST_F(RtcpReceiverTest, InjectSenderReportWithReportBlockPacket) { // remote ssrc. // Receiver report expected to be pass through since the sender ssrc match // our local ssrc. - InjectRtcpPacket(p2.Packet(), p2.Length()); + InjectRtcpPacket(p2.Data(), p2.Length()); } TEST_F(RtcpReceiverTest, InjectSenderReportPacketWithDlrr) { @@ -320,20 +301,19 @@ TEST_F(RtcpReceiverTest, InjectSenderReportPacketWithDlrr) { // Expected to be ignored since the source ssrc does not match our // local ssrc. - InjectRtcpPacket(p.Packet(), p.Length()); + InjectRtcpPacket(p.Data(), p.Length()); EXPECT_CALL(mock_receiver_feedback_, OnReceivedSenderReport(expected_sender_info_)).Times(1); EXPECT_CALL(mock_rtt_feedback_, - OnReceivedDelaySinceLastReport(kSenderSsrc, - kLastSr, - kDelayLastSr)).Times(1); + OnReceivedDelaySinceLastReport( + kSenderSsrc, kLastSr, kDelayLastSr)).Times(1); // Enable receiving sender report. rtcp_receiver_->SetRemoteSSRC(kSenderSsrc); // Expected to be pass through since the sender ssrc match our local ssrc. - InjectRtcpPacket(p.Packet(), p.Length()); + InjectRtcpPacket(p.Data(), p.Length()); } TEST_F(RtcpReceiverTest, InjectReceiverReportPacketWithRrtr) { @@ -345,14 +325,14 @@ TEST_F(RtcpReceiverTest, InjectReceiverReportPacketWithRrtr) { // Expected to be ignored since the source ssrc does not match our // local ssrc. - InjectRtcpPacket(p1.Packet(), p1.Length()); + InjectRtcpPacket(p1.Data(), p1.Length()); EXPECT_CALL(mock_rtt_feedback_, - OnReceivedDelaySinceLastReport(kSourceSsrc, - kLastSr, - kDelayLastSr)).Times(1); - EXPECT_CALL(mock_receiver_feedback_, OnReceiverReferenceTimeReport( - expected_receiver_reference_report_)).Times(1); + OnReceivedDelaySinceLastReport( + kSourceSsrc, kLastSr, kDelayLastSr)).Times(1); + EXPECT_CALL(mock_receiver_feedback_, + OnReceiverReferenceTimeReport( + expected_receiver_reference_report_)).Times(1); // Enable receiving reference time report. rtcp_receiver_->SetRemoteSSRC(kSenderSsrc); @@ -364,7 +344,7 @@ TEST_F(RtcpReceiverTest, InjectReceiverReportPacketWithRrtr) { p2.AddXrRrtrBlock(); // Expected to be pass through since the sender ssrc match our local ssrc. - InjectRtcpPacket(p2.Packet(), p2.Length()); + InjectRtcpPacket(p2.Data(), p2.Length()); } TEST_F(RtcpReceiverTest, InjectReceiverReportPacketWithIntraFrameRequest) { @@ -375,12 +355,11 @@ TEST_F(RtcpReceiverTest, InjectReceiverReportPacketWithIntraFrameRequest) { // Expected to be ignored since the source ssrc does not match our // local ssrc. - InjectRtcpPacket(p1.Packet(), p1.Length()); + InjectRtcpPacket(p1.Data(), p1.Length()); EXPECT_CALL(mock_rtt_feedback_, - OnReceivedDelaySinceLastReport(kSourceSsrc, - kLastSr, - kDelayLastSr)).Times(1); + OnReceivedDelaySinceLastReport( + kSourceSsrc, kLastSr, kDelayLastSr)).Times(1); TestRtcpPacketBuilder p2; p2.AddRr(kSenderSsrc, 1); @@ -388,23 +367,22 @@ TEST_F(RtcpReceiverTest, InjectReceiverReportPacketWithIntraFrameRequest) { p2.AddPli(kSenderSsrc, kSourceSsrc); // Expected to be pass through since the sender ssrc match our local ssrc. - InjectRtcpPacket(p2.Packet(), p2.Length()); + InjectRtcpPacket(p2.Data(), p2.Length()); } TEST_F(RtcpReceiverTest, InjectReceiverReportPacketWithCastFeedback) { TestRtcpPacketBuilder p1; p1.AddRr(kSenderSsrc, 1); p1.AddRb(kUnknownSsrc); - p1.AddCast(kSenderSsrc, kUnknownSsrc); + p1.AddCast(kSenderSsrc, kUnknownSsrc, kTargetDelayMs); // Expected to be ignored since the source ssrc does not match our // local ssrc. - InjectRtcpPacket(p1.Packet(), p1.Length()); + InjectRtcpPacket(p1.Data(), p1.Length()); EXPECT_CALL(mock_rtt_feedback_, - OnReceivedDelaySinceLastReport(kSourceSsrc, - kLastSr, - kDelayLastSr)).Times(1); + OnReceivedDelaySinceLastReport( + kSourceSsrc, kLastSr, kDelayLastSr)).Times(1); EXPECT_CALL(mock_sender_feedback_, OnReceivedCastFeedback(_)).Times(1); // Enable receiving the cast feedback. @@ -413,10 +391,10 @@ TEST_F(RtcpReceiverTest, InjectReceiverReportPacketWithCastFeedback) { TestRtcpPacketBuilder p2; p2.AddRr(kSenderSsrc, 1); p2.AddRb(kSourceSsrc); - p2.AddCast(kSenderSsrc, kSourceSsrc); + p2.AddCast(kSenderSsrc, kSourceSsrc, kTargetDelayMs); // Expected to be pass through since the sender ssrc match our local ssrc. - InjectRtcpPacket(p2.Packet(), p2.Length()); + InjectRtcpPacket(p2.Data(), p2.Length()); } TEST_F(RtcpReceiverTest, InjectReceiverReportPacketWithCastVerification) { @@ -428,9 +406,8 @@ TEST_F(RtcpReceiverTest, InjectReceiverReportPacketWithCastVerification) { kSourceSsrc); EXPECT_CALL(mock_rtt_feedback_, - OnReceivedDelaySinceLastReport(kSourceSsrc, - kLastSr, - kDelayLastSr)).Times(1); + OnReceivedDelaySinceLastReport( + kSourceSsrc, kLastSr, kDelayLastSr)).Times(1); // Enable receiving the cast feedback. rtcp_receiver.SetRemoteSSRC(kSenderSsrc); @@ -438,48 +415,15 @@ TEST_F(RtcpReceiverTest, InjectReceiverReportPacketWithCastVerification) { TestRtcpPacketBuilder p; p.AddRr(kSenderSsrc, 1); p.AddRb(kSourceSsrc); - p.AddCast(kSenderSsrc, kSourceSsrc); + p.AddCast(kSenderSsrc, kSourceSsrc, kTargetDelayMs); // Expected to be pass through since the sender ssrc match our local ssrc. - RtcpParser rtcp_parser(p.Packet(), p.Length()); + RtcpParser rtcp_parser(p.Data(), p.Length()); rtcp_receiver.IncomingRtcpPacket(&rtcp_parser); EXPECT_TRUE(sender_feedback_cast_verification.called()); } -TEST_F(RtcpReceiverTest, InjectSenderReportWithCastSenderLogVerification) { - RtcpReceiverCastLogVerification cast_log_verification; - RtcpReceiver rtcp_receiver(cast_environment_, - &mock_sender_feedback_, - &cast_log_verification, - &mock_rtt_feedback_, - kSourceSsrc); - rtcp_receiver.SetRemoteSSRC(kSenderSsrc); - - RtcpSenderLogMessage sender_log; - for (int j = 0; j < 359; ++j) { - RtcpSenderFrameLogMessage sender_frame_log; - sender_frame_log.frame_status = kRtcpSenderFrameStatusSentToNetwork; - sender_frame_log.rtp_timestamp = kRtpTimestamp + j * 90; - sender_log.push_back(sender_frame_log); - } - cast_log_verification.SetExpectedSenderLog(sender_log); - - TestRtcpPacketBuilder p; - p.AddSr(kSenderSsrc, 0); - p.AddSdesCname(kSenderSsrc, kCName); - p.AddSenderLog(kSenderSsrc); - - for (int i = 0; i < 359; ++i) { - p.AddSenderFrameLog(kRtcpSenderFrameStatusSentToNetwork, - kRtpTimestamp + i * 90); - } - RtcpParser rtcp_parser(p.Packet(), p.Length()); - rtcp_receiver.IncomingRtcpPacket(&rtcp_parser); - - EXPECT_TRUE(cast_log_verification.OnReceivedSenderLogCalled()); -} - TEST_F(RtcpReceiverTest, InjectReceiverReportWithReceiverLogVerificationBase) { static const uint32 kTimeBaseMs = 12345678; static const uint32 kTimeDelayMs = 10; @@ -494,21 +438,28 @@ TEST_F(RtcpReceiverTest, InjectReceiverReportWithReceiverLogVerificationBase) { &mock_rtt_feedback_, kSourceSsrc); rtcp_receiver.SetRemoteSSRC(kSenderSsrc); + rtcp_receiver.SetCastReceiverEventHistorySize(100); RtcpReceiverLogMessage receiver_log; RtcpReceiverFrameLogMessage frame_log(kRtpTimestamp); RtcpReceiverEventLogMessage event_log; - event_log.type = kAckSent; + event_log.type = FRAME_ACK_SENT; event_log.event_timestamp = testing_clock.NowTicks(); event_log.delay_delta = base::TimeDelta::FromMilliseconds(kDelayDeltaMs); frame_log.event_log_messages_.push_back(event_log); testing_clock.Advance(base::TimeDelta::FromMilliseconds(kTimeDelayMs)); - event_log.type = kPacketReceived; + event_log.type = PACKET_RECEIVED; event_log.event_timestamp = testing_clock.NowTicks(); event_log.packet_id = kLostPacketId1; frame_log.event_log_messages_.push_back(event_log); + + event_log.type = PACKET_RECEIVED; + event_log.event_timestamp = testing_clock.NowTicks(); + event_log.packet_id = kLostPacketId2; + frame_log.event_log_messages_.push_back(event_log); + receiver_log.push_back(frame_log); cast_log_verification.SetExpectedReceiverLog(receiver_log); @@ -517,15 +468,22 @@ TEST_F(RtcpReceiverTest, InjectReceiverReportWithReceiverLogVerificationBase) { p.AddRr(kSenderSsrc, 1); p.AddRb(kSourceSsrc); p.AddReceiverLog(kSenderSsrc); - p.AddReceiverFrameLog(kRtpTimestamp, 2, kTimeBaseMs); - p.AddReceiverEventLog(kDelayDeltaMs, 1, 0); - p.AddReceiverEventLog(kLostPacketId1, 6, kTimeDelayMs); + p.AddReceiverFrameLog(kRtpTimestamp, 3, kTimeBaseMs); + p.AddReceiverEventLog(kDelayDeltaMs, FRAME_ACK_SENT, 0); + p.AddReceiverEventLog(kLostPacketId1, PACKET_RECEIVED, kTimeDelayMs); + p.AddReceiverEventLog(kLostPacketId2, PACKET_RECEIVED, kTimeDelayMs); + + // Adds duplicated receiver event. + p.AddReceiverFrameLog(kRtpTimestamp, 3, kTimeBaseMs); + p.AddReceiverEventLog(kDelayDeltaMs, FRAME_ACK_SENT, 0); + p.AddReceiverEventLog(kLostPacketId1, PACKET_RECEIVED, kTimeDelayMs); + p.AddReceiverEventLog(kLostPacketId2, PACKET_RECEIVED, kTimeDelayMs); EXPECT_CALL(mock_rtt_feedback_, - OnReceivedDelaySinceLastReport(kSourceSsrc, kLastSr, kDelayLastSr)). - Times(1); + OnReceivedDelaySinceLastReport( + kSourceSsrc, kLastSr, kDelayLastSr)).Times(1); - RtcpParser rtcp_parser(p.Packet(), p.Length()); + RtcpParser rtcp_parser(p.Data(), p.Length()); rtcp_receiver.IncomingRtcpPacket(&rtcp_parser); EXPECT_TRUE(cast_log_verification.OnReceivedReceiverLogCalled()); @@ -551,7 +509,7 @@ TEST_F(RtcpReceiverTest, InjectReceiverReportWithReceiverLogVerificationMulti) { for (int j = 0; j < 100; ++j) { RtcpReceiverFrameLogMessage frame_log(kRtpTimestamp); RtcpReceiverEventLogMessage event_log; - event_log.type = kAckSent; + event_log.type = FRAME_ACK_SENT; event_log.event_timestamp = testing_clock.NowTicks(); event_log.delay_delta = base::TimeDelta::FromMilliseconds(kDelayDeltaMs); frame_log.event_log_messages_.push_back(event_log); @@ -566,21 +524,19 @@ TEST_F(RtcpReceiverTest, InjectReceiverReportWithReceiverLogVerificationMulti) { p.AddRb(kSourceSsrc); p.AddReceiverLog(kSenderSsrc); for (int i = 0; i < 100; ++i) { - p.AddReceiverFrameLog(kRtpTimestamp, 1, kTimeBaseMs + i * kTimeDelayMs); - p.AddReceiverEventLog(kDelayDeltaMs, 1, 0); + p.AddReceiverFrameLog(kRtpTimestamp, 1, kTimeBaseMs + i * kTimeDelayMs); + p.AddReceiverEventLog(kDelayDeltaMs, FRAME_ACK_SENT, 0); } EXPECT_CALL(mock_rtt_feedback_, - OnReceivedDelaySinceLastReport(kSourceSsrc, kLastSr, kDelayLastSr)). - Times(1); + OnReceivedDelaySinceLastReport( + kSourceSsrc, kLastSr, kDelayLastSr)).Times(1); - RtcpParser rtcp_parser(p.Packet(), p.Length()); + RtcpParser rtcp_parser(p.Data(), p.Length()); rtcp_receiver.IncomingRtcpPacket(&rtcp_parser); EXPECT_TRUE(cast_log_verification.OnReceivedReceiverLogCalled()); } - - } // namespace cast } // namespace media diff --git a/chromium/media/cast/rtcp/rtcp_sender.cc b/chromium/media/cast/rtcp/rtcp_sender.cc index b5cf4ce4ced..bf7d30c84c8 100644 --- a/chromium/media/cast/rtcp/rtcp_sender.cc +++ b/chromium/media/cast/rtcp/rtcp_sender.cc @@ -4,263 +4,226 @@ #include "media/cast/rtcp/rtcp_sender.h" +#include <stdint.h> + #include <algorithm> #include <vector> +#include "base/big_endian.h" #include "base/logging.h" #include "media/cast/cast_environment.h" -#include "media/cast/net/pacing/paced_sender.h" +#include "media/cast/rtcp/rtcp_defines.h" #include "media/cast/rtcp/rtcp_utility.h" -#include "net/base/big_endian.h" - -static const size_t kRtcpCastLogHeaderSize = 12; -static const size_t kRtcpSenderFrameLogSize = 4; -static const size_t kRtcpReceiverFrameLogSize = 8; -static const size_t kRtcpReceiverEventLogSize = 4; +#include "media/cast/transport/cast_transport_defines.h" +#include "media/cast/transport/pacing/paced_sender.h" +namespace media { +namespace cast { namespace { + +// Max delta is 4095 milliseconds because we need to be able to encode it in +// 12 bits. +const int64 kMaxWireFormatTimeDeltaMs = INT64_C(0xfff); + uint16 MergeEventTypeAndTimestampForWireFormat( - const media::cast::CastLoggingEvent& event, + const CastLoggingEvent& event, const base::TimeDelta& time_delta) { int64 time_delta_ms = time_delta.InMilliseconds(); - // Max delta is 4096 milliseconds. - DCHECK_GE(GG_INT64_C(0xfff), time_delta_ms); - - uint16 event_type_and_timestamp_delta = - static_cast<uint16>(time_delta_ms & 0xfff); - - uint16 event_type = 0; - switch (event) { - case media::cast::kAckSent: - event_type = 1; - break; - case media::cast::kAudioPlayoutDelay: - event_type = 2; - break; - case media::cast::kAudioFrameDecoded: - event_type = 3; - break; - case media::cast::kVideoFrameDecoded: - event_type = 4; - break; - case media::cast::kVideoRenderDelay: - event_type = 5; - break; - case media::cast::kPacketReceived: - event_type = 6; - break; - default: - NOTREACHED(); - } - DCHECK(!(event_type & 0xfff0)); - return (event_type << 12) + event_type_and_timestamp_delta; -} -bool ScanRtcpReceiverLogMessage( - const media::cast::RtcpReceiverLogMessage& receiver_log_message, - size_t start_size, - size_t* number_of_frames, - size_t* total_number_of_messages_to_send, - size_t* rtcp_log_size) { - if (receiver_log_message.empty()) return false; + DCHECK_GE(time_delta_ms, 0); + DCHECK_LE(time_delta_ms, kMaxWireFormatTimeDeltaMs); - size_t remaining_space = media::cast::kIpPacketSize - start_size; + uint16 time_delta_12_bits = + static_cast<uint16>(time_delta_ms & kMaxWireFormatTimeDeltaMs); - // We must have space for at least one message - DCHECK_GE(remaining_space, kRtcpCastLogHeaderSize + - kRtcpReceiverFrameLogSize + kRtcpReceiverEventLogSize) - << "Not enough buffer space"; + uint16 event_type_4_bits = ConvertEventTypeToWireFormat(event); + DCHECK(event_type_4_bits); + DCHECK(~(event_type_4_bits & 0xfff0)); + return (event_type_4_bits << 12) | time_delta_12_bits; +} - if (remaining_space < kRtcpCastLogHeaderSize + kRtcpReceiverFrameLogSize + - kRtcpReceiverEventLogSize) { - return false; - } - // Account for the RTCP header for an application-defined packet. - remaining_space -= kRtcpCastLogHeaderSize; +bool EventTimestampLessThan(const RtcpReceiverEventLogMessage& lhs, + const RtcpReceiverEventLogMessage& rhs) { + return lhs.event_timestamp < rhs.event_timestamp; +} - media::cast::RtcpReceiverLogMessage::const_iterator frame_it = - receiver_log_message.begin(); - for (; frame_it != receiver_log_message.end(); ++frame_it) { - (*number_of_frames)++; +void AddReceiverLog( + const RtcpReceiverLogMessage& redundancy_receiver_log_message, + RtcpReceiverLogMessage* receiver_log_message, + size_t* remaining_space, + size_t* number_of_frames, + size_t* total_number_of_messages_to_send) { + RtcpReceiverLogMessage::const_iterator it = + redundancy_receiver_log_message.begin(); + while (it != redundancy_receiver_log_message.end() && + *remaining_space >= + kRtcpReceiverFrameLogSize + kRtcpReceiverEventLogSize) { + receiver_log_message->push_front(*it); + size_t num_event_logs = (*remaining_space - kRtcpReceiverFrameLogSize) / + kRtcpReceiverEventLogSize; + RtcpReceiverEventLogMessages& event_log_messages = + receiver_log_message->front().event_log_messages_; + if (num_event_logs < event_log_messages.size()) + event_log_messages.resize(num_event_logs); + + *remaining_space -= kRtcpReceiverFrameLogSize + + event_log_messages.size() * kRtcpReceiverEventLogSize; + ++*number_of_frames; + *total_number_of_messages_to_send += event_log_messages.size(); + ++it; + } +} - remaining_space -= kRtcpReceiverFrameLogSize; +// A class to build a string representing the NACK list in Cast message. +// +// The string will look like "23:3-6 25:1,5-6", meaning packets 3 to 6 in frame +// 23 are being NACK'ed (i.e. they are missing from the receiver's point of +// view) and packets 1, 5 and 6 are missing in frame 25. A frame that is +// completely missing will show as "26:65535". +class NackStringBuilder { + public: + NackStringBuilder() + : frame_count_(0), + packet_count_(0), + last_frame_id_(-1), + last_packet_id_(-1), + contiguous_sequence_(false) {} + ~NackStringBuilder() {} + + bool Empty() const { return frame_count_ == 0; } + + void PushFrame(int frame_id) { + DCHECK_GE(frame_id, 0); + if (frame_count_ > 0) { + if (frame_id == last_frame_id_) { + return; + } + if (contiguous_sequence_) { + stream_ << "-" << last_packet_id_; + } + stream_ << ", "; + } + stream_ << frame_id; + last_frame_id_ = frame_id; + packet_count_ = 0; + contiguous_sequence_ = false; + ++frame_count_; + } - size_t messages_in_frame = frame_it->event_log_messages_.size(); - size_t remaining_space_in_messages = - remaining_space / kRtcpReceiverEventLogSize; - size_t messages_to_send = std::min(messages_in_frame, - remaining_space_in_messages); - if (messages_to_send > media::cast::kRtcpMaxReceiverLogMessages) { - // We can't send more than 256 messages. - remaining_space -= media::cast::kRtcpMaxReceiverLogMessages * - kRtcpReceiverEventLogSize; - *total_number_of_messages_to_send += - media::cast::kRtcpMaxReceiverLogMessages; - break; + void PushPacket(int packet_id) { + DCHECK_GE(last_frame_id_, 0); + DCHECK_GE(packet_id, 0); + if (packet_count_ == 0) { + stream_ << ":" << packet_id; + } else if (packet_id == last_packet_id_ + 1) { + contiguous_sequence_ = true; + } else { + if (contiguous_sequence_) { + stream_ << "-" << last_packet_id_; + contiguous_sequence_ = false; + } + stream_ << "," << packet_id; } - remaining_space -= messages_to_send * kRtcpReceiverEventLogSize; - *total_number_of_messages_to_send += messages_to_send; + ++packet_count_; + last_packet_id_ = packet_id; + } - if (remaining_space < - kRtcpReceiverFrameLogSize + kRtcpReceiverEventLogSize) { - // Make sure that we have room for at least one more message. - break; + std::string GetString() { + if (contiguous_sequence_) { + stream_ << "-" << last_packet_id_; + contiguous_sequence_ = false; } + return stream_.str(); } - *rtcp_log_size = kRtcpCastLogHeaderSize + - *number_of_frames * kRtcpReceiverFrameLogSize + - *total_number_of_messages_to_send * kRtcpReceiverEventLogSize; - DCHECK_GE(media::cast::kIpPacketSize, - start_size + *rtcp_log_size) << "Not enough buffer space"; - VLOG(1) << "number of frames " << *number_of_frames; - VLOG(1) << "total messages to send " << *total_number_of_messages_to_send; - VLOG(1) << "rtcp log size " << *rtcp_log_size; - return true; -} + private: + std::ostringstream stream_; + int frame_count_; + int packet_count_; + int last_frame_id_; + int last_packet_id_; + bool contiguous_sequence_; +}; } // namespace -namespace media { -namespace cast { - +// TODO(mikhal): This is only used by the receiver. Consider renaming. RtcpSender::RtcpSender(scoped_refptr<CastEnvironment> cast_environment, - PacedPacketSender* outgoing_transport, + transport::PacedPacketSender* outgoing_transport, uint32 sending_ssrc, const std::string& c_name) - : ssrc_(sending_ssrc), - c_name_(c_name), - transport_(outgoing_transport), - cast_environment_(cast_environment) { + : ssrc_(sending_ssrc), + c_name_(c_name), + transport_(outgoing_transport), + cast_environment_(cast_environment) { DCHECK_LT(c_name_.length(), kRtcpCnameSize) << "Invalid config"; } RtcpSender::~RtcpSender() {} -void RtcpSender::SendRtcpFromRtpSender(uint32 packet_type_flags, - const RtcpSenderInfo* sender_info, - const RtcpDlrrReportBlock* dlrr, - RtcpSenderLogMessage* sender_log) { - if (packet_type_flags & kRtcpRr || - packet_type_flags & kRtcpPli || - packet_type_flags & kRtcpRrtr || - packet_type_flags & kRtcpCast || - packet_type_flags & kRtcpReceiverLog || - packet_type_flags & kRtcpRpsi || - packet_type_flags & kRtcpRemb || - packet_type_flags & kRtcpNack) { - NOTREACHED() << "Invalid argument"; - } - - std::vector<uint8> packet; - packet.reserve(kIpPacketSize); - if (packet_type_flags & kRtcpSr) { - DCHECK(sender_info) << "Invalid argument"; - BuildSR(*sender_info, NULL, &packet); - BuildSdec(&packet); - } - if (packet_type_flags & kRtcpBye) { - BuildBye(&packet); - } - if (packet_type_flags & kRtcpDlrr) { - DCHECK(dlrr) << "Invalid argument"; - BuildDlrrRb(dlrr, &packet); - } - if (packet_type_flags & kRtcpSenderLog) { - DCHECK(sender_log) << "Invalid argument"; - BuildSenderLog(sender_log, &packet); - } - if (packet.empty()) - return; // Sanity don't send empty packets. - - transport_->SendRtcpPacket(packet); -} - void RtcpSender::SendRtcpFromRtpReceiver( uint32 packet_type_flags, - const RtcpReportBlock* report_block, + const transport::RtcpReportBlock* report_block, const RtcpReceiverReferenceTimeReport* rrtr, const RtcpCastMessage* cast_message, - RtcpReceiverLogMessage* receiver_log) { - if (packet_type_flags & kRtcpSr || - packet_type_flags & kRtcpDlrr || - packet_type_flags & kRtcpSenderLog) { + const ReceiverRtcpEventSubscriber::RtcpEventMultiMap* rtcp_events, + uint16 target_delay_ms) { + if (packet_type_flags & transport::kRtcpSr || + packet_type_flags & transport::kRtcpDlrr || + packet_type_flags & transport::kRtcpSenderLog) { NOTREACHED() << "Invalid argument"; } - if (packet_type_flags & kRtcpPli || - packet_type_flags & kRtcpRpsi || - packet_type_flags & kRtcpRemb || - packet_type_flags & kRtcpNack) { + if (packet_type_flags & transport::kRtcpPli || + packet_type_flags & transport::kRtcpRpsi || + packet_type_flags & transport::kRtcpRemb || + packet_type_flags & transport::kRtcpNack) { // Implement these for webrtc interop. NOTIMPLEMENTED(); } - std::vector<uint8> packet; - packet.reserve(kIpPacketSize); + transport::PacketRef packet(new base::RefCountedData<Packet>); + packet->data.reserve(kMaxIpPacketSize); - if (packet_type_flags & kRtcpRr) { - BuildRR(report_block, &packet); + if (packet_type_flags & transport::kRtcpRr) { + BuildRR(report_block, &packet->data); if (!c_name_.empty()) { - BuildSdec(&packet); + BuildSdec(&packet->data); } } - if (packet_type_flags & kRtcpBye) { - BuildBye(&packet); + if (packet_type_flags & transport::kRtcpBye) { + BuildBye(&packet->data); } - if (packet_type_flags & kRtcpRrtr) { + if (packet_type_flags & transport::kRtcpRrtr) { DCHECK(rrtr) << "Invalid argument"; - BuildRrtr(rrtr, &packet); + BuildRrtr(rrtr, &packet->data); } - if (packet_type_flags & kRtcpCast) { + if (packet_type_flags & transport::kRtcpCast) { DCHECK(cast_message) << "Invalid argument"; - BuildCast(cast_message, &packet); + BuildCast(cast_message, target_delay_ms, &packet->data); } - if (packet_type_flags & kRtcpReceiverLog) { - DCHECK(receiver_log) << "Invalid argument"; - BuildReceiverLog(receiver_log, &packet); + if (packet_type_flags & transport::kRtcpReceiverLog) { + DCHECK(rtcp_events) << "Invalid argument"; + BuildReceiverLog(*rtcp_events, &packet->data); } - if (packet.empty()) return; // Sanity don't send empty packets. - transport_->SendRtcpPacket(packet); -} - -void RtcpSender::BuildSR(const RtcpSenderInfo& sender_info, - const RtcpReportBlock* report_block, - std::vector<uint8>* packet) const { - // Sender report. - size_t start_size = packet->size(); - DCHECK_LT(start_size + 52, kIpPacketSize) << "Not enough buffer space"; - if (start_size + 52 > kIpPacketSize) return; - - uint16 number_of_rows = (report_block) ? 12 : 6; - packet->resize(start_size + 28); - - net::BigEndianWriter big_endian_writer(&((*packet)[start_size]), 28); - big_endian_writer.WriteU8(0x80 + (report_block ? 1 : 0)); - big_endian_writer.WriteU8(kPacketTypeSenderReport); - big_endian_writer.WriteU16(number_of_rows); - big_endian_writer.WriteU32(ssrc_); - big_endian_writer.WriteU32(sender_info.ntp_seconds); - big_endian_writer.WriteU32(sender_info.ntp_fraction); - big_endian_writer.WriteU32(sender_info.rtp_timestamp); - big_endian_writer.WriteU32(sender_info.send_packet_count); - big_endian_writer.WriteU32(static_cast<uint32>(sender_info.send_octet_count)); + if (packet->data.empty()) + return; // Sanity don't send empty packets. - if (report_block) { - AddReportBlocks(*report_block, packet); // Adds 24 bytes. - } + transport_->SendRtcpPacket(ssrc_, packet); } -void RtcpSender::BuildRR(const RtcpReportBlock* report_block, - std::vector<uint8>* packet) const { +void RtcpSender::BuildRR(const transport::RtcpReportBlock* report_block, + Packet* packet) const { size_t start_size = packet->size(); - DCHECK_LT(start_size + 32, kIpPacketSize) << "Not enough buffer space"; - if (start_size + 32 > kIpPacketSize) return; + DCHECK_LT(start_size + 32, kMaxIpPacketSize) << "Not enough buffer space"; + if (start_size + 32 > kMaxIpPacketSize) + return; uint16 number_of_rows = (report_block) ? 7 : 1; packet->resize(start_size + 8); - net::BigEndianWriter big_endian_writer(&((*packet)[start_size]), 8); + base::BigEndianWriter big_endian_writer( + reinterpret_cast<char*>(&((*packet)[start_size])), 8); big_endian_writer.WriteU8(0x80 + (report_block ? 1 : 0)); - big_endian_writer.WriteU8(kPacketTypeReceiverReport); + big_endian_writer.WriteU8(transport::kPacketTypeReceiverReport); big_endian_writer.WriteU16(number_of_rows); big_endian_writer.WriteU32(ssrc_); @@ -269,15 +232,17 @@ void RtcpSender::BuildRR(const RtcpReportBlock* report_block, } } -void RtcpSender::AddReportBlocks(const RtcpReportBlock& report_block, - std::vector<uint8>* packet) const { +void RtcpSender::AddReportBlocks(const transport::RtcpReportBlock& report_block, + Packet* packet) const { size_t start_size = packet->size(); - DCHECK_LT(start_size + 24, kIpPacketSize) << "Not enough buffer space"; - if (start_size + 24 > kIpPacketSize) return; + DCHECK_LT(start_size + 24, kMaxIpPacketSize) << "Not enough buffer space"; + if (start_size + 24 > kMaxIpPacketSize) + return; packet->resize(start_size + 24); - net::BigEndianWriter big_endian_writer(&((*packet)[start_size]), 24); + base::BigEndianWriter big_endian_writer( + reinterpret_cast<char*>(&((*packet)[start_size])), 24); big_endian_writer.WriteU32(report_block.media_ssrc); big_endian_writer.WriteU8(report_block.fraction_lost); big_endian_writer.WriteU8(report_block.cumulative_lost >> 16); @@ -297,30 +262,32 @@ void RtcpSender::AddReportBlocks(const RtcpReportBlock& report_block, big_endian_writer.WriteU32(report_block.delay_since_last_sr); } -void RtcpSender::BuildSdec(std::vector<uint8>* packet) const { +void RtcpSender::BuildSdec(Packet* packet) const { size_t start_size = packet->size(); - DCHECK_LT(start_size + 12 + c_name_.length(), kIpPacketSize) + DCHECK_LT(start_size + 12 + c_name_.length(), kMaxIpPacketSize) << "Not enough buffer space"; - if (start_size + 12 > kIpPacketSize) return; + if (start_size + 12 > kMaxIpPacketSize) + return; // SDES Source Description. packet->resize(start_size + 10); - net::BigEndianWriter big_endian_writer(&((*packet)[start_size]), 10); + base::BigEndianWriter big_endian_writer( + reinterpret_cast<char*>(&((*packet)[start_size])), 10); // We always need to add one SDES CNAME. big_endian_writer.WriteU8(0x80 + 1); - big_endian_writer.WriteU8(kPacketTypeSdes); + big_endian_writer.WriteU8(transport::kPacketTypeSdes); // Handle SDES length later on. uint32 sdes_length_position = static_cast<uint32>(start_size) + 3; big_endian_writer.WriteU16(0); big_endian_writer.WriteU32(ssrc_); // Add our own SSRC. - big_endian_writer.WriteU8(1); // CNAME = 1 + big_endian_writer.WriteU8(1); // CNAME = 1 big_endian_writer.WriteU8(static_cast<uint8>(c_name_.length())); size_t sdes_length = 10 + c_name_.length(); - packet->insert(packet->end(), c_name_.c_str(), - c_name_.c_str() + c_name_.length()); + packet->insert( + packet->end(), c_name_.c_str(), c_name_.c_str() + c_name_.length()); size_t padding = 0; @@ -340,20 +307,21 @@ void RtcpSender::BuildSdec(std::vector<uint8>* packet) const { (*packet)[sdes_length_position] = buffer_length; } -void RtcpSender::BuildPli(uint32 remote_ssrc, - std::vector<uint8>* packet) const { +void RtcpSender::BuildPli(uint32 remote_ssrc, Packet* packet) const { size_t start_size = packet->size(); - DCHECK_LT(start_size + 12, kIpPacketSize) << "Not enough buffer space"; - if (start_size + 12 > kIpPacketSize) return; + DCHECK_LT(start_size + 12, kMaxIpPacketSize) << "Not enough buffer space"; + if (start_size + 12 > kMaxIpPacketSize) + return; packet->resize(start_size + 12); - net::BigEndianWriter big_endian_writer(&((*packet)[start_size]), 12); + base::BigEndianWriter big_endian_writer( + reinterpret_cast<char*>(&((*packet)[start_size])), 12); uint8 FMT = 1; // Picture loss indicator. big_endian_writer.WriteU8(0x80 + FMT); - big_endian_writer.WriteU8(kPacketTypePayloadSpecific); - big_endian_writer.WriteU16(2); // Used fixed length of 2. - big_endian_writer.WriteU32(ssrc_); // Add our own SSRC. + big_endian_writer.WriteU8(transport::kPacketTypePayloadSpecific); + big_endian_writer.WriteU16(2); // Used fixed length of 2. + big_endian_writer.WriteU32(ssrc_); // Add our own SSRC. big_endian_writer.WriteU32(remote_ssrc); // Add the remote SSRC. } @@ -366,18 +334,19 @@ void RtcpSender::BuildPli(uint32 remote_ssrc, | defined per codec ... | Padding (0) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ -void RtcpSender::BuildRpsi(const RtcpRpsiMessage* rpsi, - std::vector<uint8>* packet) const { +void RtcpSender::BuildRpsi(const RtcpRpsiMessage* rpsi, Packet* packet) const { size_t start_size = packet->size(); - DCHECK_LT(start_size + 24, kIpPacketSize) << "Not enough buffer space"; - if (start_size + 24 > kIpPacketSize) return; + DCHECK_LT(start_size + 24, kMaxIpPacketSize) << "Not enough buffer space"; + if (start_size + 24 > kMaxIpPacketSize) + return; packet->resize(start_size + 24); - net::BigEndianWriter big_endian_writer(&((*packet)[start_size]), 24); + base::BigEndianWriter big_endian_writer( + reinterpret_cast<char*>(&((*packet)[start_size])), 24); uint8 FMT = 3; // Reference Picture Selection Indication. big_endian_writer.WriteU8(0x80 + FMT); - big_endian_writer.WriteU8(kPacketTypePayloadSpecific); + big_endian_writer.WriteU8(transport::kPacketTypePayloadSpecific); // Calculate length. uint32 bits_required = 7; @@ -407,8 +376,8 @@ void RtcpSender::BuildRpsi(const RtcpRpsiMessage* rpsi, // Add picture ID. for (int i = bytes_required - 1; i > 0; i--) { - big_endian_writer.WriteU8( - 0x80 | static_cast<uint8>(rpsi->picture_id >> (i * 7))); + big_endian_writer.WriteU8(0x80 | + static_cast<uint8>(rpsi->picture_id >> (i * 7))); } // Add last byte of picture ID. big_endian_writer.WriteU8(static_cast<uint8>(rpsi->picture_id & 0x7f)); @@ -419,38 +388,38 @@ void RtcpSender::BuildRpsi(const RtcpRpsiMessage* rpsi, } } -void RtcpSender::BuildRemb(const RtcpRembMessage* remb, - std::vector<uint8>* packet) const { +void RtcpSender::BuildRemb(const RtcpRembMessage* remb, Packet* packet) const { size_t start_size = packet->size(); size_t remb_size = 20 + 4 * remb->remb_ssrcs.size(); - DCHECK_LT(start_size + remb_size, kIpPacketSize) + DCHECK_LT(start_size + remb_size, kMaxIpPacketSize) << "Not enough buffer space"; - if (start_size + remb_size > kIpPacketSize) return; + if (start_size + remb_size > kMaxIpPacketSize) + return; packet->resize(start_size + remb_size); - net::BigEndianWriter big_endian_writer(&((*packet)[start_size]), remb_size); + base::BigEndianWriter big_endian_writer( + reinterpret_cast<char*>(&((*packet)[start_size])), remb_size); // Add application layer feedback. uint8 FMT = 15; big_endian_writer.WriteU8(0x80 + FMT); - big_endian_writer.WriteU8(kPacketTypePayloadSpecific); + big_endian_writer.WriteU8(transport::kPacketTypePayloadSpecific); big_endian_writer.WriteU8(0); big_endian_writer.WriteU8(static_cast<uint8>(remb->remb_ssrcs.size() + 4)); big_endian_writer.WriteU32(ssrc_); // Add our own SSRC. - big_endian_writer.WriteU32(0); // Remote SSRC must be 0. + big_endian_writer.WriteU32(0); // Remote SSRC must be 0. big_endian_writer.WriteU32(kRemb); big_endian_writer.WriteU8(static_cast<uint8>(remb->remb_ssrcs.size())); // 6 bit exponent and a 18 bit mantissa. uint8 bitrate_exponent; uint32 bitrate_mantissa; - BitrateToRembExponentBitrate(remb->remb_bitrate, - &bitrate_exponent, - &bitrate_mantissa); + BitrateToRembExponentBitrate( + remb->remb_bitrate, &bitrate_exponent, &bitrate_mantissa); - big_endian_writer.WriteU8(static_cast<uint8>((bitrate_exponent << 2) + - ((bitrate_mantissa >> 16) & 0x03))); + big_endian_writer.WriteU8(static_cast<uint8>( + (bitrate_exponent << 2) + ((bitrate_mantissa >> 16) & 0x03))); big_endian_writer.WriteU8(static_cast<uint8>(bitrate_mantissa >> 8)); big_endian_writer.WriteU8(static_cast<uint8>(bitrate_mantissa)); @@ -458,34 +427,33 @@ void RtcpSender::BuildRemb(const RtcpRembMessage* remb, for (; it != remb->remb_ssrcs.end(); ++it) { big_endian_writer.WriteU32(*it); } - cast_environment_->Logging()->InsertGenericEvent(kRembBitrate, - remb->remb_bitrate); } -void RtcpSender::BuildNack(const RtcpNackMessage* nack, - std::vector<uint8>* packet) const { +void RtcpSender::BuildNack(const RtcpNackMessage* nack, Packet* packet) const { size_t start_size = packet->size(); - DCHECK_LT(start_size + 16, kIpPacketSize) << "Not enough buffer space"; - if (start_size + 16 > kIpPacketSize) return; + DCHECK_LT(start_size + 16, kMaxIpPacketSize) << "Not enough buffer space"; + if (start_size + 16 > kMaxIpPacketSize) + return; packet->resize(start_size + 16); - net::BigEndianWriter big_endian_writer(&((*packet)[start_size]), 16); + base::BigEndianWriter big_endian_writer( + reinterpret_cast<char*>(&((*packet)[start_size])), 16); uint8 FMT = 1; big_endian_writer.WriteU8(0x80 + FMT); - big_endian_writer.WriteU8(kPacketTypeGenericRtpFeedback); + big_endian_writer.WriteU8(transport::kPacketTypeGenericRtpFeedback); big_endian_writer.WriteU8(0); size_t nack_size_pos = start_size + 3; big_endian_writer.WriteU8(3); - big_endian_writer.WriteU32(ssrc_); // Add our own SSRC. + big_endian_writer.WriteU32(ssrc_); // Add our own SSRC. big_endian_writer.WriteU32(nack->remote_ssrc); // Add the remote SSRC. // Build NACK bitmasks and write them to the Rtcp message. // The nack list should be sorted and not contain duplicates. size_t number_of_nack_fields = 0; - size_t max_number_of_nack_fields = std::min<size_t>(kRtcpMaxNackFields, - (kIpPacketSize - packet->size()) / 4); + size_t max_number_of_nack_fields = std::min<size_t>( + kRtcpMaxNackFields, (kMaxIpPacketSize - packet->size()) / 4); std::list<uint16>::const_iterator it = nack->nack_list.begin(); while (it != nack->nack_list.end() && @@ -504,11 +472,13 @@ void RtcpSender::BuildNack(const RtcpNackMessage* nack, } // Write the sequence number and the bitmask to the packet. start_size = packet->size(); - DCHECK_LT(start_size + 4, kIpPacketSize) << "Not enough buffer space"; - if (start_size + 4 > kIpPacketSize) return; + DCHECK_LT(start_size + 4, kMaxIpPacketSize) << "Not enough buffer space"; + if (start_size + 4 > kMaxIpPacketSize) + return; packet->resize(start_size + 4); - net::BigEndianWriter big_endian_nack_writer(&((*packet)[start_size]), 4); + base::BigEndianWriter big_endian_nack_writer( + reinterpret_cast<char*>(&((*packet)[start_size])), 4); big_endian_nack_writer.WriteU16(nack_sequence_number); big_endian_nack_writer.WriteU16(bitmask); number_of_nack_fields++; @@ -517,75 +487,41 @@ void RtcpSender::BuildNack(const RtcpNackMessage* nack, (*packet)[nack_size_pos] = static_cast<uint8>(2 + number_of_nack_fields); } -void RtcpSender::BuildBye(std::vector<uint8>* packet) const { +void RtcpSender::BuildBye(Packet* packet) const { size_t start_size = packet->size(); - DCHECK_LT(start_size + 8, kIpPacketSize) << "Not enough buffer space"; - if (start_size + 8 > kIpPacketSize) return; + DCHECK_LT(start_size + 8, kMaxIpPacketSize) << "Not enough buffer space"; + if (start_size + 8 > kMaxIpPacketSize) + return; packet->resize(start_size + 8); - net::BigEndianWriter big_endian_writer(&((*packet)[start_size]), 8); + base::BigEndianWriter big_endian_writer( + reinterpret_cast<char*>(&((*packet)[start_size])), 8); big_endian_writer.WriteU8(0x80 + 1); - big_endian_writer.WriteU8(kPacketTypeBye); - big_endian_writer.WriteU16(1); // Length. - big_endian_writer.WriteU32(ssrc_); // Add our own SSRC. -} - -/* - 0 1 2 3 - 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - |V=2|P|reserved | PT=XR=207 | length | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | SSRC | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | BT=5 | reserved | block length | - +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ - | SSRC_1 (SSRC of first receiver) | sub- - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ block - | last RR (LRR) | 1 - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | delay since last RR (DLRR) | - +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ -*/ -void RtcpSender::BuildDlrrRb(const RtcpDlrrReportBlock* dlrr, - std::vector<uint8>* packet) const { - size_t start_size = packet->size(); - DCHECK_LT(start_size + 24, kIpPacketSize) << "Not enough buffer space"; - if (start_size + 24 > kIpPacketSize) return; - - packet->resize(start_size + 24); - - net::BigEndianWriter big_endian_writer(&((*packet)[start_size]), 24); - big_endian_writer.WriteU8(0x80); - big_endian_writer.WriteU8(kPacketTypeXr); - big_endian_writer.WriteU16(5); // Length. + big_endian_writer.WriteU8(transport::kPacketTypeBye); + big_endian_writer.WriteU16(1); // Length. big_endian_writer.WriteU32(ssrc_); // Add our own SSRC. - big_endian_writer.WriteU8(5); // Add block type. - big_endian_writer.WriteU8(0); // Add reserved. - big_endian_writer.WriteU16(3); // Block length. - big_endian_writer.WriteU32(ssrc_); // Add the media (received RTP) SSRC. - big_endian_writer.WriteU32(dlrr->last_rr); - big_endian_writer.WriteU32(dlrr->delay_since_last_rr); } void RtcpSender::BuildRrtr(const RtcpReceiverReferenceTimeReport* rrtr, - std::vector<uint8>* packet) const { + Packet* packet) const { size_t start_size = packet->size(); - DCHECK_LT(start_size + 20, kIpPacketSize) << "Not enough buffer space"; - if (start_size + 20 > kIpPacketSize) return; + DCHECK_LT(start_size + 20, kMaxIpPacketSize) << "Not enough buffer space"; + if (start_size + 20 > kMaxIpPacketSize) + return; packet->resize(start_size + 20); - net::BigEndianWriter big_endian_writer(&((*packet)[start_size]), 20); + base::BigEndianWriter big_endian_writer( + reinterpret_cast<char*>(&((*packet)[start_size])), 20); big_endian_writer.WriteU8(0x80); - big_endian_writer.WriteU8(kPacketTypeXr); - big_endian_writer.WriteU16(4); // Length. + big_endian_writer.WriteU8(transport::kPacketTypeXr); + big_endian_writer.WriteU16(4); // Length. big_endian_writer.WriteU32(ssrc_); // Add our own SSRC. - big_endian_writer.WriteU8(4); // Add block type. - big_endian_writer.WriteU8(0); // Add reserved. - big_endian_writer.WriteU16(2); // Block length. + big_endian_writer.WriteU8(4); // Add block type. + big_endian_writer.WriteU8(0); // Add reserved. + big_endian_writer.WriteU16(2); // Block length. // Add the media (received RTP) SSRC. big_endian_writer.WriteU32(rrtr->ntp_seconds); @@ -593,47 +529,54 @@ void RtcpSender::BuildRrtr(const RtcpReceiverReferenceTimeReport* rrtr, } void RtcpSender::BuildCast(const RtcpCastMessage* cast, - std::vector<uint8>* packet) const { + uint16 target_delay_ms, + Packet* packet) const { size_t start_size = packet->size(); - DCHECK_LT(start_size + 20, kIpPacketSize) << "Not enough buffer space"; - if (start_size + 20 > kIpPacketSize) return; + DCHECK_LT(start_size + 20, kMaxIpPacketSize) << "Not enough buffer space"; + if (start_size + 20 > kMaxIpPacketSize) + return; packet->resize(start_size + 20); - net::BigEndianWriter big_endian_writer(&((*packet)[start_size]), 20); + base::BigEndianWriter big_endian_writer( + reinterpret_cast<char*>(&((*packet)[start_size])), 20); uint8 FMT = 15; // Application layer feedback. big_endian_writer.WriteU8(0x80 + FMT); - big_endian_writer.WriteU8(kPacketTypePayloadSpecific); + big_endian_writer.WriteU8(transport::kPacketTypePayloadSpecific); big_endian_writer.WriteU8(0); - size_t cast_size_pos = start_size + 3; // Save length position. + size_t cast_size_pos = start_size + 3; // Save length position. big_endian_writer.WriteU8(4); - big_endian_writer.WriteU32(ssrc_); // Add our own SSRC. + big_endian_writer.WriteU32(ssrc_); // Add our own SSRC. big_endian_writer.WriteU32(cast->media_ssrc_); // Remote SSRC. big_endian_writer.WriteU32(kCast); big_endian_writer.WriteU8(static_cast<uint8>(cast->ack_frame_id_)); size_t cast_loss_field_pos = start_size + 17; // Save loss field position. big_endian_writer.WriteU8(0); // Overwritten with number_of_loss_fields. - big_endian_writer.WriteU8(0); // Reserved. - big_endian_writer.WriteU8(0); // Reserved. + big_endian_writer.WriteU16(target_delay_ms); size_t number_of_loss_fields = 0; - size_t max_number_of_loss_fields = std::min<size_t>(kRtcpMaxCastLossFields, - (kIpPacketSize - packet->size()) / 4); + size_t max_number_of_loss_fields = std::min<size_t>( + kRtcpMaxCastLossFields, (kMaxIpPacketSize - packet->size()) / 4); MissingFramesAndPacketsMap::const_iterator frame_it = cast->missing_frames_and_packets_.begin(); + NackStringBuilder nack_string_builder; for (; frame_it != cast->missing_frames_and_packets_.end() && - number_of_loss_fields < max_number_of_loss_fields; ++frame_it) { + number_of_loss_fields < max_number_of_loss_fields; + ++frame_it) { + nack_string_builder.PushFrame(frame_it->first); // Iterate through all frames with missing packets. if (frame_it->second.empty()) { // Special case all packets in a frame is missing. start_size = packet->size(); packet->resize(start_size + 4); - net::BigEndianWriter big_endian_nack_writer(&((*packet)[start_size]), 4); + base::BigEndianWriter big_endian_nack_writer( + reinterpret_cast<char*>(&((*packet)[start_size])), 4); big_endian_nack_writer.WriteU8(static_cast<uint8>(frame_it->first)); big_endian_nack_writer.WriteU16(kRtcpCastAllPacketsLost); big_endian_nack_writer.WriteU8(0); + nack_string_builder.PushPacket(kRtcpCastAllPacketsLost); ++number_of_loss_fields; } else { PacketIdSet::const_iterator packet_it = frame_it->second.begin(); @@ -642,18 +585,20 @@ void RtcpSender::BuildCast(const RtcpCastMessage* cast, start_size = packet->size(); packet->resize(start_size + 4); - net::BigEndianWriter big_endian_nack_writer( - &((*packet)[start_size]), 4); + base::BigEndianWriter big_endian_nack_writer( + reinterpret_cast<char*>(&((*packet)[start_size])), 4); // Write frame and packet id to buffer before calculating bitmask. big_endian_nack_writer.WriteU8(static_cast<uint8>(frame_it->first)); big_endian_nack_writer.WriteU16(packet_id); + nack_string_builder.PushPacket(packet_id); uint8 bitmask = 0; ++packet_it; while (packet_it != frame_it->second.end()) { int shift = static_cast<uint8>(*packet_it - packet_id) - 1; if (shift >= 0 && shift <= 7) { + nack_string_builder.PushPacket(*packet_it); bitmask |= (1 << shift); ++packet_it; } else { @@ -665,80 +610,48 @@ void RtcpSender::BuildCast(const RtcpCastMessage* cast, } } } + VLOG_IF(1, !nack_string_builder.Empty()) + << "SSRC: " << cast->media_ssrc_ + << ", ACK: " << cast->ack_frame_id_ + << ", NACK: " << nack_string_builder.GetString(); DCHECK_LE(number_of_loss_fields, kRtcpMaxCastLossFields); (*packet)[cast_size_pos] = static_cast<uint8>(4 + number_of_loss_fields); (*packet)[cast_loss_field_pos] = static_cast<uint8>(number_of_loss_fields); } -void RtcpSender::BuildSenderLog(RtcpSenderLogMessage* sender_log_message, - std::vector<uint8>* packet) const { - DCHECK(sender_log_message); - DCHECK(packet); - size_t start_size = packet->size(); - size_t remaining_space = kIpPacketSize - start_size; - DCHECK_GE(remaining_space, kRtcpCastLogHeaderSize + kRtcpSenderFrameLogSize) - << "Not enough buffer space"; - if (remaining_space < kRtcpCastLogHeaderSize + kRtcpSenderFrameLogSize) - return; - - size_t space_for_x_messages = - (remaining_space - kRtcpCastLogHeaderSize) / kRtcpSenderFrameLogSize; - size_t number_of_messages = std::min(space_for_x_messages, - sender_log_message->size()); - - size_t log_size = kRtcpCastLogHeaderSize + - number_of_messages * kRtcpSenderFrameLogSize; - packet->resize(start_size + log_size); - - net::BigEndianWriter big_endian_writer(&((*packet)[start_size]), log_size); - big_endian_writer.WriteU8(0x80 + kSenderLogSubtype); - big_endian_writer.WriteU8(kPacketTypeApplicationDefined); - big_endian_writer.WriteU16(static_cast<uint16>(2 + number_of_messages)); - big_endian_writer.WriteU32(ssrc_); // Add our own SSRC. - big_endian_writer.WriteU32(kCast); - - for (; number_of_messages > 0; --number_of_messages) { - DCHECK(!sender_log_message->empty()); - const RtcpSenderFrameLogMessage& message = sender_log_message->front(); - big_endian_writer.WriteU8(static_cast<uint8>(message.frame_status)); - // We send the 24 east significant bits of the RTP timestamp. - big_endian_writer.WriteU8(static_cast<uint8>(message.rtp_timestamp >> 16)); - big_endian_writer.WriteU8(static_cast<uint8>(message.rtp_timestamp >> 8)); - big_endian_writer.WriteU8(static_cast<uint8>(message.rtp_timestamp)); - sender_log_message->pop_front(); - } -} - -void RtcpSender::BuildReceiverLog(RtcpReceiverLogMessage* receiver_log_message, - std::vector<uint8>* packet) const { - DCHECK(receiver_log_message); +void RtcpSender::BuildReceiverLog( + const ReceiverRtcpEventSubscriber::RtcpEventMultiMap& rtcp_events, + Packet* packet) { const size_t packet_start_size = packet->size(); size_t number_of_frames = 0; size_t total_number_of_messages_to_send = 0; size_t rtcp_log_size = 0; - - if (!ScanRtcpReceiverLogMessage(*receiver_log_message, - packet_start_size, - &number_of_frames, - &total_number_of_messages_to_send, - &rtcp_log_size)) { + RtcpReceiverLogMessage receiver_log_message; + + if (!BuildRtcpReceiverLogMessage(rtcp_events, + packet_start_size, + &receiver_log_message, + &number_of_frames, + &total_number_of_messages_to_send, + &rtcp_log_size)) { return; } packet->resize(packet_start_size + rtcp_log_size); - net::BigEndianWriter big_endian_writer(&((*packet)[packet_start_size]), - rtcp_log_size); + base::BigEndianWriter big_endian_writer( + reinterpret_cast<char*>(&((*packet)[packet_start_size])), rtcp_log_size); big_endian_writer.WriteU8(0x80 + kReceiverLogSubtype); - big_endian_writer.WriteU8(kPacketTypeApplicationDefined); - big_endian_writer.WriteU16(static_cast<uint16>(2 + 2 * number_of_frames + - total_number_of_messages_to_send)); + big_endian_writer.WriteU8(transport::kPacketTypeApplicationDefined); + big_endian_writer.WriteU16(static_cast<uint16>( + 2 + 2 * number_of_frames + total_number_of_messages_to_send)); big_endian_writer.WriteU32(ssrc_); // Add our own SSRC. big_endian_writer.WriteU32(kCast); - while (!receiver_log_message->empty() && + while (!receiver_log_message.empty() && total_number_of_messages_to_send > 0) { - RtcpReceiverFrameLogMessage& frame_log_messages = - receiver_log_message->front(); + RtcpReceiverFrameLogMessage& frame_log_messages( + receiver_log_message.front()); + // Add our frame header. big_endian_writer.WriteU32(frame_log_messages.rtp_timestamp_); size_t messages_in_frame = frame_log_messages.event_log_messages_.size(); @@ -765,19 +678,18 @@ void RtcpSender::BuildReceiverLog(RtcpReceiverLogMessage* receiver_log_message, const RtcpReceiverEventLogMessage& event_message = frame_log_messages.event_log_messages_.front(); uint16 event_type_and_timestamp_delta = - MergeEventTypeAndTimestampForWireFormat(event_message.type, - event_message.event_timestamp - event_timestamp_base); + MergeEventTypeAndTimestampForWireFormat( + event_message.type, + event_message.event_timestamp - event_timestamp_base); switch (event_message.type) { - case kAckSent: - case kAudioPlayoutDelay: - case kAudioFrameDecoded: - case kVideoFrameDecoded: - case kVideoRenderDelay: - big_endian_writer.WriteU16(static_cast<uint16>( - event_message.delay_delta.InMilliseconds())); + case FRAME_ACK_SENT: + case FRAME_PLAYOUT: + case FRAME_DECODED: + big_endian_writer.WriteU16( + static_cast<uint16>(event_message.delay_delta.InMilliseconds())); big_endian_writer.WriteU16(event_type_and_timestamp_delta); break; - case kPacketReceived: + case PACKET_RECEIVED: big_endian_writer.WriteU16(event_message.packet_id); big_endian_writer.WriteU16(event_type_and_timestamp_delta); break; @@ -789,10 +701,124 @@ void RtcpSender::BuildReceiverLog(RtcpReceiverLogMessage* receiver_log_message, } if (frame_log_messages.event_log_messages_.empty()) { // We sent all messages on this frame; pop the frame header. - receiver_log_message->pop_front(); + receiver_log_message.pop_front(); } } - DCHECK_EQ(total_number_of_messages_to_send, 0); + DCHECK_EQ(total_number_of_messages_to_send, 0u); +} + +bool RtcpSender::BuildRtcpReceiverLogMessage( + const ReceiverRtcpEventSubscriber::RtcpEventMultiMap& rtcp_events, + size_t start_size, + RtcpReceiverLogMessage* receiver_log_message, + size_t* number_of_frames, + size_t* total_number_of_messages_to_send, + size_t* rtcp_log_size) { + size_t remaining_space = + std::min(kMaxReceiverLogBytes, kMaxIpPacketSize - start_size); + if (remaining_space < kRtcpCastLogHeaderSize + kRtcpReceiverFrameLogSize + + kRtcpReceiverEventLogSize) { + return false; + } + + // We use this to do event timestamp sorting and truncating for events of + // a single frame. + std::vector<RtcpReceiverEventLogMessage> sorted_log_messages; + + // Account for the RTCP header for an application-defined packet. + remaining_space -= kRtcpCastLogHeaderSize; + + ReceiverRtcpEventSubscriber::RtcpEventMultiMap::const_reverse_iterator rit = + rtcp_events.rbegin(); + + while (rit != rtcp_events.rend() && + remaining_space >= + kRtcpReceiverFrameLogSize + kRtcpReceiverEventLogSize) { + const RtpTimestamp rtp_timestamp = rit->first; + RtcpReceiverFrameLogMessage frame_log(rtp_timestamp); + remaining_space -= kRtcpReceiverFrameLogSize; + ++*number_of_frames; + + // Get all events of a single frame. + sorted_log_messages.clear(); + do { + RtcpReceiverEventLogMessage event_log_message; + event_log_message.type = rit->second.type; + event_log_message.event_timestamp = rit->second.timestamp; + event_log_message.delay_delta = rit->second.delay_delta; + event_log_message.packet_id = rit->second.packet_id; + sorted_log_messages.push_back(event_log_message); + ++rit; + } while (rit != rtcp_events.rend() && rit->first == rtp_timestamp); + + std::sort(sorted_log_messages.begin(), + sorted_log_messages.end(), + &EventTimestampLessThan); + + // From |sorted_log_messages|, only take events that are no greater than + // |kMaxWireFormatTimeDeltaMs| seconds away from the latest event. Events + // older than that cannot be encoded over the wire. + std::vector<RtcpReceiverEventLogMessage>::reverse_iterator sorted_rit = + sorted_log_messages.rbegin(); + base::TimeTicks first_event_timestamp = sorted_rit->event_timestamp; + size_t events_in_frame = 0; + while (sorted_rit != sorted_log_messages.rend() && + events_in_frame < kRtcpMaxReceiverLogMessages && + remaining_space >= kRtcpReceiverEventLogSize) { + base::TimeDelta delta(first_event_timestamp - + sorted_rit->event_timestamp); + if (delta.InMilliseconds() > kMaxWireFormatTimeDeltaMs) + break; + frame_log.event_log_messages_.push_front(*sorted_rit); + ++events_in_frame; + ++*total_number_of_messages_to_send; + remaining_space -= kRtcpReceiverEventLogSize; + ++sorted_rit; + } + + receiver_log_message->push_front(frame_log); + } + + rtcp_events_history_.push_front(*receiver_log_message); + + // We don't try to match RTP timestamps of redundancy frame logs with those + // from the newest set (which would save the space of an extra RTP timestamp + // over the wire). Unless the redundancy frame logs are very recent, it's + // unlikely there will be a match anyway. + if (rtcp_events_history_.size() > kFirstRedundancyOffset) { + // Add first redundnacy messages, if enough space remaining + AddReceiverLog(rtcp_events_history_[kFirstRedundancyOffset], + receiver_log_message, + &remaining_space, + number_of_frames, + total_number_of_messages_to_send); + } + + if (rtcp_events_history_.size() > kSecondRedundancyOffset) { + // Add second redundancy messages, if enough space remaining + AddReceiverLog(rtcp_events_history_[kSecondRedundancyOffset], + receiver_log_message, + &remaining_space, + number_of_frames, + total_number_of_messages_to_send); + } + + if (rtcp_events_history_.size() > kReceiveLogMessageHistorySize) { + rtcp_events_history_.pop_back(); + } + + DCHECK_LE(rtcp_events_history_.size(), kReceiveLogMessageHistorySize); + + *rtcp_log_size = + kRtcpCastLogHeaderSize + *number_of_frames * kRtcpReceiverFrameLogSize + + *total_number_of_messages_to_send * kRtcpReceiverEventLogSize; + DCHECK_GE(kMaxIpPacketSize, start_size + *rtcp_log_size) + << "Not enough buffer space."; + + VLOG(3) << "number of frames: " << *number_of_frames; + VLOG(3) << "total messages to send: " << *total_number_of_messages_to_send; + VLOG(3) << "rtcp log size: " << *rtcp_log_size; + return *number_of_frames > 0; } } // namespace cast diff --git a/chromium/media/cast/rtcp/rtcp_sender.h b/chromium/media/cast/rtcp/rtcp_sender.h index e931c693c0f..f09a4fb0e53 100644 --- a/chromium/media/cast/rtcp/rtcp_sender.h +++ b/chromium/media/cast/rtcp/rtcp_sender.h @@ -5,95 +5,96 @@ #ifndef MEDIA_CAST_RTCP_RTCP_SENDER_H_ #define MEDIA_CAST_RTCP_RTCP_SENDER_H_ +#include <deque> #include <list> #include <string> #include "media/cast/cast_config.h" #include "media/cast/cast_defines.h" +#include "media/cast/rtcp/receiver_rtcp_event_subscriber.h" #include "media/cast/rtcp/rtcp.h" #include "media/cast/rtcp/rtcp_defines.h" +#include "media/cast/transport/cast_transport_defines.h" +#include "media/cast/transport/rtcp/rtcp_builder.h" namespace media { namespace cast { +// We limit the size of receiver logs to avoid queuing up packets. +const size_t kMaxReceiverLogBytes = 200; + +// The determines how long to hold receiver log events, based on how +// many "receiver log message reports" ago the events were sent. +const size_t kReceiveLogMessageHistorySize = 20; + +// This determines when to send events the second time. +const size_t kFirstRedundancyOffset = 10; +COMPILE_ASSERT(kFirstRedundancyOffset > 0 && + kFirstRedundancyOffset <= kReceiveLogMessageHistorySize, + redundancy_offset_out_of_range); + +// When to send events the third time. +const size_t kSecondRedundancyOffset = 20; +COMPILE_ASSERT(kSecondRedundancyOffset > + kFirstRedundancyOffset && kSecondRedundancyOffset <= + kReceiveLogMessageHistorySize, + redundancy_offset_out_of_range); + +// TODO(mikhal): Resolve duplication between this and RtcpBuilder. class RtcpSender { public: RtcpSender(scoped_refptr<CastEnvironment> cast_environment, - PacedPacketSender* const paced_packet_sender, + transport::PacedPacketSender* outgoing_transport, uint32 sending_ssrc, const std::string& c_name); virtual ~RtcpSender(); - void SendRtcpFromRtpSender(uint32 packet_type_flags, - const RtcpSenderInfo* sender_info, - const RtcpDlrrReportBlock* dlrr, - RtcpSenderLogMessage* sender_log); - - void SendRtcpFromRtpReceiver(uint32 packet_type_flags, - const RtcpReportBlock* report_block, - const RtcpReceiverReferenceTimeReport* rrtr, - const RtcpCastMessage* cast_message, - RtcpReceiverLogMessage* receiver_log); - - enum RtcpPacketType { - kRtcpSr = 0x0002, - kRtcpRr = 0x0004, - kRtcpBye = 0x0008, - kRtcpPli = 0x0010, - kRtcpNack = 0x0020, - kRtcpFir = 0x0040, - kRtcpSrReq = 0x0200, - kRtcpDlrr = 0x0400, - kRtcpRrtr = 0x0800, - kRtcpRpsi = 0x8000, - kRtcpRemb = 0x10000, - kRtcpCast = 0x20000, - kRtcpSenderLog = 0x40000, - kRtcpReceiverLog = 0x80000, - }; + void SendRtcpFromRtpReceiver( + uint32 packet_type_flags, + const transport::RtcpReportBlock* report_block, + const RtcpReceiverReferenceTimeReport* rrtr, + const RtcpCastMessage* cast_message, + const ReceiverRtcpEventSubscriber::RtcpEventMultiMap* rtcp_events, + uint16 target_delay_ms); private: - void BuildSR(const RtcpSenderInfo& sender_info, - const RtcpReportBlock* report_block, - std::vector<uint8>* packet) const; + void BuildRR(const transport::RtcpReportBlock* report_block, + Packet* packet) const; - void BuildRR(const RtcpReportBlock* report_block, - std::vector<uint8>* packet) const; + void AddReportBlocks(const transport::RtcpReportBlock& report_block, + Packet* packet) const; - void AddReportBlocks(const RtcpReportBlock& report_block, - std::vector<uint8>* packet) const; + void BuildSdec(Packet* packet) const; - void BuildSdec(std::vector<uint8>* packet) const; + void BuildPli(uint32 remote_ssrc, Packet* packet) const; - void BuildPli(uint32 remote_ssrc, - std::vector<uint8>* packet) const; + void BuildRemb(const RtcpRembMessage* remb, Packet* packet) const; - void BuildRemb(const RtcpRembMessage* remb, - std::vector<uint8>* packet) const; + void BuildRpsi(const RtcpRpsiMessage* rpsi, Packet* packet) const; - void BuildRpsi(const RtcpRpsiMessage* rpsi, - std::vector<uint8>* packet) const; + void BuildNack(const RtcpNackMessage* nack, Packet* packet) const; - void BuildNack(const RtcpNackMessage* nack, - std::vector<uint8>* packet) const; - - void BuildBye(std::vector<uint8>* packet) const; - - void BuildDlrrRb(const RtcpDlrrReportBlock* dlrr, - std::vector<uint8>* packet) const; + void BuildBye(Packet* packet) const; void BuildRrtr(const RtcpReceiverReferenceTimeReport* rrtr, - std::vector<uint8>* packet) const; + Packet* packet) const; void BuildCast(const RtcpCastMessage* cast_message, - std::vector<uint8>* packet) const; + uint16 target_delay_ms, + Packet* packet) const; - void BuildSenderLog(RtcpSenderLogMessage* sender_log_message, - std::vector<uint8>* packet) const; + void BuildReceiverLog( + const ReceiverRtcpEventSubscriber::RtcpEventMultiMap& rtcp_events, + Packet* packet); - void BuildReceiverLog(RtcpReceiverLogMessage* receiver_log_message, - std::vector<uint8>* packet) const; + bool BuildRtcpReceiverLogMessage( + const ReceiverRtcpEventSubscriber::RtcpEventMultiMap& rtcp_events, + size_t start_size, + RtcpReceiverLogMessage* receiver_log_message, + size_t* number_of_frames, + size_t* total_number_of_messages_to_send, + size_t* rtcp_log_size); inline void BitrateToRembExponentBitrate(uint32 bitrate, uint8* exponent, @@ -113,9 +114,11 @@ class RtcpSender { const std::string c_name_; // Not owned by this class. - PacedPacketSender* transport_; + transport::PacedPacketSender* const transport_; scoped_refptr<CastEnvironment> cast_environment_; + std::deque<RtcpReceiverLogMessage> rtcp_events_history_; + DISALLOW_COPY_AND_ASSIGN(RtcpSender); }; diff --git a/chromium/media/cast/rtcp/rtcp_sender_unittest.cc b/chromium/media/cast/rtcp/rtcp_sender_unittest.cc index 16e9ee18ffb..0b0c7d3ab89 100644 --- a/chromium/media/cast/rtcp/rtcp_sender_unittest.cc +++ b/chromium/media/cast/rtcp/rtcp_sender_unittest.cc @@ -6,11 +6,13 @@ #include "base/test/simple_test_tick_clock.h" #include "media/cast/cast_defines.h" #include "media/cast/cast_environment.h" -#include "media/cast/net/pacing/paced_sender.h" +#include "media/cast/rtcp/receiver_rtcp_event_subscriber.h" #include "media/cast/rtcp/rtcp_sender.h" #include "media/cast/rtcp/rtcp_utility.h" #include "media/cast/rtcp/test_rtcp_packet_builder.h" -#include "media/cast/test/fake_task_runner.h" +#include "media/cast/test/fake_single_thread_task_runner.h" +#include "media/cast/transport/cast_transport_defines.h" +#include "media/cast/transport/pacing/paced_sender.h" #include "testing/gmock/include/gmock/gmock.h" namespace media { @@ -19,95 +21,99 @@ namespace cast { namespace { static const uint32 kSendingSsrc = 0x12345678; static const uint32 kMediaSsrc = 0x87654321; +static const int16 kDefaultDelay = 100; static const std::string kCName("test@10.1.1.1"); + +transport::RtcpReportBlock GetReportBlock() { + transport::RtcpReportBlock report_block; + // Initialize remote_ssrc to a "clearly illegal" value. + report_block.remote_ssrc = 0xDEAD; + report_block.media_ssrc = kMediaSsrc; // SSRC of the RTP packet sender. + report_block.fraction_lost = kLoss >> 24; + report_block.cumulative_lost = kLoss; // 24 bits valid. + report_block.extended_high_sequence_number = kExtendedMax; + report_block.jitter = kTestJitter; + report_block.last_sr = kLastSr; + report_block.delay_since_last_sr = kDelayLastSr; + return report_block; +} + } // namespace -class TestRtcpTransport : public PacedPacketSender { +class TestRtcpTransport : public transport::PacedPacketSender { public: - TestRtcpTransport() - : expected_packet_length_(0), - packet_count_(0) { - } - - virtual bool SendRtcpPacket(const Packet& packet) OVERRIDE { - EXPECT_EQ(expected_packet_length_, packet.size()); - EXPECT_EQ(0, memcmp(expected_packet_, &(packet[0]), packet.size())); + TestRtcpTransport() : packet_count_(0) {} + + virtual bool SendRtcpPacket(uint32 ssrc, + transport::PacketRef packet) OVERRIDE { + EXPECT_EQ(expected_packet_.size(), packet->data.size()); + EXPECT_EQ(0, memcmp(expected_packet_.data(), + packet->data.data(), + packet->data.size())); packet_count_++; return true; } - virtual bool SendPackets(const PacketList& packets) OVERRIDE { + virtual bool SendPackets( + const transport::SendPacketVector& packets) OVERRIDE { return false; } - - virtual bool ResendPackets(const PacketList& packets) OVERRIDE { + virtual bool ResendPackets( + const transport::SendPacketVector& packets, + base::TimeDelta dedupe_window) OVERRIDE { return false; } - void SetExpectedRtcpPacket(const uint8* rtcp_buffer, size_t length) { - expected_packet_length_ = length; - memcpy(expected_packet_, rtcp_buffer, length); + virtual void CancelSendingPacket( + const transport::PacketKey& packet_key) OVERRIDE { + } + + void SetExpectedRtcpPacket(scoped_ptr<Packet> packet) { + expected_packet_.swap(*packet); } int packet_count() const { return packet_count_; } private: - uint8 expected_packet_[kIpPacketSize]; - size_t expected_packet_length_; + Packet expected_packet_; int packet_count_; + + DISALLOW_COPY_AND_ASSIGN(TestRtcpTransport); }; class RtcpSenderTest : public ::testing::Test { protected: RtcpSenderTest() - : task_runner_(new test::FakeTaskRunner(&testing_clock_)), - cast_environment_(new CastEnvironment(&testing_clock_, task_runner_, - task_runner_, task_runner_, task_runner_, task_runner_, - GetDefaultCastLoggingConfig())), + : testing_clock_(new base::SimpleTestTickClock()), + task_runner_(new test::FakeSingleThreadTaskRunner(testing_clock_)), + cast_environment_(new CastEnvironment( + scoped_ptr<base::TickClock>(testing_clock_).Pass(), + task_runner_, + task_runner_, + task_runner_)), rtcp_sender_(new RtcpSender(cast_environment_, &test_transport_, kSendingSsrc, - kCName)) { - } + kCName)) {} - base::SimpleTestTickClock testing_clock_; + base::SimpleTestTickClock* testing_clock_; // Owned by CastEnvironment. TestRtcpTransport test_transport_; - scoped_refptr<test::FakeTaskRunner> task_runner_; + scoped_refptr<test::FakeSingleThreadTaskRunner> task_runner_; scoped_refptr<CastEnvironment> cast_environment_; scoped_ptr<RtcpSender> rtcp_sender_; -}; - -TEST_F(RtcpSenderTest, RtcpSenderReport) { - RtcpSenderInfo sender_info; - sender_info.ntp_seconds = kNtpHigh; - sender_info.ntp_fraction = kNtpLow; - sender_info.rtp_timestamp = kRtpTimestamp; - sender_info.send_packet_count = kSendPacketCount; - sender_info.send_octet_count = kSendOctetCount; - // Sender report + c_name. - TestRtcpPacketBuilder p; - p.AddSr(kSendingSsrc, 0); - p.AddSdesCname(kSendingSsrc, kCName); - test_transport_.SetExpectedRtcpPacket(p.Packet(), p.Length()); - - rtcp_sender_->SendRtcpFromRtpSender(RtcpSender::kRtcpSr, - &sender_info, - NULL, - NULL); - - EXPECT_EQ(1, test_transport_.packet_count()); -} + DISALLOW_COPY_AND_ASSIGN(RtcpSenderTest); +}; TEST_F(RtcpSenderTest, RtcpReceiverReport) { // Empty receiver report + c_name. TestRtcpPacketBuilder p1; p1.AddRr(kSendingSsrc, 0); p1.AddSdesCname(kSendingSsrc, kCName); - test_transport_.SetExpectedRtcpPacket(p1.Packet(), p1.Length()); + test_transport_.SetExpectedRtcpPacket(p1.GetPacket()); - rtcp_sender_->SendRtcpFromRtpReceiver(RtcpSender::kRtcpRr, - NULL, NULL, NULL, NULL); + rtcp_sender_->SendRtcpFromRtpReceiver( + transport::kRtcpRr, NULL, NULL, NULL, NULL, kDefaultDelay); EXPECT_EQ(1, test_transport_.packet_count()); @@ -116,133 +122,16 @@ TEST_F(RtcpSenderTest, RtcpReceiverReport) { p2.AddRr(kSendingSsrc, 1); p2.AddRb(kMediaSsrc); p2.AddSdesCname(kSendingSsrc, kCName); - test_transport_.SetExpectedRtcpPacket(p2.Packet(), p2.Length()); + test_transport_.SetExpectedRtcpPacket(p2.GetPacket().Pass()); - RtcpReportBlock report_block; - // Initialize remote_ssrc to a "clearly illegal" value. - report_block.remote_ssrc = 0xDEAD; - report_block.media_ssrc = kMediaSsrc; // SSRC of the RTP packet sender. - report_block.fraction_lost = kLoss >> 24; - report_block.cumulative_lost = kLoss; // 24 bits valid. - report_block.extended_high_sequence_number = kExtendedMax; - report_block.jitter = kTestJitter; - report_block.last_sr = kLastSr; - report_block.delay_since_last_sr = kDelayLastSr; + transport::RtcpReportBlock report_block = GetReportBlock(); - rtcp_sender_->SendRtcpFromRtpReceiver(RtcpSender::kRtcpRr, &report_block, - NULL, NULL, NULL); + rtcp_sender_->SendRtcpFromRtpReceiver( + transport::kRtcpRr, &report_block, NULL, NULL, NULL, kDefaultDelay); EXPECT_EQ(2, test_transport_.packet_count()); } -TEST_F(RtcpSenderTest, RtcpSenderReportWithDlrr) { - RtcpSenderInfo sender_info; - sender_info.ntp_seconds = kNtpHigh; - sender_info.ntp_fraction = kNtpLow; - sender_info.rtp_timestamp = kRtpTimestamp; - sender_info.send_packet_count = kSendPacketCount; - sender_info.send_octet_count = kSendOctetCount; - - // Sender report + c_name + dlrr. - TestRtcpPacketBuilder p1; - p1.AddSr(kSendingSsrc, 0); - p1.AddSdesCname(kSendingSsrc, kCName); - p1.AddXrHeader(kSendingSsrc); - p1.AddXrDlrrBlock(kSendingSsrc); - test_transport_.SetExpectedRtcpPacket(p1.Packet(), p1.Length()); - - RtcpDlrrReportBlock dlrr_rb; - dlrr_rb.last_rr = kLastRr; - dlrr_rb.delay_since_last_rr = kDelayLastRr; - - rtcp_sender_->SendRtcpFromRtpSender( - RtcpSender::kRtcpSr | RtcpSender::kRtcpDlrr, - &sender_info, - &dlrr_rb, - NULL); - - EXPECT_EQ(1, test_transport_.packet_count()); -} - -TEST_F(RtcpSenderTest, RtcpSenderReportWithDlrrAndLog) { - RtcpSenderInfo sender_info; - sender_info.ntp_seconds = kNtpHigh; - sender_info.ntp_fraction = kNtpLow; - sender_info.rtp_timestamp = kRtpTimestamp; - sender_info.send_packet_count = kSendPacketCount; - sender_info.send_octet_count = kSendOctetCount; - - // Sender report + c_name + dlrr + sender log. - TestRtcpPacketBuilder p; - p.AddSr(kSendingSsrc, 0); - p.AddSdesCname(kSendingSsrc, kCName); - p.AddXrHeader(kSendingSsrc); - p.AddXrDlrrBlock(kSendingSsrc); - p.AddSenderLog(kSendingSsrc); - p.AddSenderFrameLog(kRtcpSenderFrameStatusSentToNetwork, kRtpTimestamp); - - test_transport_.SetExpectedRtcpPacket(p.Packet(), p.Length()); - - RtcpDlrrReportBlock dlrr_rb; - dlrr_rb.last_rr = kLastRr; - dlrr_rb.delay_since_last_rr = kDelayLastRr; - - RtcpSenderFrameLogMessage sender_frame_log; - sender_frame_log.frame_status = kRtcpSenderFrameStatusSentToNetwork; - sender_frame_log.rtp_timestamp = kRtpTimestamp; - - RtcpSenderLogMessage sender_log; - sender_log.push_back(sender_frame_log); - - rtcp_sender_->SendRtcpFromRtpSender( - RtcpSender::kRtcpSr | RtcpSender::kRtcpDlrr | RtcpSender::kRtcpSenderLog, - &sender_info, - &dlrr_rb, - &sender_log); - - EXPECT_EQ(1, test_transport_.packet_count()); - EXPECT_TRUE(sender_log.empty()); -} - -TEST_F(RtcpSenderTest, RtcpSenderReporWithTooManyLogFrames) { - RtcpSenderInfo sender_info; - sender_info.ntp_seconds = kNtpHigh; - sender_info.ntp_fraction = kNtpLow; - sender_info.rtp_timestamp = kRtpTimestamp; - sender_info.send_packet_count = kSendPacketCount; - sender_info.send_octet_count = kSendOctetCount; - - // Sender report + c_name + sender log. - TestRtcpPacketBuilder p; - p.AddSr(kSendingSsrc, 0); - p.AddSdesCname(kSendingSsrc, kCName); - p.AddSenderLog(kSendingSsrc); - - for (int i = 0; i < 359; ++i) { - p.AddSenderFrameLog(kRtcpSenderFrameStatusSentToNetwork, - kRtpTimestamp + i * 90); - } - test_transport_.SetExpectedRtcpPacket(p.Packet(), p.Length()); - - - RtcpSenderLogMessage sender_log; - for (int j = 0; j < 400; ++j) { - RtcpSenderFrameLogMessage sender_frame_log; - sender_frame_log.frame_status = kRtcpSenderFrameStatusSentToNetwork; - sender_frame_log.rtp_timestamp = kRtpTimestamp + j * 90; - sender_log.push_back(sender_frame_log); - } - - rtcp_sender_->SendRtcpFromRtpSender( - RtcpSender::kRtcpSr | RtcpSender::kRtcpSenderLog, - &sender_info, - NULL, - &sender_log); - - EXPECT_EQ(1, test_transport_.packet_count()); - EXPECT_EQ(41u, sender_log.size()); -} - TEST_F(RtcpSenderTest, RtcpReceiverReportWithRrtr) { // Receiver report with report block + c_name. TestRtcpPacketBuilder p; @@ -251,29 +140,21 @@ TEST_F(RtcpSenderTest, RtcpReceiverReportWithRrtr) { p.AddSdesCname(kSendingSsrc, kCName); p.AddXrHeader(kSendingSsrc); p.AddXrRrtrBlock(); - test_transport_.SetExpectedRtcpPacket(p.Packet(), p.Length()); + test_transport_.SetExpectedRtcpPacket(p.GetPacket().Pass()); - RtcpReportBlock report_block; - // Initialize remote_ssrc to a "clearly illegal" value. - report_block.remote_ssrc = 0xDEAD; - report_block.media_ssrc = kMediaSsrc; // SSRC of the RTP packet sender. - report_block.fraction_lost = kLoss >> 24; - report_block.cumulative_lost = kLoss; // 24 bits valid. - report_block.extended_high_sequence_number = kExtendedMax; - report_block.jitter = kTestJitter; - report_block.last_sr = kLastSr; - report_block.delay_since_last_sr = kDelayLastSr; + transport::RtcpReportBlock report_block = GetReportBlock(); RtcpReceiverReferenceTimeReport rrtr; rrtr.ntp_seconds = kNtpHigh; rrtr.ntp_fraction = kNtpLow; rtcp_sender_->SendRtcpFromRtpReceiver( - RtcpSender::kRtcpRr | RtcpSender::kRtcpRrtr, + transport::kRtcpRr | transport::kRtcpRrtr, &report_block, &rrtr, NULL, - NULL); + NULL, + kDefaultDelay); EXPECT_EQ(1, test_transport_.packet_count()); } @@ -284,19 +165,10 @@ TEST_F(RtcpSenderTest, RtcpReceiverReportWithCast) { p.AddRr(kSendingSsrc, 1); p.AddRb(kMediaSsrc); p.AddSdesCname(kSendingSsrc, kCName); - p.AddCast(kSendingSsrc, kMediaSsrc); - test_transport_.SetExpectedRtcpPacket(p.Packet(), p.Length()); + p.AddCast(kSendingSsrc, kMediaSsrc, kDefaultDelay); + test_transport_.SetExpectedRtcpPacket(p.GetPacket().Pass()); - RtcpReportBlock report_block; - // Initialize remote_ssrc to a "clearly illegal" value. - report_block.remote_ssrc = 0xDEAD; - report_block.media_ssrc = kMediaSsrc; // SSRC of the RTP packet sender. - report_block.fraction_lost = kLoss >> 24; - report_block.cumulative_lost = kLoss; // 24 bits valid. - report_block.extended_high_sequence_number = kExtendedMax; - report_block.jitter = kTestJitter; - report_block.last_sr = kLastSr; - report_block.delay_since_last_sr = kDelayLastSr; + transport::RtcpReportBlock report_block = GetReportBlock(); RtcpCastMessage cast_message(kMediaSsrc); cast_message.ack_frame_id_ = kAckFrameId; @@ -310,11 +182,12 @@ TEST_F(RtcpSenderTest, RtcpReceiverReportWithCast) { missing_packets; rtcp_sender_->SendRtcpFromRtpReceiver( - RtcpSender::kRtcpRr | RtcpSender::kRtcpCast, + transport::kRtcpRr | transport::kRtcpCast, &report_block, NULL, &cast_message, - NULL); + NULL, + kDefaultDelay); EXPECT_EQ(1, test_transport_.packet_count()); } @@ -326,19 +199,10 @@ TEST_F(RtcpSenderTest, RtcpReceiverReportWithRrtraAndCastMessage) { p.AddSdesCname(kSendingSsrc, kCName); p.AddXrHeader(kSendingSsrc); p.AddXrRrtrBlock(); - p.AddCast(kSendingSsrc, kMediaSsrc); - test_transport_.SetExpectedRtcpPacket(p.Packet(), p.Length()); + p.AddCast(kSendingSsrc, kMediaSsrc, kDefaultDelay); + test_transport_.SetExpectedRtcpPacket(p.GetPacket().Pass()); - RtcpReportBlock report_block; - // Initialize remote_ssrc to a "clearly illegal" value. - report_block.remote_ssrc = 0xDEAD; - report_block.media_ssrc = kMediaSsrc; // SSRC of the RTP packet sender. - report_block.fraction_lost = kLoss >> 24; - report_block.cumulative_lost = kLoss; // 24 bits valid. - report_block.extended_high_sequence_number = kExtendedMax; - report_block.jitter = kTestJitter; - report_block.last_sr = kLastSr; - report_block.delay_since_last_sr = kDelayLastSr; + transport::RtcpReportBlock report_block = GetReportBlock(); RtcpReceiverReferenceTimeReport rrtr; rrtr.ntp_seconds = kNtpHigh; @@ -356,11 +220,12 @@ TEST_F(RtcpSenderTest, RtcpReceiverReportWithRrtraAndCastMessage) { missing_packets; rtcp_sender_->SendRtcpFromRtpReceiver( - RtcpSender::kRtcpRr | RtcpSender::kRtcpRrtr | RtcpSender::kRtcpCast, + transport::kRtcpRr | transport::kRtcpRrtr | transport::kRtcpCast, &report_block, &rrtr, &cast_message, - NULL); + NULL, + kDefaultDelay); EXPECT_EQ(1, test_transport_.packet_count()); } @@ -368,7 +233,6 @@ TEST_F(RtcpSenderTest, RtcpReceiverReportWithRrtraAndCastMessage) { TEST_F(RtcpSenderTest, RtcpReceiverReportWithRrtrCastMessageAndLog) { static const uint32 kTimeBaseMs = 12345678; static const uint32 kTimeDelayMs = 10; - static const uint32 kDelayDeltaMs = 123; TestRtcpPacketBuilder p; p.AddRr(kSendingSsrc, 1); @@ -376,19 +240,10 @@ TEST_F(RtcpSenderTest, RtcpReceiverReportWithRrtrCastMessageAndLog) { p.AddSdesCname(kSendingSsrc, kCName); p.AddXrHeader(kSendingSsrc); p.AddXrRrtrBlock(); - p.AddCast(kSendingSsrc, kMediaSsrc); - test_transport_.SetExpectedRtcpPacket(p.Packet(), p.Length()); + p.AddCast(kSendingSsrc, kMediaSsrc, kDefaultDelay); + test_transport_.SetExpectedRtcpPacket(p.GetPacket().Pass()); - RtcpReportBlock report_block; - // Initialize remote_ssrc to a "clearly illegal" value. - report_block.remote_ssrc = 0xDEAD; - report_block.media_ssrc = kMediaSsrc; // SSRC of the RTP packet sender. - report_block.fraction_lost = kLoss >> 24; - report_block.cumulative_lost = kLoss; // 24 bits valid. - report_block.extended_high_sequence_number = kExtendedMax; - report_block.jitter = kTestJitter; - report_block.last_sr = kLastSr; - report_block.delay_since_last_sr = kDelayLastSr; + transport::RtcpReportBlock report_block = GetReportBlock(); RtcpReceiverReferenceTimeReport rrtr; rrtr.ntp_seconds = kNtpHigh; @@ -405,185 +260,296 @@ TEST_F(RtcpSenderTest, RtcpReceiverReportWithRrtrCastMessageAndLog) { cast_message.missing_frames_and_packets_[kFrameIdWithLostPackets] = missing_packets; - // Test empty Log message. - RtcpReceiverLogMessage receiver_log; + ReceiverRtcpEventSubscriber event_subscriber(500, VIDEO_EVENT); + ReceiverRtcpEventSubscriber::RtcpEventMultiMap rtcp_events; - VLOG(0) << " Test empty Log " ; rtcp_sender_->SendRtcpFromRtpReceiver( - RtcpSender::kRtcpRr | RtcpSender::kRtcpRrtr | RtcpSender::kRtcpCast | - RtcpSender::kRtcpReceiverLog, + transport::kRtcpRr | transport::kRtcpRrtr | transport::kRtcpCast | + transport::kRtcpReceiverLog, &report_block, &rrtr, &cast_message, - &receiver_log); - + &rtcp_events, + kDefaultDelay); base::SimpleTestTickClock testing_clock; testing_clock.Advance(base::TimeDelta::FromMilliseconds(kTimeBaseMs)); p.AddReceiverLog(kSendingSsrc); p.AddReceiverFrameLog(kRtpTimestamp, 2, kTimeBaseMs); - p.AddReceiverEventLog(kDelayDeltaMs, 1, 0); - p.AddReceiverEventLog(kLostPacketId1, 6, kTimeDelayMs); - - test_transport_.SetExpectedRtcpPacket(p.Packet(), p.Length()); + p.AddReceiverEventLog(0, FRAME_ACK_SENT, 0); + p.AddReceiverEventLog(kLostPacketId1, PACKET_RECEIVED, kTimeDelayMs); - RtcpReceiverFrameLogMessage frame_log(kRtpTimestamp); - RtcpReceiverEventLogMessage event_log; - - event_log.type = kAckSent; - event_log.event_timestamp = testing_clock.NowTicks(); - event_log.delay_delta = base::TimeDelta::FromMilliseconds(kDelayDeltaMs); - frame_log.event_log_messages_.push_back(event_log); + test_transport_.SetExpectedRtcpPacket(p.GetPacket().Pass()); + FrameEvent frame_event; + frame_event.rtp_timestamp = kRtpTimestamp; + frame_event.type = FRAME_ACK_SENT; + frame_event.media_type = VIDEO_EVENT; + frame_event.timestamp = testing_clock.NowTicks(); + event_subscriber.OnReceiveFrameEvent(frame_event); testing_clock.Advance(base::TimeDelta::FromMilliseconds(kTimeDelayMs)); - event_log.type = kPacketReceived; - event_log.event_timestamp = testing_clock.NowTicks(); - event_log.packet_id = kLostPacketId1; - frame_log.event_log_messages_.push_back(event_log); - receiver_log.push_back(frame_log); + PacketEvent packet_event; + packet_event.rtp_timestamp = kRtpTimestamp; + packet_event.type = PACKET_RECEIVED; + packet_event.media_type = VIDEO_EVENT; + packet_event.timestamp = testing_clock.NowTicks(); + packet_event.packet_id = kLostPacketId1; + event_subscriber.OnReceivePacketEvent(packet_event); + event_subscriber.GetRtcpEventsAndReset(&rtcp_events); + EXPECT_EQ(2u, rtcp_events.size()); - VLOG(0) << " Test Log " ; rtcp_sender_->SendRtcpFromRtpReceiver( - RtcpSender::kRtcpRr | RtcpSender::kRtcpRrtr | RtcpSender::kRtcpCast | - RtcpSender::kRtcpReceiverLog, + transport::kRtcpRr | transport::kRtcpRrtr | transport::kRtcpCast | + transport::kRtcpReceiverLog, &report_block, &rrtr, &cast_message, - &receiver_log); + &rtcp_events, + kDefaultDelay); - EXPECT_TRUE(receiver_log.empty()); EXPECT_EQ(2, test_transport_.packet_count()); } TEST_F(RtcpSenderTest, RtcpReceiverReportWithOversizedFrameLog) { static const uint32 kTimeBaseMs = 12345678; static const uint32 kTimeDelayMs = 10; - static const uint32 kDelayDeltaMs = 123; TestRtcpPacketBuilder p; p.AddRr(kSendingSsrc, 1); p.AddRb(kMediaSsrc); p.AddSdesCname(kSendingSsrc, kCName); - RtcpReportBlock report_block; - // Initialize remote_ssrc to a "clearly illegal" value. - report_block.remote_ssrc = 0xDEAD; - report_block.media_ssrc = kMediaSsrc; // SSRC of the RTP packet sender. - report_block.fraction_lost = kLoss >> 24; - report_block.cumulative_lost = kLoss; // 24 bits valid. - report_block.extended_high_sequence_number = kExtendedMax; - report_block.jitter = kTestJitter; - report_block.last_sr = kLastSr; - report_block.delay_since_last_sr = kDelayLastSr; + transport::RtcpReportBlock report_block = GetReportBlock(); base::SimpleTestTickClock testing_clock; testing_clock.Advance(base::TimeDelta::FromMilliseconds(kTimeBaseMs)); p.AddReceiverLog(kSendingSsrc); - p.AddReceiverFrameLog(kRtpTimestamp, 1, kTimeBaseMs); - p.AddReceiverEventLog(kDelayDeltaMs, 1, 0); - p.AddReceiverFrameLog(kRtpTimestamp + 2345, - kRtcpMaxReceiverLogMessages, kTimeBaseMs); - - for (size_t i = 0; i < kRtcpMaxReceiverLogMessages; ++i) { + int remaining_bytes = kMaxReceiverLogBytes; + remaining_bytes -= kRtcpCastLogHeaderSize; + + remaining_bytes -= kRtcpReceiverFrameLogSize; + int num_events = remaining_bytes / kRtcpReceiverEventLogSize; + EXPECT_LE(num_events, static_cast<int>(kRtcpMaxReceiverLogMessages)); + // Only the last |num_events| events are sent due to receiver log size cap. + p.AddReceiverFrameLog( + kRtpTimestamp + 2345, + num_events, + kTimeBaseMs + (kRtcpMaxReceiverLogMessages - num_events) * kTimeDelayMs); + for (int i = 0; i < num_events; i++) { p.AddReceiverEventLog( - kLostPacketId1, 6, static_cast<uint16>(kTimeDelayMs * i)); + kLostPacketId1, PACKET_RECEIVED, + static_cast<uint16>(kTimeDelayMs * i)); } - test_transport_.SetExpectedRtcpPacket(p.Packet(), p.Length()); - - RtcpReceiverFrameLogMessage frame_1_log(kRtpTimestamp); - RtcpReceiverEventLogMessage event_log; - - event_log.type = kAckSent; - event_log.event_timestamp = testing_clock.NowTicks(); - event_log.delay_delta = base::TimeDelta::FromMilliseconds(kDelayDeltaMs); - frame_1_log.event_log_messages_.push_back(event_log); + test_transport_.SetExpectedRtcpPacket(p.GetPacket().Pass()); - RtcpReceiverLogMessage receiver_log; - receiver_log.push_back(frame_1_log); + ReceiverRtcpEventSubscriber event_subscriber(500, VIDEO_EVENT); + FrameEvent frame_event; + frame_event.rtp_timestamp = kRtpTimestamp; + frame_event.type = FRAME_ACK_SENT; + frame_event.media_type = VIDEO_EVENT; + frame_event.timestamp = testing_clock.NowTicks(); + event_subscriber.OnReceiveFrameEvent(frame_event); - RtcpReceiverFrameLogMessage frame_2_log(kRtpTimestamp + 2345); - - for (int j = 0; j < 300; ++j) { - event_log.type = kPacketReceived; - event_log.event_timestamp = testing_clock.NowTicks(); - event_log.packet_id = kLostPacketId1; - frame_2_log.event_log_messages_.push_back(event_log); + for (size_t i = 0; i < kRtcpMaxReceiverLogMessages; ++i) { + PacketEvent packet_event; + packet_event.rtp_timestamp = kRtpTimestamp + 2345; + packet_event.type = PACKET_RECEIVED; + packet_event.media_type = VIDEO_EVENT; + packet_event.timestamp = testing_clock.NowTicks(); + packet_event.packet_id = kLostPacketId1; + event_subscriber.OnReceivePacketEvent(packet_event); testing_clock.Advance(base::TimeDelta::FromMilliseconds(kTimeDelayMs)); } - receiver_log.push_back(frame_2_log); + + ReceiverRtcpEventSubscriber::RtcpEventMultiMap rtcp_events; + event_subscriber.GetRtcpEventsAndReset(&rtcp_events); rtcp_sender_->SendRtcpFromRtpReceiver( - RtcpSender::kRtcpRr | RtcpSender::kRtcpReceiverLog, + transport::kRtcpRr | transport::kRtcpReceiverLog, &report_block, NULL, NULL, - &receiver_log); + &rtcp_events, + kDefaultDelay); EXPECT_EQ(1, test_transport_.packet_count()); - EXPECT_EQ(1u, receiver_log.size()); - EXPECT_EQ(300u - kRtcpMaxReceiverLogMessages, - receiver_log.front().event_log_messages_.size()); } TEST_F(RtcpSenderTest, RtcpReceiverReportWithTooManyLogFrames) { static const uint32 kTimeBaseMs = 12345678; static const uint32 kTimeDelayMs = 10; - static const uint32 kDelayDeltaMs = 123; TestRtcpPacketBuilder p; p.AddRr(kSendingSsrc, 1); p.AddRb(kMediaSsrc); p.AddSdesCname(kSendingSsrc, kCName); - RtcpReportBlock report_block; - // Initialize remote_ssrc to a "clearly illegal" value. - report_block.remote_ssrc = 0xDEAD; - report_block.media_ssrc = kMediaSsrc; // SSRC of the RTP packet sender. - report_block.fraction_lost = kLoss >> 24; - report_block.cumulative_lost = kLoss; // 24 bits valid. - report_block.extended_high_sequence_number = kExtendedMax; - report_block.jitter = kTestJitter; - report_block.last_sr = kLastSr; - report_block.delay_since_last_sr = kDelayLastSr; + transport::RtcpReportBlock report_block = GetReportBlock(); base::SimpleTestTickClock testing_clock; testing_clock.Advance(base::TimeDelta::FromMilliseconds(kTimeBaseMs)); p.AddReceiverLog(kSendingSsrc); - for (int i = 0; i < 119; ++i) { - p.AddReceiverFrameLog(kRtpTimestamp, 1, kTimeBaseMs + i * kTimeDelayMs); - p.AddReceiverEventLog(kDelayDeltaMs, 1, 0); - } - test_transport_.SetExpectedRtcpPacket(p.Packet(), p.Length()); + int remaining_bytes = kMaxReceiverLogBytes; + remaining_bytes -= kRtcpCastLogHeaderSize; + + int num_events = + remaining_bytes / (kRtcpReceiverFrameLogSize + kRtcpReceiverEventLogSize); - RtcpReceiverLogMessage receiver_log; + // The last |num_events| events are sent due to receiver log size cap. + for (size_t i = kRtcpMaxReceiverLogMessages - num_events; + i < kRtcpMaxReceiverLogMessages; + ++i) { + p.AddReceiverFrameLog(kRtpTimestamp + i, 1, kTimeBaseMs + i * kTimeDelayMs); + p.AddReceiverEventLog(0, FRAME_ACK_SENT, 0); + } + test_transport_.SetExpectedRtcpPacket(p.GetPacket().Pass()); - for (int j = 0; j < 200; ++j) { - RtcpReceiverFrameLogMessage frame_log(kRtpTimestamp); - RtcpReceiverEventLogMessage event_log; + ReceiverRtcpEventSubscriber event_subscriber(500, VIDEO_EVENT); - event_log.type = kAckSent; - event_log.event_timestamp = testing_clock.NowTicks(); - event_log.delay_delta = base::TimeDelta::FromMilliseconds(kDelayDeltaMs); - frame_log.event_log_messages_.push_back(event_log); - receiver_log.push_back(frame_log); + for (size_t i = 0; i < kRtcpMaxReceiverLogMessages; ++i) { + FrameEvent frame_event; + frame_event.rtp_timestamp = kRtpTimestamp + static_cast<int>(i); + frame_event.type = FRAME_ACK_SENT; + frame_event.media_type = VIDEO_EVENT; + frame_event.timestamp = testing_clock.NowTicks(); + event_subscriber.OnReceiveFrameEvent(frame_event); testing_clock.Advance(base::TimeDelta::FromMilliseconds(kTimeDelayMs)); } + + ReceiverRtcpEventSubscriber::RtcpEventMultiMap rtcp_events; + event_subscriber.GetRtcpEventsAndReset(&rtcp_events); + rtcp_sender_->SendRtcpFromRtpReceiver( - RtcpSender::kRtcpRr | RtcpSender::kRtcpReceiverLog, + transport::kRtcpRr | transport::kRtcpReceiverLog, &report_block, NULL, NULL, - &receiver_log); + &rtcp_events, + kDefaultDelay); EXPECT_EQ(1, test_transport_.packet_count()); - EXPECT_EQ(81u, receiver_log.size()); +} + +TEST_F(RtcpSenderTest, RtcpReceiverReportWithOldLogFrames) { + static const uint32 kTimeBaseMs = 12345678; + + TestRtcpPacketBuilder p; + p.AddRr(kSendingSsrc, 1); + p.AddRb(kMediaSsrc); + p.AddSdesCname(kSendingSsrc, kCName); + + transport::RtcpReportBlock report_block = GetReportBlock(); + + base::SimpleTestTickClock testing_clock; + testing_clock.Advance(base::TimeDelta::FromMilliseconds(kTimeBaseMs)); + + p.AddReceiverLog(kSendingSsrc); + + // Log 11 events for a single frame, each |kTimeBetweenEventsMs| apart. + // Only last 10 events will be sent because the first event is more than + // 4095 milliseconds away from latest event. + const int kTimeBetweenEventsMs = 410; + p.AddReceiverFrameLog(kRtpTimestamp, 10, kTimeBaseMs + kTimeBetweenEventsMs); + for (int i = 0; i < 10; ++i) { + p.AddReceiverEventLog(0, FRAME_ACK_SENT, i * kTimeBetweenEventsMs); + } + test_transport_.SetExpectedRtcpPacket(p.GetPacket().Pass()); + + ReceiverRtcpEventSubscriber event_subscriber(500, VIDEO_EVENT); + for (int i = 0; i < 11; ++i) { + FrameEvent frame_event; + frame_event.rtp_timestamp = kRtpTimestamp; + frame_event.type = FRAME_ACK_SENT; + frame_event.media_type = VIDEO_EVENT; + frame_event.timestamp = testing_clock.NowTicks(); + event_subscriber.OnReceiveFrameEvent(frame_event); + testing_clock.Advance( + base::TimeDelta::FromMilliseconds(kTimeBetweenEventsMs)); + } + + ReceiverRtcpEventSubscriber::RtcpEventMultiMap rtcp_events; + event_subscriber.GetRtcpEventsAndReset(&rtcp_events); + + rtcp_sender_->SendRtcpFromRtpReceiver( + transport::kRtcpRr | transport::kRtcpReceiverLog, + &report_block, + NULL, + NULL, + &rtcp_events, + kDefaultDelay); + + EXPECT_EQ(1, test_transport_.packet_count()); +} + +TEST_F(RtcpSenderTest, RtcpReceiverReportRedundancy) { + uint32 time_base_ms = 12345678; + int kTimeBetweenEventsMs = 10; + + transport::RtcpReportBlock report_block = GetReportBlock(); + + base::SimpleTestTickClock testing_clock; + testing_clock.Advance(base::TimeDelta::FromMilliseconds(time_base_ms)); + + ReceiverRtcpEventSubscriber event_subscriber(500, VIDEO_EVENT); + size_t packet_count = kReceiveLogMessageHistorySize + 10; + for (size_t i = 0; i < packet_count; i++) { + TestRtcpPacketBuilder p; + p.AddRr(kSendingSsrc, 1); + p.AddRb(kMediaSsrc); + p.AddSdesCname(kSendingSsrc, kCName); + + p.AddReceiverLog(kSendingSsrc); + + if (i >= kSecondRedundancyOffset) { + p.AddReceiverFrameLog( + kRtpTimestamp, + 1, + time_base_ms - kSecondRedundancyOffset * kTimeBetweenEventsMs); + p.AddReceiverEventLog(0, FRAME_ACK_SENT, 0); + } + if (i >= kFirstRedundancyOffset) { + p.AddReceiverFrameLog( + kRtpTimestamp, + 1, + time_base_ms - kFirstRedundancyOffset * kTimeBetweenEventsMs); + p.AddReceiverEventLog(0, FRAME_ACK_SENT, 0); + } + p.AddReceiverFrameLog(kRtpTimestamp, 1, time_base_ms); + p.AddReceiverEventLog(0, FRAME_ACK_SENT, 0); + + test_transport_.SetExpectedRtcpPacket(p.GetPacket().Pass()); + + FrameEvent frame_event; + frame_event.rtp_timestamp = kRtpTimestamp; + frame_event.type = FRAME_ACK_SENT; + frame_event.media_type = VIDEO_EVENT; + frame_event.timestamp = testing_clock.NowTicks(); + event_subscriber.OnReceiveFrameEvent(frame_event); + + ReceiverRtcpEventSubscriber::RtcpEventMultiMap rtcp_events; + event_subscriber.GetRtcpEventsAndReset(&rtcp_events); + + rtcp_sender_->SendRtcpFromRtpReceiver( + transport::kRtcpRr | transport::kRtcpReceiverLog, + &report_block, + NULL, + NULL, + &rtcp_events, + kDefaultDelay); + + testing_clock.Advance( + base::TimeDelta::FromMilliseconds(kTimeBetweenEventsMs)); + time_base_ms += kTimeBetweenEventsMs; + } + + EXPECT_EQ(static_cast<int>(packet_count), test_transport_.packet_count()); } } // namespace cast diff --git a/chromium/media/cast/rtcp/rtcp_unittest.cc b/chromium/media/cast/rtcp/rtcp_unittest.cc index 535f3c34f83..095e6d24df9 100644 --- a/chromium/media/cast/rtcp/rtcp_unittest.cc +++ b/chromium/media/cast/rtcp/rtcp_unittest.cc @@ -2,15 +2,19 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include <stdint.h> + #include "base/test/simple_test_tick_clock.h" #include "media/cast/cast_defines.h" #include "media/cast/cast_environment.h" -#include "media/cast/net/pacing/paced_sender.h" #include "media/cast/rtcp/mock_rtcp_receiver_feedback.h" #include "media/cast/rtcp/mock_rtcp_sender_feedback.h" #include "media/cast/rtcp/rtcp.h" #include "media/cast/rtcp/test_rtcp_packet_builder.h" -#include "media/cast/test/fake_task_runner.h" +#include "media/cast/test/fake_single_thread_task_runner.h" +#include "media/cast/transport/cast_transport_config.h" +#include "media/cast/transport/cast_transport_sender_impl.h" +#include "media/cast/transport/pacing/paced_sender.h" #include "testing/gmock/include/gmock/gmock.h" namespace media { @@ -22,60 +26,109 @@ static const uint32 kSenderSsrc = 0x10203; static const uint32 kReceiverSsrc = 0x40506; static const std::string kCName("test@10.1.1.1"); static const uint32 kRtcpIntervalMs = 500; -static const int64 kStartMillisecond = GG_INT64_C(12345678900000); static const int64 kAddedDelay = 123; -static const int64 kAddedShortDelay= 100; +static const int64 kAddedShortDelay = 100; -class LocalRtcpTransport : public PacedPacketSender { +class RtcpTestPacketSender : public transport::PacketSender { public: - explicit LocalRtcpTransport(scoped_refptr<CastEnvironment> cast_environment, - base::SimpleTestTickClock* testing_clock) + explicit RtcpTestPacketSender(base::SimpleTestTickClock* testing_clock) : drop_packets_(false), short_delay_(false), + rtcp_receiver_(NULL), testing_clock_(testing_clock) {} + virtual ~RtcpTestPacketSender() {} + // Packet lists imply a RTP packet. + void set_rtcp_receiver(Rtcp* rtcp) { rtcp_receiver_ = rtcp; } + + void set_short_delay() { short_delay_ = true; } + + void set_drop_packets(bool drop_packets) { drop_packets_ = drop_packets; } + + // A singular packet implies a RTCP packet. + virtual bool SendPacket(transport::PacketRef packet, + const base::Closure& cb) OVERRIDE { + if (short_delay_) { + testing_clock_->Advance( + base::TimeDelta::FromMilliseconds(kAddedShortDelay)); + } else { + testing_clock_->Advance(base::TimeDelta::FromMilliseconds(kAddedDelay)); + } + if (drop_packets_) + return true; + + rtcp_receiver_->IncomingRtcpPacket(&packet->data[0], packet->data.size()); + return true; + } - void SetRtcpReceiver(Rtcp* rtcp) { rtcp_ = rtcp; } + private: + bool drop_packets_; + bool short_delay_; + Rtcp* rtcp_receiver_; + base::SimpleTestTickClock* testing_clock_; - void SetShortDelay() { short_delay_ = true; } + DISALLOW_COPY_AND_ASSIGN(RtcpTestPacketSender); +}; - void SetDropPackets(bool drop_packets) { drop_packets_ = drop_packets; } +class LocalRtcpTransport : public transport::PacedPacketSender { + public: + LocalRtcpTransport(scoped_refptr<CastEnvironment> cast_environment, + base::SimpleTestTickClock* testing_clock) + : drop_packets_(false), + short_delay_(false), + testing_clock_(testing_clock) {} + void set_rtcp_receiver(Rtcp* rtcp) { rtcp_ = rtcp; } - virtual bool SendRtcpPacket(const std::vector<uint8>& packet) OVERRIDE { + void set_short_delay() { short_delay_ = true; } + + void set_drop_packets(bool drop_packets) { drop_packets_ = drop_packets; } + + virtual bool SendRtcpPacket(uint32 ssrc, + transport::PacketRef packet) OVERRIDE { if (short_delay_) { testing_clock_->Advance( base::TimeDelta::FromMilliseconds(kAddedShortDelay)); } else { testing_clock_->Advance(base::TimeDelta::FromMilliseconds(kAddedDelay)); } - if (drop_packets_) return true; + if (drop_packets_) + return true; - rtcp_->IncomingRtcpPacket(&(packet[0]), packet.size()); + rtcp_->IncomingRtcpPacket(&packet->data[0], packet->data.size()); return true; } - virtual bool SendPackets(const PacketList& packets) OVERRIDE { + virtual bool SendPackets( + const transport::SendPacketVector& packets) OVERRIDE { return false; } - virtual bool ResendPackets(const PacketList& packets) OVERRIDE { + virtual bool ResendPackets( + const transport::SendPacketVector& packets, + base::TimeDelta dedupe_window) OVERRIDE { return false; } + virtual void CancelSendingPacket( + const transport::PacketKey& packet_key) OVERRIDE { + } + private: bool drop_packets_; bool short_delay_; Rtcp* rtcp_; base::SimpleTestTickClock* testing_clock_; scoped_refptr<CastEnvironment> cast_environment_; + + DISALLOW_COPY_AND_ASSIGN(LocalRtcpTransport); }; class RtcpPeer : public Rtcp { public: RtcpPeer(scoped_refptr<CastEnvironment> cast_environment, RtcpSenderFeedback* sender_feedback, - PacedPacketSender* const paced_packet_sender, - RtpSenderStatistics* rtp_sender_statistics, + transport::CastTransportSender* const transport_sender, + transport::PacedPacketSender* paced_packet_sender, RtpReceiverStatistics* rtp_receiver_statistics, RtcpMode rtcp_mode, const base::TimeDelta& rtcp_interval, @@ -84,95 +137,129 @@ class RtcpPeer : public Rtcp { const std::string& c_name) : Rtcp(cast_environment, sender_feedback, + transport_sender, paced_packet_sender, - rtp_sender_statistics, rtp_receiver_statistics, rtcp_mode, rtcp_interval, local_ssrc, remote_ssrc, - c_name) { - } + c_name, + AUDIO_EVENT) {} - using Rtcp::CheckForWrapAround; + using Rtcp::OnReceivedNtp; using Rtcp::OnReceivedLipSyncInfo; }; class RtcpTest : public ::testing::Test { protected: RtcpTest() - : task_runner_(new test::FakeTaskRunner(&testing_clock_)), - cast_environment_(new CastEnvironment(&testing_clock_, task_runner_, - task_runner_, task_runner_, task_runner_, task_runner_, - GetDefaultCastLoggingConfig())), - transport_(cast_environment_, &testing_clock_) { - testing_clock_.Advance( - base::TimeDelta::FromMilliseconds(kStartMillisecond)); + : testing_clock_(new base::SimpleTestTickClock()), + task_runner_(new test::FakeSingleThreadTaskRunner(testing_clock_)), + cast_environment_(new CastEnvironment( + scoped_ptr<base::TickClock>(testing_clock_).Pass(), + task_runner_, + task_runner_, + task_runner_)), + sender_to_receiver_(testing_clock_), + receiver_to_sender_(cast_environment_, testing_clock_) { + testing_clock_->Advance(base::TimeTicks::Now() - base::TimeTicks()); + net::IPEndPoint dummy_endpoint; + transport_sender_.reset(new transport::CastTransportSenderImpl( + NULL, + testing_clock_, + dummy_endpoint, + base::Bind(&UpdateCastTransportStatus), + transport::BulkRawEventsCallback(), + base::TimeDelta(), + task_runner_, + &sender_to_receiver_)); + transport::CastTransportAudioConfig config; + config.rtp.config.ssrc = kSenderSsrc; + config.rtp.max_outstanding_frames = 1; + transport_sender_->InitializeAudio(config); + EXPECT_CALL(mock_sender_feedback_, OnReceivedCastFeedback(_)).Times(0); } virtual ~RtcpTest() {} - virtual void SetUp() { - EXPECT_CALL(mock_sender_feedback_, OnReceivedCastFeedback(_)).Times(0); + static void UpdateCastTransportStatus(transport::CastTransportStatus status) { + bool result = (status == transport::TRANSPORT_AUDIO_INITIALIZED || + status == transport::TRANSPORT_VIDEO_INITIALIZED); + EXPECT_TRUE(result); + } + + void RunTasks(int during_ms) { + for (int i = 0; i < during_ms; ++i) { + // Call process the timers every 1 ms. + testing_clock_->Advance(base::TimeDelta::FromMilliseconds(1)); + task_runner_->RunTasks(); + } } - base::SimpleTestTickClock testing_clock_; - scoped_refptr<test::FakeTaskRunner> task_runner_; + base::SimpleTestTickClock* testing_clock_; // Owned by CastEnvironment. + scoped_refptr<test::FakeSingleThreadTaskRunner> task_runner_; scoped_refptr<CastEnvironment> cast_environment_; - LocalRtcpTransport transport_; + RtcpTestPacketSender sender_to_receiver_; + scoped_ptr<transport::CastTransportSenderImpl> transport_sender_; + LocalRtcpTransport receiver_to_sender_; MockRtcpSenderFeedback mock_sender_feedback_; + + DISALLOW_COPY_AND_ASSIGN(RtcpTest); }; TEST_F(RtcpTest, TimeToSend) { - base::TimeTicks start_time; - start_time += base::TimeDelta::FromMilliseconds(kStartMillisecond); + const base::TimeTicks start_time = testing_clock_->NowTicks(); Rtcp rtcp(cast_environment_, &mock_sender_feedback_, - &transport_, - NULL, + transport_sender_.get(), + &receiver_to_sender_, NULL, kRtcpCompound, base::TimeDelta::FromMilliseconds(kRtcpIntervalMs), kSenderSsrc, kReceiverSsrc, - kCName); - transport_.SetRtcpReceiver(&rtcp); + kCName, + AUDIO_EVENT); + receiver_to_sender_.set_rtcp_receiver(&rtcp); EXPECT_LE(start_time, rtcp.TimeToSendNextRtcpReport()); - EXPECT_GE(start_time + base::TimeDelta::FromMilliseconds( - kRtcpIntervalMs * 3 / 2), - rtcp.TimeToSendNextRtcpReport()); + EXPECT_GE( + start_time + base::TimeDelta::FromMilliseconds(kRtcpIntervalMs * 3 / 2), + rtcp.TimeToSendNextRtcpReport()); base::TimeDelta delta = rtcp.TimeToSendNextRtcpReport() - start_time; - testing_clock_.Advance(delta); - EXPECT_EQ(testing_clock_.NowTicks(), rtcp.TimeToSendNextRtcpReport()); + testing_clock_->Advance(delta); + EXPECT_EQ(testing_clock_->NowTicks(), rtcp.TimeToSendNextRtcpReport()); } TEST_F(RtcpTest, BasicSenderReport) { Rtcp rtcp(cast_environment_, &mock_sender_feedback_, - &transport_, + transport_sender_.get(), NULL, NULL, kRtcpCompound, base::TimeDelta::FromMilliseconds(kRtcpIntervalMs), kSenderSsrc, kReceiverSsrc, - kCName); - transport_.SetRtcpReceiver(&rtcp); - rtcp.SendRtcpFromRtpSender(NULL); + kCName, + AUDIO_EVENT); + sender_to_receiver_.set_rtcp_receiver(&rtcp); + rtcp.SendRtcpFromRtpSender(base::TimeTicks(), 0); } TEST_F(RtcpTest, BasicReceiverReport) { Rtcp rtcp(cast_environment_, &mock_sender_feedback_, - &transport_, NULL, + &receiver_to_sender_, NULL, kRtcpCompound, base::TimeDelta::FromMilliseconds(kRtcpIntervalMs), kSenderSsrc, kReceiverSsrc, - kCName); - transport_.SetRtcpReceiver(&rtcp); + kCName, + AUDIO_EVENT); + receiver_to_sender_.set_rtcp_receiver(&rtcp); rtcp.SendRtcpFromRtpReceiver(NULL, NULL); } @@ -182,20 +269,20 @@ TEST_F(RtcpTest, BasicCast) { // Media receiver. Rtcp rtcp(cast_environment_, &mock_sender_feedback_, - &transport_, NULL, + &receiver_to_sender_, NULL, kRtcpReducedSize, base::TimeDelta::FromMilliseconds(kRtcpIntervalMs), kSenderSsrc, kSenderSsrc, - kCName); - transport_.SetRtcpReceiver(&rtcp); + kCName, + AUDIO_EVENT); + receiver_to_sender_.set_rtcp_receiver(&rtcp); RtcpCastMessage cast_message(kSenderSsrc); cast_message.ack_frame_id_ = kAckFrameId; PacketIdSet missing_packets; - cast_message.missing_frames_and_packets_[ - kLostFrameId] = missing_packets; + cast_message.missing_frames_and_packets_[kLostFrameId] = missing_packets; missing_packets.insert(kLostPacketId1); missing_packets.insert(kLostPacketId2); @@ -207,334 +294,254 @@ TEST_F(RtcpTest, BasicCast) { TEST_F(RtcpTest, RttReducedSizeRtcp) { // Media receiver. - LocalRtcpTransport receiver_transport(cast_environment_, &testing_clock_); Rtcp rtcp_receiver(cast_environment_, &mock_sender_feedback_, - &receiver_transport, NULL, + &receiver_to_sender_, NULL, kRtcpReducedSize, base::TimeDelta::FromMilliseconds(kRtcpIntervalMs), kReceiverSsrc, kSenderSsrc, - kCName); + kCName, + AUDIO_EVENT); // Media sender. - LocalRtcpTransport sender_transport(cast_environment_, &testing_clock_); Rtcp rtcp_sender(cast_environment_, &mock_sender_feedback_, - &sender_transport, + transport_sender_.get(), NULL, NULL, kRtcpReducedSize, base::TimeDelta::FromMilliseconds(kRtcpIntervalMs), kSenderSsrc, kReceiverSsrc, - kCName); + kCName, + AUDIO_EVENT); - receiver_transport.SetRtcpReceiver(&rtcp_sender); - sender_transport.SetRtcpReceiver(&rtcp_receiver); + sender_to_receiver_.set_rtcp_receiver(&rtcp_receiver); + receiver_to_sender_.set_rtcp_receiver(&rtcp_sender); base::TimeDelta rtt; base::TimeDelta avg_rtt; base::TimeDelta min_rtt; base::TimeDelta max_rtt; - EXPECT_FALSE(rtcp_sender.Rtt(&rtt, &avg_rtt, &min_rtt, &max_rtt)); - EXPECT_FALSE(rtcp_receiver.Rtt(&rtt, &avg_rtt, &min_rtt, &max_rtt)); + EXPECT_FALSE(rtcp_sender.Rtt(&rtt, &avg_rtt, &min_rtt, &max_rtt)); + EXPECT_FALSE(rtcp_receiver.Rtt(&rtt, &avg_rtt, &min_rtt, &max_rtt)); - rtcp_sender.SendRtcpFromRtpSender(NULL); + rtcp_sender.SendRtcpFromRtpSender(testing_clock_->NowTicks(), 1); + RunTasks(33); rtcp_receiver.SendRtcpFromRtpReceiver(NULL, NULL); - EXPECT_TRUE(rtcp_sender.Rtt(&rtt, &avg_rtt, &min_rtt, &max_rtt)); - EXPECT_FALSE(rtcp_receiver.Rtt(&rtt, &avg_rtt, &min_rtt, &max_rtt)); - EXPECT_NEAR(2 * kAddedDelay, rtt.InMilliseconds(), 1); - EXPECT_NEAR(2 * kAddedDelay, avg_rtt.InMilliseconds(), 1); - EXPECT_NEAR(2 * kAddedDelay, min_rtt.InMilliseconds(), 1); - EXPECT_NEAR(2 * kAddedDelay, max_rtt.InMilliseconds(), 1); - rtcp_sender.SendRtcpFromRtpSender(NULL); - EXPECT_TRUE(rtcp_receiver.Rtt(&rtt, &avg_rtt, &min_rtt, &max_rtt)); - - EXPECT_NEAR(2 * kAddedDelay, rtt.InMilliseconds(), 1); - EXPECT_NEAR(2 * kAddedDelay, avg_rtt.InMilliseconds(), 1); - EXPECT_NEAR(2 * kAddedDelay, min_rtt.InMilliseconds(), 1); - EXPECT_NEAR(2 * kAddedDelay, max_rtt.InMilliseconds(), 1); + EXPECT_TRUE(rtcp_sender.Rtt(&rtt, &avg_rtt, &min_rtt, &max_rtt)); + EXPECT_FALSE(rtcp_receiver.Rtt(&rtt, &avg_rtt, &min_rtt, &max_rtt)); + EXPECT_NEAR(2 * kAddedDelay, rtt.InMilliseconds(), 2); + EXPECT_NEAR(2 * kAddedDelay, avg_rtt.InMilliseconds(), 2); + EXPECT_NEAR(2 * kAddedDelay, min_rtt.InMilliseconds(), 2); + EXPECT_NEAR(2 * kAddedDelay, max_rtt.InMilliseconds(), 2); + rtcp_sender.SendRtcpFromRtpSender(testing_clock_->NowTicks(), 2); + RunTasks(33); + EXPECT_TRUE(rtcp_receiver.Rtt(&rtt, &avg_rtt, &min_rtt, &max_rtt)); + + EXPECT_NEAR(2 * kAddedDelay, rtt.InMilliseconds(), 2); + EXPECT_NEAR(2 * kAddedDelay, avg_rtt.InMilliseconds(), 2); + EXPECT_NEAR(2 * kAddedDelay, min_rtt.InMilliseconds(), 2); + EXPECT_NEAR(2 * kAddedDelay, max_rtt.InMilliseconds(), 2); } TEST_F(RtcpTest, Rtt) { // Media receiver. - LocalRtcpTransport receiver_transport(cast_environment_, &testing_clock_); Rtcp rtcp_receiver(cast_environment_, &mock_sender_feedback_, - &receiver_transport, NULL, + &receiver_to_sender_, NULL, kRtcpCompound, base::TimeDelta::FromMilliseconds(kRtcpIntervalMs), kReceiverSsrc, kSenderSsrc, - kCName); + kCName, + AUDIO_EVENT); // Media sender. - LocalRtcpTransport sender_transport(cast_environment_, &testing_clock_); Rtcp rtcp_sender(cast_environment_, &mock_sender_feedback_, - &sender_transport, + transport_sender_.get(), NULL, NULL, kRtcpCompound, base::TimeDelta::FromMilliseconds(kRtcpIntervalMs), kSenderSsrc, kReceiverSsrc, - kCName); + kCName, + AUDIO_EVENT); - receiver_transport.SetRtcpReceiver(&rtcp_sender); - sender_transport.SetRtcpReceiver(&rtcp_receiver); + receiver_to_sender_.set_rtcp_receiver(&rtcp_sender); + sender_to_receiver_.set_rtcp_receiver(&rtcp_receiver); base::TimeDelta rtt; base::TimeDelta avg_rtt; base::TimeDelta min_rtt; base::TimeDelta max_rtt; - EXPECT_FALSE(rtcp_sender.Rtt(&rtt, &avg_rtt, &min_rtt, &max_rtt)); - EXPECT_FALSE(rtcp_receiver.Rtt(&rtt, &avg_rtt, &min_rtt, &max_rtt)); + EXPECT_FALSE(rtcp_sender.Rtt(&rtt, &avg_rtt, &min_rtt, &max_rtt)); + EXPECT_FALSE(rtcp_receiver.Rtt(&rtt, &avg_rtt, &min_rtt, &max_rtt)); - rtcp_sender.SendRtcpFromRtpSender(NULL); + rtcp_sender.SendRtcpFromRtpSender(testing_clock_->NowTicks(), 1); + RunTasks(33); rtcp_receiver.SendRtcpFromRtpReceiver(NULL, NULL); - EXPECT_TRUE(rtcp_sender.Rtt(&rtt, &avg_rtt, &min_rtt, &max_rtt)); - EXPECT_FALSE(rtcp_receiver.Rtt(&rtt, &avg_rtt, &min_rtt, &max_rtt)); - EXPECT_NEAR(2 * kAddedDelay, rtt.InMilliseconds(), 1); - EXPECT_NEAR(2 * kAddedDelay, avg_rtt.InMilliseconds(), 1); - EXPECT_NEAR(2 * kAddedDelay, min_rtt.InMilliseconds(), 1); - EXPECT_NEAR(2 * kAddedDelay, max_rtt.InMilliseconds(), 1); - rtcp_sender.SendRtcpFromRtpSender(NULL); - EXPECT_TRUE(rtcp_receiver.Rtt(&rtt, &avg_rtt, &min_rtt, &max_rtt)); - EXPECT_NEAR(2 * kAddedDelay, rtt.InMilliseconds(), 1); - EXPECT_NEAR(2 * kAddedDelay, avg_rtt.InMilliseconds(), 1); - EXPECT_NEAR(2 * kAddedDelay, min_rtt.InMilliseconds(), 1); - EXPECT_NEAR(2 * kAddedDelay, max_rtt.InMilliseconds(), 1); + EXPECT_TRUE(rtcp_sender.Rtt(&rtt, &avg_rtt, &min_rtt, &max_rtt)); + RunTasks(33); - receiver_transport.SetShortDelay(); - sender_transport.SetShortDelay(); - rtcp_receiver.SendRtcpFromRtpReceiver(NULL, NULL); - EXPECT_TRUE(rtcp_sender.Rtt(&rtt, &avg_rtt, &min_rtt, &max_rtt)); - EXPECT_NEAR(kAddedDelay + kAddedShortDelay, rtt.InMilliseconds(), 1); - EXPECT_NEAR((kAddedShortDelay + 3 * kAddedDelay) / 2, - avg_rtt.InMilliseconds(), - 1); - EXPECT_NEAR(kAddedDelay + kAddedShortDelay, min_rtt.InMilliseconds(), 1); - EXPECT_NEAR(2 * kAddedDelay, max_rtt.InMilliseconds(), 1); + EXPECT_FALSE(rtcp_receiver.Rtt(&rtt, &avg_rtt, &min_rtt, &max_rtt)); + RunTasks(33); - rtcp_sender.SendRtcpFromRtpSender(NULL); - EXPECT_TRUE(rtcp_receiver.Rtt(&rtt, &avg_rtt, &min_rtt, &max_rtt)); + EXPECT_NEAR(2 * kAddedDelay, rtt.InMilliseconds(), 2); + EXPECT_NEAR(2 * kAddedDelay, avg_rtt.InMilliseconds(), 2); + EXPECT_NEAR(2 * kAddedDelay, min_rtt.InMilliseconds(), 2); + EXPECT_NEAR(2 * kAddedDelay, max_rtt.InMilliseconds(), 2); + + rtcp_sender.SendRtcpFromRtpSender(testing_clock_->NowTicks(), 2); + RunTasks(33); + EXPECT_TRUE(rtcp_receiver.Rtt(&rtt, &avg_rtt, &min_rtt, &max_rtt)); + EXPECT_NEAR(2 * kAddedDelay, rtt.InMilliseconds(), 2); + EXPECT_NEAR(2 * kAddedDelay, avg_rtt.InMilliseconds(), 2); + EXPECT_NEAR(2 * kAddedDelay, min_rtt.InMilliseconds(), 2); + EXPECT_NEAR(2 * kAddedDelay, max_rtt.InMilliseconds(), 2); + + receiver_to_sender_.set_short_delay(); + sender_to_receiver_.set_short_delay(); + rtcp_receiver.SendRtcpFromRtpReceiver(NULL, NULL); + EXPECT_TRUE(rtcp_sender.Rtt(&rtt, &avg_rtt, &min_rtt, &max_rtt)); + EXPECT_NEAR(kAddedDelay + kAddedShortDelay, rtt.InMilliseconds(), 2); + EXPECT_NEAR( + (kAddedShortDelay + 3 * kAddedDelay) / 2, avg_rtt.InMilliseconds(), 2); + EXPECT_NEAR(kAddedDelay + kAddedShortDelay, min_rtt.InMilliseconds(), 2); + EXPECT_NEAR(2 * kAddedDelay, max_rtt.InMilliseconds(), 2); + + rtcp_sender.SendRtcpFromRtpSender(testing_clock_->NowTicks(), 3); + RunTasks(33); + EXPECT_TRUE(rtcp_receiver.Rtt(&rtt, &avg_rtt, &min_rtt, &max_rtt)); EXPECT_NEAR(2 * kAddedShortDelay, rtt.InMilliseconds(), 1); EXPECT_NEAR((2 * kAddedShortDelay + 2 * kAddedDelay) / 2, - avg_rtt.InMilliseconds(), - 1); - EXPECT_NEAR(2 * kAddedShortDelay, min_rtt.InMilliseconds(), 1); - EXPECT_NEAR(2 * kAddedDelay, max_rtt.InMilliseconds(), 1); + avg_rtt.InMilliseconds(), + 1); + EXPECT_NEAR(2 * kAddedShortDelay, min_rtt.InMilliseconds(), 2); + EXPECT_NEAR(2 * kAddedDelay, max_rtt.InMilliseconds(), 2); rtcp_receiver.SendRtcpFromRtpReceiver(NULL, NULL); - EXPECT_TRUE(rtcp_sender.Rtt(&rtt, &avg_rtt, &min_rtt, &max_rtt)); - EXPECT_NEAR(2 * kAddedShortDelay, rtt.InMilliseconds(), 1); - EXPECT_NEAR(2 * kAddedShortDelay, min_rtt.InMilliseconds(), 1); - EXPECT_NEAR(2 * kAddedDelay, max_rtt.InMilliseconds(), 1); + EXPECT_TRUE(rtcp_sender.Rtt(&rtt, &avg_rtt, &min_rtt, &max_rtt)); + EXPECT_NEAR(2 * kAddedShortDelay, rtt.InMilliseconds(), 2); + EXPECT_NEAR(2 * kAddedShortDelay, min_rtt.InMilliseconds(), 2); + EXPECT_NEAR(2 * kAddedDelay, max_rtt.InMilliseconds(), 2); rtcp_receiver.SendRtcpFromRtpReceiver(NULL, NULL); - EXPECT_TRUE(rtcp_sender.Rtt(&rtt, &avg_rtt, &min_rtt, &max_rtt)); - EXPECT_NEAR(2 * kAddedShortDelay, rtt.InMilliseconds(), 1); - EXPECT_NEAR(2 * kAddedShortDelay, min_rtt.InMilliseconds(), 1); - EXPECT_NEAR(2 * kAddedDelay, max_rtt.InMilliseconds(), 1); + EXPECT_TRUE(rtcp_sender.Rtt(&rtt, &avg_rtt, &min_rtt, &max_rtt)); + EXPECT_NEAR(2 * kAddedShortDelay, rtt.InMilliseconds(), 2); + EXPECT_NEAR(2 * kAddedShortDelay, min_rtt.InMilliseconds(), 2); + EXPECT_NEAR(2 * kAddedDelay, max_rtt.InMilliseconds(), 2); } TEST_F(RtcpTest, RttWithPacketLoss) { // Media receiver. - LocalRtcpTransport receiver_transport(cast_environment_, &testing_clock_); Rtcp rtcp_receiver(cast_environment_, &mock_sender_feedback_, - &receiver_transport, NULL, + &receiver_to_sender_, NULL, kRtcpReducedSize, base::TimeDelta::FromMilliseconds(kRtcpIntervalMs), - kSenderSsrc, kReceiverSsrc, - kCName); + kSenderSsrc, + kCName, + AUDIO_EVENT); // Media sender. - LocalRtcpTransport sender_transport(cast_environment_, &testing_clock_); Rtcp rtcp_sender(cast_environment_, &mock_sender_feedback_, - &sender_transport, + transport_sender_.get(), NULL, NULL, kRtcpReducedSize, base::TimeDelta::FromMilliseconds(kRtcpIntervalMs), - kReceiverSsrc, kSenderSsrc, - kCName); + kReceiverSsrc, + kCName, + AUDIO_EVENT); - receiver_transport.SetRtcpReceiver(&rtcp_sender); - sender_transport.SetRtcpReceiver(&rtcp_receiver); + receiver_to_sender_.set_rtcp_receiver(&rtcp_sender); + sender_to_receiver_.set_rtcp_receiver(&rtcp_receiver); rtcp_receiver.SendRtcpFromRtpReceiver(NULL, NULL); - rtcp_sender.SendRtcpFromRtpSender(NULL); + rtcp_sender.SendRtcpFromRtpSender(testing_clock_->NowTicks(), 0); + RunTasks(33); base::TimeDelta rtt; base::TimeDelta avg_rtt; base::TimeDelta min_rtt; base::TimeDelta max_rtt; - EXPECT_FALSE(rtcp_sender.Rtt(&rtt, &avg_rtt, &min_rtt, &max_rtt)); - EXPECT_TRUE(rtcp_receiver.Rtt(&rtt, &avg_rtt, &min_rtt, &max_rtt)); + EXPECT_FALSE(rtcp_sender.Rtt(&rtt, &avg_rtt, &min_rtt, &max_rtt)); + EXPECT_TRUE(rtcp_receiver.Rtt(&rtt, &avg_rtt, &min_rtt, &max_rtt)); EXPECT_NEAR(2 * kAddedDelay, rtt.InMilliseconds(), 1); EXPECT_NEAR(2 * kAddedDelay, avg_rtt.InMilliseconds(), 1); EXPECT_NEAR(2 * kAddedDelay, min_rtt.InMilliseconds(), 1); EXPECT_NEAR(2 * kAddedDelay, max_rtt.InMilliseconds(), 1); - receiver_transport.SetShortDelay(); - sender_transport.SetShortDelay(); - receiver_transport.SetDropPackets(true); + receiver_to_sender_.set_short_delay(); + sender_to_receiver_.set_short_delay(); + receiver_to_sender_.set_drop_packets(true); rtcp_receiver.SendRtcpFromRtpReceiver(NULL, NULL); - rtcp_sender.SendRtcpFromRtpSender(NULL); + rtcp_sender.SendRtcpFromRtpSender(testing_clock_->NowTicks(), 1); + RunTasks(33); - EXPECT_TRUE(rtcp_receiver.Rtt(&rtt, &avg_rtt, &min_rtt, &max_rtt)); - EXPECT_NEAR(kAddedDelay + kAddedShortDelay, rtt.InMilliseconds(), 1); + EXPECT_TRUE(rtcp_receiver.Rtt(&rtt, &avg_rtt, &min_rtt, &max_rtt)); + EXPECT_NEAR(kAddedDelay + kAddedShortDelay, rtt.InMilliseconds(), 2); } TEST_F(RtcpTest, NtpAndTime) { - const int64 kSecondsbetweenYear1900and2010 = GG_INT64_C(40176 * 24 * 60 * 60); - const int64 kSecondsbetweenYear1900and2030 = GG_INT64_C(47481 * 24 * 60 * 60); + const int64 kSecondsbetweenYear1900and2010 = INT64_C(40176 * 24 * 60 * 60); + const int64 kSecondsbetweenYear1900and2030 = INT64_C(47481 * 24 * 60 * 60); uint32 ntp_seconds_1 = 0; - uint32 ntp_fractions_1 = 0; + uint32 ntp_fraction_1 = 0; base::TimeTicks input_time = base::TimeTicks::Now(); - ConvertTimeTicksToNtp(input_time, &ntp_seconds_1, &ntp_fractions_1); + ConvertTimeTicksToNtp(input_time, &ntp_seconds_1, &ntp_fraction_1); // Verify absolute value. EXPECT_GT(ntp_seconds_1, kSecondsbetweenYear1900and2010); EXPECT_LT(ntp_seconds_1, kSecondsbetweenYear1900and2030); - base::TimeTicks out_1 = ConvertNtpToTimeTicks(ntp_seconds_1, ntp_fractions_1); + base::TimeTicks out_1 = ConvertNtpToTimeTicks(ntp_seconds_1, ntp_fraction_1); EXPECT_EQ(input_time, out_1); // Verify inverse. base::TimeDelta time_delta = base::TimeDelta::FromMilliseconds(1000); input_time += time_delta; uint32 ntp_seconds_2 = 0; - uint32 ntp_fractions_2 = 0; + uint32 ntp_fraction_2 = 0; - ConvertTimeTicksToNtp(input_time, &ntp_seconds_2, &ntp_fractions_2); - base::TimeTicks out_2 = ConvertNtpToTimeTicks(ntp_seconds_2, ntp_fractions_2); + ConvertTimeTicksToNtp(input_time, &ntp_seconds_2, &ntp_fraction_2); + base::TimeTicks out_2 = ConvertNtpToTimeTicks(ntp_seconds_2, ntp_fraction_2); EXPECT_EQ(input_time, out_2); // Verify inverse. // Verify delta. EXPECT_EQ((out_2 - out_1), time_delta); - EXPECT_EQ((ntp_seconds_2 - ntp_seconds_1), GG_UINT32_C(1)); - EXPECT_NEAR(ntp_fractions_2, ntp_fractions_1, 1); + EXPECT_EQ((ntp_seconds_2 - ntp_seconds_1), UINT32_C(1)); + EXPECT_NEAR(ntp_fraction_2, ntp_fraction_1, 1); time_delta = base::TimeDelta::FromMilliseconds(500); input_time += time_delta; uint32 ntp_seconds_3 = 0; - uint32 ntp_fractions_3 = 0; + uint32 ntp_fraction_3 = 0; - ConvertTimeTicksToNtp(input_time, &ntp_seconds_3, &ntp_fractions_3); - base::TimeTicks out_3 = ConvertNtpToTimeTicks(ntp_seconds_3, ntp_fractions_3); + ConvertTimeTicksToNtp(input_time, &ntp_seconds_3, &ntp_fraction_3); + base::TimeTicks out_3 = ConvertNtpToTimeTicks(ntp_seconds_3, ntp_fraction_3); EXPECT_EQ(input_time, out_3); // Verify inverse. // Verify delta. EXPECT_EQ((out_3 - out_2), time_delta); - EXPECT_NEAR((ntp_fractions_3 - ntp_fractions_2), 0xffffffff / 2, 1); -} - -TEST_F(RtcpTest, WrapAround) { - RtcpPeer rtcp_peer(cast_environment_, - &mock_sender_feedback_, - NULL, - NULL, - NULL, - kRtcpReducedSize, - base::TimeDelta::FromMilliseconds(kRtcpIntervalMs), - kReceiverSsrc, - kSenderSsrc, - kCName); - uint32 new_timestamp = 0; - uint32 old_timestamp = 0; - EXPECT_EQ(0, rtcp_peer.CheckForWrapAround(new_timestamp, old_timestamp)); - new_timestamp = 1234567890; - old_timestamp = 1234567000; - EXPECT_EQ(0, rtcp_peer.CheckForWrapAround(new_timestamp, old_timestamp)); - new_timestamp = 1234567000; - old_timestamp = 1234567890; - EXPECT_EQ(0, rtcp_peer.CheckForWrapAround(new_timestamp, old_timestamp)); - new_timestamp = 123; - old_timestamp = 4234567890u; - EXPECT_EQ(1, rtcp_peer.CheckForWrapAround(new_timestamp, old_timestamp)); - new_timestamp = 4234567890u; - old_timestamp = 123; - EXPECT_EQ(-1, rtcp_peer.CheckForWrapAround(new_timestamp, old_timestamp)); -} - -TEST_F(RtcpTest, RtpTimestampInSenderTime) { - RtcpPeer rtcp_peer(cast_environment_, - &mock_sender_feedback_, - NULL, - NULL, - NULL, - kRtcpReducedSize, - base::TimeDelta::FromMilliseconds(kRtcpIntervalMs), - kReceiverSsrc, - kSenderSsrc, - kCName); - int frequency = 32000; - uint32 rtp_timestamp = 64000; - base::TimeTicks rtp_timestamp_in_ticks; - - // Test fail before we get a OnReceivedLipSyncInfo. - EXPECT_FALSE(rtcp_peer.RtpTimestampInSenderTime(frequency, rtp_timestamp, - &rtp_timestamp_in_ticks)); - - uint32 ntp_seconds = 0; - uint32 ntp_fractions = 0; - uint64 input_time_us = 12345678901000LL; - base::TimeTicks input_time; - input_time += base::TimeDelta::FromMicroseconds(input_time_us); - - // Test exact match. - ConvertTimeTicksToNtp(input_time, &ntp_seconds, &ntp_fractions); - rtcp_peer.OnReceivedLipSyncInfo(rtp_timestamp, ntp_seconds, ntp_fractions); - EXPECT_TRUE(rtcp_peer.RtpTimestampInSenderTime(frequency, rtp_timestamp, - &rtp_timestamp_in_ticks)); - EXPECT_EQ(input_time, rtp_timestamp_in_ticks); - - // Test older rtp_timestamp. - rtp_timestamp = 32000; - EXPECT_TRUE(rtcp_peer.RtpTimestampInSenderTime(frequency, rtp_timestamp, - &rtp_timestamp_in_ticks)); - EXPECT_EQ(input_time - base::TimeDelta::FromMilliseconds(1000), - rtp_timestamp_in_ticks); - - // Test older rtp_timestamp with wrap. - rtp_timestamp = 4294903296u; - EXPECT_TRUE(rtcp_peer.RtpTimestampInSenderTime(frequency, rtp_timestamp, - &rtp_timestamp_in_ticks)); - EXPECT_EQ(input_time - base::TimeDelta::FromMilliseconds(4000), - rtp_timestamp_in_ticks); - - // Test newer rtp_timestamp. - rtp_timestamp = 128000; - EXPECT_TRUE(rtcp_peer.RtpTimestampInSenderTime(frequency, rtp_timestamp, - &rtp_timestamp_in_ticks)); - EXPECT_EQ(input_time + base::TimeDelta::FromMilliseconds(2000), - rtp_timestamp_in_ticks); - - // Test newer rtp_timestamp with wrap. - rtp_timestamp = 4294903296u; - rtcp_peer.OnReceivedLipSyncInfo(rtp_timestamp, ntp_seconds, ntp_fractions); - rtp_timestamp = 64000; - EXPECT_TRUE(rtcp_peer.RtpTimestampInSenderTime(frequency, rtp_timestamp, - &rtp_timestamp_in_ticks)); - EXPECT_EQ(input_time + base::TimeDelta::FromMilliseconds(4000), - rtp_timestamp_in_ticks); + EXPECT_NEAR((ntp_fraction_3 - ntp_fraction_2), 0xffffffff / 2, 1); } } // namespace cast diff --git a/chromium/media/cast/rtcp/rtcp_utility.cc b/chromium/media/cast/rtcp/rtcp_utility.cc index daeaa8aaceb..e29f82e9cf9 100644 --- a/chromium/media/cast/rtcp/rtcp_utility.cc +++ b/chromium/media/cast/rtcp/rtcp_utility.cc @@ -4,8 +4,9 @@ #include "media/cast/rtcp/rtcp_utility.h" +#include "base/big_endian.h" #include "base/logging.h" -#include "net/base/big_endian.h" +#include "media/cast/transport/cast_transport_defines.h" namespace media { namespace cast { @@ -19,18 +20,15 @@ RtcpParser::RtcpParser(const uint8* rtcpData, size_t rtcpDataLength) state_(kStateTopLevel), number_of_blocks_(0), field_type_(kRtcpNotValidCode) { + memset(&field_, 0, sizeof(field_)); Validate(); } RtcpParser::~RtcpParser() {} -RtcpFieldTypes RtcpParser::FieldType() const { - return field_type_; -} +RtcpFieldTypes RtcpParser::FieldType() const { return field_type_; } -const RtcpField& RtcpParser::Field() const { - return field_; -} +const RtcpField& RtcpParser::Field() const { return field_; } RtcpFieldTypes RtcpParser::Begin() { rtcp_data_ = rtcp_data_begin_; @@ -41,7 +39,8 @@ RtcpFieldTypes RtcpParser::Iterate() { // Reset packet type field_type_ = kRtcpNotValidCode; - if (!IsValid()) return kRtcpNotValidCode; + if (!IsValid()) + return kRtcpNotValidCode; switch (state_) { case kStateTopLevel: @@ -62,9 +61,6 @@ RtcpFieldTypes RtcpParser::Iterate() { case kStateApplicationSpecificCastReceiverEventLog: IterateCastReceiverLogEvent(); break; - case kStateApplicationSpecificCastSenderLog: - IterateCastSenderLog(); - break; case kStateExtendedReportBlock: IterateExtendedReportItem(); break; @@ -101,51 +97,53 @@ void RtcpParser::IterateTopLevel() { RtcpCommonHeader header; bool success = RtcpParseCommonHeader(rtcp_data_, rtcp_data_end_, &header); - if (!success) return; + if (!success) + return; rtcp_block_end_ = rtcp_data_ + header.length_in_octets; - if (rtcp_block_end_ > rtcp_data_end_) return; // Bad block! + if (rtcp_block_end_ > rtcp_data_end_) + return; // Bad block! switch (header.PT) { - case kPacketTypeSenderReport: + case transport::kPacketTypeSenderReport: // number of Report blocks number_of_blocks_ = header.IC; ParseSR(); return; - case kPacketTypeReceiverReport: + case transport::kPacketTypeReceiverReport: // number of Report blocks number_of_blocks_ = header.IC; ParseRR(); return; - case kPacketTypeSdes: + case transport::kPacketTypeSdes: // number of Sdes blocks number_of_blocks_ = header.IC; if (!ParseSdes()) { break; // Nothing supported found, continue to next block! } return; - case kPacketTypeBye: + case transport::kPacketTypeBye: number_of_blocks_ = header.IC; if (!ParseBye()) { // Nothing supported found, continue to next block! break; } return; - case kPacketTypeApplicationDefined: + case transport::kPacketTypeApplicationDefined: if (!ParseApplicationDefined(header.IC)) { // Nothing supported found, continue to next block! break; } return; - case kPacketTypeGenericRtpFeedback: // Fall through! - case kPacketTypePayloadSpecific: + case transport::kPacketTypeGenericRtpFeedback: // Fall through! + case transport::kPacketTypePayloadSpecific: if (!ParseFeedBackCommon(header)) { // Nothing supported found, continue to next block! break; } return; - case kPacketTypeXr: + case transport::kPacketTypeXr: if (!ParseExtendedReport()) { break; // Nothing supported found, continue to next block! } @@ -160,103 +158,111 @@ void RtcpParser::IterateTopLevel() { void RtcpParser::IterateReportBlockItem() { bool success = ParseReportBlockItem(); - if (!success) Iterate(); + if (!success) + Iterate(); } void RtcpParser::IterateSdesItem() { bool success = ParseSdesItem(); - if (!success) Iterate(); + if (!success) + Iterate(); } void RtcpParser::IterateByeItem() { bool success = ParseByeItem(); - if (!success) Iterate(); + if (!success) + Iterate(); } void RtcpParser::IterateExtendedReportItem() { bool success = ParseExtendedReportItem(); - if (!success) Iterate(); + if (!success) + Iterate(); } void RtcpParser::IterateExtendedReportDelaySinceLastReceiverReportItem() { bool success = ParseExtendedReportDelaySinceLastReceiverReport(); - if (!success) Iterate(); + if (!success) + Iterate(); } void RtcpParser::IterateNackItem() { bool success = ParseNackItem(); - if (!success) Iterate(); + if (!success) + Iterate(); } void RtcpParser::IterateRpsiItem() { bool success = ParseRpsiItem(); - if (!success) Iterate(); + if (!success) + Iterate(); } void RtcpParser::IterateFirItem() { bool success = ParseFirItem(); - if (!success) Iterate(); + if (!success) + Iterate(); } void RtcpParser::IteratePayloadSpecificAppItem() { bool success = ParsePayloadSpecificAppItem(); - if (!success) Iterate(); + if (!success) + Iterate(); } void RtcpParser::IteratePayloadSpecificRembItem() { bool success = ParsePayloadSpecificRembItem(); - if (!success) Iterate(); + if (!success) + Iterate(); } void RtcpParser::IteratePayloadSpecificCastItem() { bool success = ParsePayloadSpecificCastItem(); - if (!success) Iterate(); + if (!success) + Iterate(); } void RtcpParser::IteratePayloadSpecificCastNackItem() { bool success = ParsePayloadSpecificCastNackItem(); - if (!success) Iterate(); + if (!success) + Iterate(); } void RtcpParser::IterateCastReceiverLogFrame() { bool success = ParseCastReceiverLogFrameItem(); - if (!success) Iterate(); + if (!success) + Iterate(); } void RtcpParser::IterateCastReceiverLogEvent() { bool success = ParseCastReceiverLogEventItem(); - if (!success) Iterate(); -} - -void RtcpParser::IterateCastSenderLog() { - bool success = ParseCastSenderLogItem(); - if (!success) Iterate(); + if (!success) + Iterate(); } void RtcpParser::Validate() { - if (rtcp_data_ == NULL) return; // NOT VALID + if (rtcp_data_ == NULL) + return; // NOT VALID RtcpCommonHeader header; - bool success = RtcpParseCommonHeader(rtcp_data_begin_, rtcp_data_end_, - &header); + bool success = + RtcpParseCommonHeader(rtcp_data_begin_, rtcp_data_end_, &header); - if (!success) return; // NOT VALID! + if (!success) + return; // NOT VALID! valid_packet_ = true; } -bool RtcpParser::IsValid() const { - return valid_packet_; -} +bool RtcpParser::IsValid() const { return valid_packet_; } -void RtcpParser::EndCurrentBlock() { - rtcp_data_ = rtcp_block_end_; -} +void RtcpParser::EndCurrentBlock() { rtcp_data_ = rtcp_block_end_; } bool RtcpParser::RtcpParseCommonHeader(const uint8* data_begin, const uint8* data_end, RtcpCommonHeader* parsed_header) const { - if (!data_begin || !data_end) return false; + if (!data_begin || !data_end) + return false; // 0 1 2 3 // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 @@ -266,31 +272,36 @@ bool RtcpParser::RtcpParseCommonHeader(const uint8* data_begin, // // Common header for all Rtcp packets, 4 octets. - if ((data_end - data_begin) < 4) return false; + if ((data_end - data_begin) < 4) + return false; - parsed_header->V = data_begin[0] >> 6; - parsed_header->P = ((data_begin[0] & 0x20) == 0) ? false : true; + parsed_header->V = data_begin[0] >> 6; + parsed_header->P = ((data_begin[0] & 0x20) == 0) ? false : true; parsed_header->IC = data_begin[0] & 0x1f; parsed_header->PT = data_begin[1]; parsed_header->length_in_octets = ((data_begin[2] << 8) + data_begin[3] + 1) * 4; - if (parsed_header->length_in_octets == 0) return false; + if (parsed_header->length_in_octets == 0) + return false; // Check if RTP version field == 2. - if (parsed_header->V != 2) return false; + if (parsed_header->V != 2) + return false; return true; } bool RtcpParser::ParseRR() { ptrdiff_t length = rtcp_block_end_ - rtcp_data_; - if (length < 8) return false; + if (length < 8) + return false; field_type_ = kRtcpRrCode; - net::BigEndianReader big_endian_reader(rtcp_data_, length); + base::BigEndianReader big_endian_reader( + reinterpret_cast<const char*>(rtcp_data_), length); big_endian_reader.Skip(4); // Skip header big_endian_reader.ReadU32(&field_.receiver_report.sender_ssrc); field_.receiver_report.number_of_report_blocks = number_of_blocks_; @@ -309,7 +320,8 @@ bool RtcpParser::ParseSR() { } field_type_ = kRtcpSrCode; - net::BigEndianReader big_endian_reader(rtcp_data_, length); + base::BigEndianReader big_endian_reader( + reinterpret_cast<const char*>(rtcp_data_), length); big_endian_reader.Skip(4); // Skip header big_endian_reader.ReadU32(&field_.sender_report.sender_ssrc); big_endian_reader.ReadU32(&field_.sender_report.ntp_most_significant); @@ -339,7 +351,8 @@ bool RtcpParser::ParseReportBlockItem() { return false; } - net::BigEndianReader big_endian_reader(rtcp_data_, length); + base::BigEndianReader big_endian_reader( + reinterpret_cast<const char*>(rtcp_data_), length); big_endian_reader.ReadU32(&field_.report_block_item.ssrc); big_endian_reader.ReadU8(&field_.report_block_item.fraction_lost); @@ -399,7 +412,8 @@ bool RtcpParser::ParseSdesItem() { } uint32 ssrc; - net::BigEndianReader big_endian_reader(rtcp_data_, data_length); + base::BigEndianReader big_endian_reader( + reinterpret_cast<const char*>(rtcp_data_), data_length); big_endian_reader.ReadU32(&ssrc); rtcp_data_ += 4; @@ -418,7 +432,8 @@ bool RtcpParser::ParseSdesTypes() { // Only the c_name item is mandatory. RFC 3550 page 46. bool found_c_name = false; ptrdiff_t length = rtcp_block_end_ - rtcp_data_; - net::BigEndianReader big_endian_reader(rtcp_data_, length); + base::BigEndianReader big_endian_reader( + reinterpret_cast<const char*>(rtcp_data_), length); while (big_endian_reader.remaining() > 0) { uint8 tag; @@ -484,7 +499,8 @@ bool RtcpParser::ParseByeItem() { field_type_ = kRtcpByeCode; - net::BigEndianReader big_endian_reader(rtcp_data_, length); + base::BigEndianReader big_endian_reader( + reinterpret_cast<const char*>(rtcp_data_), length); big_endian_reader.ReadU32(&field_.bye.sender_ssrc); rtcp_data_ += 4; @@ -498,8 +514,7 @@ bool RtcpParser::ParseByeItem() { bool RtcpParser::ParseApplicationDefined(uint8 subtype) { ptrdiff_t length = rtcp_block_end_ - rtcp_data_; - if (length < 16 || - !(subtype == kSenderLogSubtype || subtype == kReceiverLogSubtype)) { + if (length < 16 || subtype != kReceiverLogSubtype) { state_ = kStateTopLevel; EndCurrentBlock(); return false; @@ -508,7 +523,8 @@ bool RtcpParser::ParseApplicationDefined(uint8 subtype) { uint32 sender_ssrc; uint32 name; - net::BigEndianReader big_endian_reader(rtcp_data_, length); + base::BigEndianReader big_endian_reader( + reinterpret_cast<const char*>(rtcp_data_), length); big_endian_reader.Skip(4); // Skip header. big_endian_reader.ReadU32(&sender_ssrc); big_endian_reader.ReadU32(&name); @@ -520,11 +536,6 @@ bool RtcpParser::ParseApplicationDefined(uint8 subtype) { } rtcp_data_ += 12; switch (subtype) { - case kSenderLogSubtype: - state_ = kStateApplicationSpecificCastSenderLog; - field_type_ = kRtcpApplicationSpecificCastSenderLogCode; - field_.cast_sender_log.sender_ssrc = sender_ssrc; - break; case kReceiverLogSubtype: state_ = kStateApplicationSpecificCastReceiverFrameLog; field_type_ = kRtcpApplicationSpecificCastReceiverLogCode; @@ -545,7 +556,8 @@ bool RtcpParser::ParseCastReceiverLogFrameItem() { } uint32 rtp_timestamp; uint32 data; - net::BigEndianReader big_endian_reader(rtcp_data_, length); + base::BigEndianReader big_endian_reader( + reinterpret_cast<const char*>(rtcp_data_), length); big_endian_reader.ReadU32(&rtp_timestamp); big_endian_reader.ReadU32(&data); @@ -577,7 +589,8 @@ bool RtcpParser::ParseCastReceiverLogEventItem() { uint16 delay_delta_or_packet_id; uint16 event_type_and_timestamp_delta; - net::BigEndianReader big_endian_reader(rtcp_data_, length); + base::BigEndianReader big_endian_reader( + reinterpret_cast<const char*>(rtcp_data_), length); big_endian_reader.ReadU16(&delay_delta_or_packet_id); big_endian_reader.ReadU16(&event_type_and_timestamp_delta); @@ -585,7 +598,9 @@ bool RtcpParser::ParseCastReceiverLogEventItem() { field_.cast_receiver_log.event = static_cast<uint8>(event_type_and_timestamp_delta >> 12); - field_.cast_receiver_log.delay_delta_or_packet_id = delay_delta_or_packet_id; + // delay_delta is in union'ed with packet_id. + field_.cast_receiver_log.delay_delta_or_packet_id.packet_id = + delay_delta_or_packet_id; field_.cast_receiver_log.event_timestamp_delta = event_type_and_timestamp_delta & 0xfff; @@ -593,30 +608,10 @@ bool RtcpParser::ParseCastReceiverLogEventItem() { return true; } -bool RtcpParser::ParseCastSenderLogItem() { - ptrdiff_t length = rtcp_block_end_ - rtcp_data_; - - if (length < 4) { - state_ = kStateTopLevel; - EndCurrentBlock(); - return false; - } - uint32 data; - net::BigEndianReader big_endian_reader(rtcp_data_, length); - big_endian_reader.ReadU32(&data); - - rtcp_data_ += 4; - - field_.cast_sender_log.status = static_cast<uint8>(data >> 24); - // We have 24 LSB of the RTP timestamp on the wire. - field_.cast_sender_log.rtp_timestamp = data & 0xffffff; - field_type_ = kRtcpApplicationSpecificCastSenderLogCode; - return true; -} - bool RtcpParser::ParseFeedBackCommon(const RtcpCommonHeader& header) { - DCHECK((header.PT == kPacketTypeGenericRtpFeedback) || - (header.PT == kPacketTypePayloadSpecific)) << "Invalid state"; + DCHECK((header.PT == transport::kPacketTypeGenericRtpFeedback) || + (header.PT == transport::kPacketTypePayloadSpecific)) + << "Invalid state"; ptrdiff_t length = rtcp_block_end_ - rtcp_data_; @@ -627,21 +622,22 @@ bool RtcpParser::ParseFeedBackCommon(const RtcpCommonHeader& header) { uint32 sender_ssrc; uint32 media_ssrc; - net::BigEndianReader big_endian_reader(rtcp_data_, length); + base::BigEndianReader big_endian_reader( + reinterpret_cast<const char*>(rtcp_data_), length); big_endian_reader.Skip(4); // Skip header. big_endian_reader.ReadU32(&sender_ssrc); big_endian_reader.ReadU32(&media_ssrc); rtcp_data_ += 12; - if (header.PT == kPacketTypeGenericRtpFeedback) { + if (header.PT == transport::kPacketTypeGenericRtpFeedback) { // Transport layer feedback switch (header.IC) { case 1: // Nack field_type_ = kRtcpGenericRtpFeedbackNackCode; field_.nack.sender_ssrc = sender_ssrc; - field_.nack.media_ssrc = media_ssrc; + field_.nack.media_ssrc = media_ssrc; state_ = kStateGenericRtpFeedbackNack; return true; case 2: @@ -667,14 +663,14 @@ bool RtcpParser::ParseFeedBackCommon(const RtcpCommonHeader& header) { EndCurrentBlock(); return false; - } else if (header.PT == kPacketTypePayloadSpecific) { + } else if (header.PT == transport::kPacketTypePayloadSpecific) { // Payload specific feedback switch (header.IC) { case 1: // PLI field_type_ = kRtcpPayloadSpecificPliCode; field_.pli.sender_ssrc = sender_ssrc; - field_.pli.media_ssrc = media_ssrc; + field_.pli.media_ssrc = media_ssrc; // Note: No state transition, PLI FCI is empty! return true; @@ -684,7 +680,7 @@ bool RtcpParser::ParseFeedBackCommon(const RtcpCommonHeader& header) { case 3: field_type_ = kRtcpPayloadSpecificRpsiCode; field_.rpsi.sender_ssrc = sender_ssrc; - field_.rpsi.media_ssrc = media_ssrc; + field_.rpsi.media_ssrc = media_ssrc; state_ = kStatePayloadSpecificRpsi; return true; case 4: @@ -693,7 +689,7 @@ bool RtcpParser::ParseFeedBackCommon(const RtcpCommonHeader& header) { case 15: field_type_ = kRtcpPayloadSpecificAppCode; field_.application_specific.sender_ssrc = sender_ssrc; - field_.application_specific.media_ssrc = media_ssrc; + field_.application_specific.media_ssrc = media_ssrc; state_ = kStatePayloadSpecificApplication; return true; default: @@ -736,7 +732,8 @@ bool RtcpParser::ParseRpsiItem() { field_type_ = kRtcpPayloadSpecificRpsiCode; uint8 padding_bits; - net::BigEndianReader big_endian_reader(rtcp_data_, length); + base::BigEndianReader big_endian_reader( + reinterpret_cast<const char*>(rtcp_data_), length); big_endian_reader.ReadU8(&padding_bits); big_endian_reader.ReadU8(&field_.rpsi.payload_type); big_endian_reader.ReadBytes(&field_.rpsi.native_bit_string, length - 2); @@ -759,7 +756,8 @@ bool RtcpParser::ParseNackItem() { field_type_ = kRtcpGenericRtpFeedbackNackItemCode; - net::BigEndianReader big_endian_reader(rtcp_data_, length); + base::BigEndianReader big_endian_reader( + reinterpret_cast<const char*>(rtcp_data_), length); big_endian_reader.ReadU16(&field_.nack_item.packet_id); big_endian_reader.ReadU16(&field_.nack_item.bitmask); rtcp_data_ += 4; @@ -775,7 +773,8 @@ bool RtcpParser::ParsePayloadSpecificAppItem() { return false; } uint32 name; - net::BigEndianReader big_endian_reader(rtcp_data_, length); + base::BigEndianReader big_endian_reader( + reinterpret_cast<const char*>(rtcp_data_), length); big_endian_reader.ReadU32(&name); rtcp_data_ += 4; @@ -802,7 +801,8 @@ bool RtcpParser::ParsePayloadSpecificRembItem() { return false; } - net::BigEndianReader big_endian_reader(rtcp_data_, length); + base::BigEndianReader big_endian_reader( + reinterpret_cast<const char*>(rtcp_data_), length); big_endian_reader.ReadU8(&field_.remb_item.number_of_ssrcs); uint8 byte_1; @@ -841,9 +841,11 @@ bool RtcpParser::ParsePayloadSpecificCastItem() { } field_type_ = kRtcpPayloadSpecificCastCode; - net::BigEndianReader big_endian_reader(rtcp_data_, length); + base::BigEndianReader big_endian_reader( + reinterpret_cast<const char*>(rtcp_data_), length); big_endian_reader.ReadU8(&field_.cast_item.last_frame_id); big_endian_reader.ReadU8(&field_.cast_item.number_of_lost_fields); + big_endian_reader.ReadU16(&field_.cast_item.target_delay_ms); rtcp_data_ += 4; @@ -867,7 +869,8 @@ bool RtcpParser::ParsePayloadSpecificCastNackItem() { } field_type_ = kRtcpPayloadSpecificCastNackItemCode; - net::BigEndianReader big_endian_reader(rtcp_data_, length); + base::BigEndianReader big_endian_reader( + reinterpret_cast<const char*>(rtcp_data_), length); big_endian_reader.ReadU8(&field_.cast_nack_item.frame_id); big_endian_reader.ReadU16(&field_.cast_nack_item.packet_id); big_endian_reader.ReadU8(&field_.cast_nack_item.bitmask); @@ -887,7 +890,8 @@ bool RtcpParser::ParseFirItem() { } field_type_ = kRtcpPayloadSpecificFirItemCode; - net::BigEndianReader big_endian_reader(rtcp_data_, length); + base::BigEndianReader big_endian_reader( + reinterpret_cast<const char*>(rtcp_data_), length); big_endian_reader.ReadU32(&field_.fir_item.ssrc); big_endian_reader.ReadU8(&field_.fir_item.command_sequence_number); @@ -897,11 +901,13 @@ bool RtcpParser::ParseFirItem() { bool RtcpParser::ParseExtendedReport() { ptrdiff_t length = rtcp_block_end_ - rtcp_data_; - if (length < 8) return false; + if (length < 8) + return false; field_type_ = kRtcpXrCode; - net::BigEndianReader big_endian_reader(rtcp_data_, length); + base::BigEndianReader big_endian_reader( + reinterpret_cast<const char*>(rtcp_data_), length); big_endian_reader.Skip(4); // Skip header. big_endian_reader.ReadU32(&field_.extended_report.sender_ssrc); @@ -921,7 +927,8 @@ bool RtcpParser::ParseExtendedReportItem() { uint8 block_type; uint16 block_length; - net::BigEndianReader big_endian_reader(rtcp_data_, length); + base::BigEndianReader big_endian_reader( + reinterpret_cast<const char*>(rtcp_data_), length); big_endian_reader.ReadU8(&block_type); big_endian_reader.Skip(1); // Ignore reserved. big_endian_reader.ReadU16(&block_length); @@ -970,7 +977,8 @@ bool RtcpParser::ParseExtendedReportReceiverReferenceTimeReport() { return false; } - net::BigEndianReader big_endian_reader(rtcp_data_, length); + base::BigEndianReader big_endian_reader( + reinterpret_cast<const char*>(rtcp_data_), length); big_endian_reader.ReadU32(&field_.rrtr.ntp_most_significant); big_endian_reader.ReadU32(&field_.rrtr.ntp_least_significant); @@ -993,7 +1001,8 @@ bool RtcpParser::ParseExtendedReportDelaySinceLastReceiverReport() { return false; } - net::BigEndianReader big_endian_reader(rtcp_data_, length); + base::BigEndianReader big_endian_reader( + reinterpret_cast<const char*>(rtcp_data_), length); big_endian_reader.ReadU32(&field_.dlrr.receivers_ssrc); big_endian_reader.ReadU32(&field_.dlrr.last_receiver_report); big_endian_reader.ReadU32(&field_.dlrr.delay_last_receiver_report); @@ -1005,5 +1014,53 @@ bool RtcpParser::ParseExtendedReportDelaySinceLastReceiverReport() { return true; } +// Converts a log event type to an integer value. +// NOTE: We have only allocated 4 bits to represent the type of event over the +// wire. Therefore, this function can only return values from 0 to 15. +uint8 ConvertEventTypeToWireFormat(CastLoggingEvent event) { + switch (event) { + case FRAME_ACK_SENT: + return 11; + case FRAME_PLAYOUT: + return 12; + case FRAME_DECODED: + return 13; + case PACKET_RECEIVED: + return 14; + default: + return 0; // Not an interesting event. + } +} + +CastLoggingEvent TranslateToLogEventFromWireFormat(uint8 event) { + // TODO(imcheng): Remove the old mappings once they are no longer used. + switch (event) { + case 1: // AudioAckSent + case 5: // VideoAckSent + case 11: // Unified + return FRAME_ACK_SENT; + case 2: // AudioPlayoutDelay + case 7: // VideoRenderDelay + case 12: // Unified + return FRAME_PLAYOUT; + case 3: // AudioFrameDecoded + case 6: // VideoFrameDecoded + case 13: // Unified + return FRAME_DECODED; + case 4: // AudioPacketReceived + case 8: // VideoPacketReceived + case 14: // Unified + return PACKET_RECEIVED; + case 9: // DuplicateAudioPacketReceived + case 10: // DuplicateVideoPacketReceived + default: + // If the sender adds new log messages we will end up here until we add + // the new messages in the receiver. + VLOG(1) << "Unexpected log message received: " << static_cast<int>(event); + NOTREACHED(); + return UNKNOWN; + } +} + } // namespace cast } // namespace media diff --git a/chromium/media/cast/rtcp/rtcp_utility.h b/chromium/media/cast/rtcp/rtcp_utility.h index 5cf55d91060..34f3f25a889 100644 --- a/chromium/media/cast/rtcp/rtcp_utility.h +++ b/chromium/media/cast/rtcp/rtcp_utility.h @@ -7,6 +7,7 @@ #include "media/cast/cast_config.h" #include "media/cast/cast_defines.h" +#include "media/cast/logging/logging_defines.h" #include "media/cast/rtcp/rtcp_defines.h" namespace media { @@ -21,7 +22,6 @@ static const int kRtcpMaxNumberOfRembFeedbackSsrcs = 255; static const uint32 kRemb = ('R' << 24) + ('E' << 16) + ('M' << 8) + 'B'; static const uint32 kCast = ('C' << 24) + ('A' << 16) + ('S' << 8) + 'T'; -static const uint8 kSenderLogSubtype = 1; static const uint8 kReceiverLogSubtype = 2; static const size_t kRtcpMaxReceiverLogMessages = 256; @@ -37,7 +37,7 @@ struct RtcpFieldReceiverReport { struct RtcpFieldSenderReport { // RFC 3550. uint32 sender_ssrc; - uint8 number_of_report_blocks; + uint8 number_of_report_blocks; uint32 ntp_most_significant; uint32 ntp_least_significant; uint32 rtp_timestamp; @@ -48,7 +48,7 @@ struct RtcpFieldSenderReport { struct RtcpFieldReportBlockItem { // RFC 3550. uint32 ssrc; - uint8 fraction_lost; + uint8 fraction_lost; uint32 cumulative_number_of_packets_lost; uint32 extended_highest_sequence_number; uint32 jitter; @@ -101,9 +101,9 @@ struct RtcpFieldPayloadSpecificRpsi { // RFC 4585. uint32 sender_ssrc; uint32 media_ssrc; - uint8 payload_type; + uint8 payload_type; uint16 number_of_valid_bits; - uint8 native_bit_string[kRtcpRpsiDataSize]; + uint8 native_bit_string[kRtcpRpsiDataSize]; }; struct RtcpFieldXr { @@ -138,6 +138,7 @@ struct RtcpFieldPayloadSpecificRembItem { struct RtcpFieldPayloadSpecificCastItem { uint8 last_frame_id; uint8 number_of_lost_fields; + uint16 target_delay_ms; }; struct RtcpFieldPayloadSpecificCastNackItem { @@ -151,41 +152,37 @@ struct RtcpFieldApplicationSpecificCastReceiverLogItem { uint32 rtp_timestamp; uint32 event_timestamp_base; uint8 event; - uint16 delay_delta_or_packet_id; + union { + uint16 packet_id; + int16 delay_delta; + } delay_delta_or_packet_id; uint16 event_timestamp_delta; }; -struct RtcpFieldApplicationSpecificCastSenderLogItem { - uint32 sender_ssrc; - uint8 status; - uint32 rtp_timestamp; -}; - union RtcpField { - RtcpFieldReceiverReport receiver_report; - RtcpFieldSenderReport sender_report; - RtcpFieldReportBlockItem report_block_item; - RtcpFieldSdesCName c_name; - RtcpFieldBye bye; - - RtcpFieldXr extended_report; - RtcpFieldXrRrtr rrtr; - RtcpFieldXrDlrr dlrr; - - RtcpFieldGenericRtpFeedbackNack nack; - RtcpFieldGenericRtpFeedbackNackItem nack_item; - - RtcpFieldPayloadSpecificPli pli; - RtcpFieldPayloadSpecificRpsi rpsi; - RtcpFieldPayloadSpecificFir fir; - RtcpFieldPayloadSpecificFirItem fir_item; - RtcpFieldPayloadSpecificApplication application_specific; - RtcpFieldPayloadSpecificRembItem remb_item; - RtcpFieldPayloadSpecificCastItem cast_item; - RtcpFieldPayloadSpecificCastNackItem cast_nack_item; + RtcpFieldReceiverReport receiver_report; + RtcpFieldSenderReport sender_report; + RtcpFieldReportBlockItem report_block_item; + RtcpFieldSdesCName c_name; + RtcpFieldBye bye; + + RtcpFieldXr extended_report; + RtcpFieldXrRrtr rrtr; + RtcpFieldXrDlrr dlrr; + + RtcpFieldGenericRtpFeedbackNack nack; + RtcpFieldGenericRtpFeedbackNackItem nack_item; + + RtcpFieldPayloadSpecificPli pli; + RtcpFieldPayloadSpecificRpsi rpsi; + RtcpFieldPayloadSpecificFir fir; + RtcpFieldPayloadSpecificFirItem fir_item; + RtcpFieldPayloadSpecificApplication application_specific; + RtcpFieldPayloadSpecificRembItem remb_item; + RtcpFieldPayloadSpecificCastItem cast_item; + RtcpFieldPayloadSpecificCastNackItem cast_nack_item; RtcpFieldApplicationSpecificCastReceiverLogItem cast_receiver_log; - RtcpFieldApplicationSpecificCastSenderLogItem cast_sender_log; }; enum RtcpFieldTypes { @@ -195,7 +192,6 @@ enum RtcpFieldTypes { kRtcpRrCode, kRtcpSrCode, kRtcpReportBlockItemCode, - kRtcpSdesCode, kRtcpSdesChunkCode, kRtcpByeCode, @@ -209,7 +205,6 @@ enum RtcpFieldTypes { // RFC 4585. kRtcpGenericRtpFeedbackNackCode, kRtcpGenericRtpFeedbackNackItemCode, - kRtcpPayloadSpecificPliCode, kRtcpPayloadSpecificRpsiCode, kRtcpPayloadSpecificAppCode, @@ -222,7 +217,6 @@ enum RtcpFieldTypes { kRtcpApplicationSpecificCastReceiverLogCode, kRtcpApplicationSpecificCastReceiverLogFrameCode, kRtcpApplicationSpecificCastReceiverLogEventCode, - kRtcpApplicationSpecificCastSenderLogCode, // RFC 5104. kRtcpPayloadSpecificFirCode, @@ -233,27 +227,13 @@ enum RtcpFieldTypes { }; struct RtcpCommonHeader { - uint8 V; // Version. - bool P; // Padding. - uint8 IC; // Item count / subtype. - uint8 PT; // Packet Type. + uint8 V; // Version. + bool P; // Padding. + uint8 IC; // Item count / subtype. + uint8 PT; // Packet Type. uint16 length_in_octets; }; -enum RtcpPacketTypes { - kPacketTypeLow = 194, // SMPTE time-code mapping. - kPacketTypeInterArrivalJitterReport = 195, - kPacketTypeSenderReport = 200, - kPacketTypeReceiverReport = 201, - kPacketTypeSdes = 202, - kPacketTypeBye = 203, - kPacketTypeApplicationDefined = 204, - kPacketTypeGenericRtpFeedback = 205, - kPacketTypePayloadSpecific = 206, - kPacketTypeXr = 207, - kPacketTypeHigh = 210, // Port Mapping. -}; - class RtcpParser { public: RtcpParser(const uint8* rtcp_data, size_t rtcp_length); @@ -275,7 +255,6 @@ class RtcpParser { kStateBye, kStateApplicationSpecificCastReceiverFrameLog, kStateApplicationSpecificCastReceiverEventLog, - kStateApplicationSpecificCastSenderLog, kStateExtendedReportBlock, kStateExtendedReportDelaySinceLastReceiverReport, kStateGenericRtpFeedbackNack, @@ -297,7 +276,6 @@ class RtcpParser { void IterateByeItem(); void IterateCastReceiverLogFrame(); void IterateCastReceiverLogEvent(); - void IterateCastSenderLog(); void IterateExtendedReportItem(); void IterateExtendedReportDelaySinceLastReceiverReportItem(); void IterateNackItem(); @@ -323,7 +301,6 @@ class RtcpParser { bool ParseApplicationDefined(uint8 subtype); bool ParseCastReceiverLogFrameItem(); bool ParseCastReceiverLogEventItem(); - bool ParseCastSenderLogItem(); bool ParseExtendedReport(); bool ParseExtendedReportItem(); @@ -355,6 +332,14 @@ class RtcpParser { DISALLOW_COPY_AND_ASSIGN(RtcpParser); }; +// Converts a log event type to an integer value. +// NOTE: We have only allocated 4 bits to represent the type of event over the +// wire. Therefore, this function can only return values from 0 to 15. +uint8 ConvertEventTypeToWireFormat(CastLoggingEvent event); + +// The inverse of |ConvertEventTypeToWireFormat()|. +CastLoggingEvent TranslateToLogEventFromWireFormat(uint8 event); + } // namespace cast } // namespace media diff --git a/chromium/media/cast/rtcp/test_rtcp_packet_builder.cc b/chromium/media/cast/rtcp/test_rtcp_packet_builder.cc index f4117f53dec..8d0809d928e 100644 --- a/chromium/media/cast/rtcp/test_rtcp_packet_builder.cc +++ b/chromium/media/cast/rtcp/test_rtcp_packet_builder.cc @@ -5,14 +5,14 @@ #include "media/cast/rtcp/test_rtcp_packet_builder.h" #include "base/logging.h" +#include "media/cast/rtcp/rtcp_utility.h" namespace media { namespace cast { TestRtcpPacketBuilder::TestRtcpPacketBuilder() : ptr_of_length_(NULL), - big_endian_writer_(buffer_, kIpPacketSize) { -} + big_endian_writer_(reinterpret_cast<char*>(buffer_), kMaxIpPacketSize) {} void TestRtcpPacketBuilder::AddSr(uint32 sender_ssrc, int number_of_report_blocks) { @@ -91,8 +91,8 @@ void TestRtcpPacketBuilder::AddXrHeader(uint32 sender_ssrc) { } void TestRtcpPacketBuilder::AddXrUnknownBlock() { - big_endian_writer_.WriteU8(9); // Block type. - big_endian_writer_.WriteU8(0); // Reserved. + big_endian_writer_.WriteU8(9); // Block type. + big_endian_writer_.WriteU8(0); // Reserved. big_endian_writer_.WriteU16(4); // Block length. // First receiver same as sender of this report. big_endian_writer_.WriteU32(0); @@ -102,8 +102,8 @@ void TestRtcpPacketBuilder::AddXrUnknownBlock() { } void TestRtcpPacketBuilder::AddXrDlrrBlock(uint32 sender_ssrc) { - big_endian_writer_.WriteU8(5); // Block type. - big_endian_writer_.WriteU8(0); // Reserved. + big_endian_writer_.WriteU8(5); // Block type. + big_endian_writer_.WriteU8(0); // Reserved. big_endian_writer_.WriteU16(3); // Block length. // First receiver same as sender of this report. @@ -113,8 +113,8 @@ void TestRtcpPacketBuilder::AddXrDlrrBlock(uint32 sender_ssrc) { } void TestRtcpPacketBuilder::AddXrExtendedDlrrBlock(uint32 sender_ssrc) { - big_endian_writer_.WriteU8(5); // Block type. - big_endian_writer_.WriteU8(0); // Reserved. + big_endian_writer_.WriteU8(5); // Block type. + big_endian_writer_.WriteU8(0); // Reserved. big_endian_writer_.WriteU16(9); // Block length. big_endian_writer_.WriteU32(0xaaaaaaaa); big_endian_writer_.WriteU32(0xaaaaaaaa); @@ -130,8 +130,8 @@ void TestRtcpPacketBuilder::AddXrExtendedDlrrBlock(uint32 sender_ssrc) { } void TestRtcpPacketBuilder::AddXrRrtrBlock() { - big_endian_writer_.WriteU8(4); // Block type. - big_endian_writer_.WriteU8(0); // Reserved. + big_endian_writer_.WriteU8(4); // Block type. + big_endian_writer_.WriteU8(0); // Reserved. big_endian_writer_.WriteU16(2); // Block length. big_endian_writer_.WriteU32(kNtpHigh); big_endian_writer_.WriteU32(kNtpLow); @@ -167,8 +167,8 @@ void TestRtcpPacketBuilder::AddRpsi(uint32 sender_ssrc, uint32 media_ssrc) { uint64 picture_id = kPictureId; for (int i = 9; i > 0; i--) { - big_endian_writer_.WriteU8( - 0x80 | static_cast<uint8>(picture_id >> (i * 7))); + big_endian_writer_.WriteU8(0x80 | + static_cast<uint8>(picture_id >> (i * 7))); } // Add last byte of picture ID. big_endian_writer_.WriteU8(static_cast<uint8>(picture_id & 0x7f)); @@ -189,7 +189,9 @@ void TestRtcpPacketBuilder::AddRemb(uint32 sender_ssrc, uint32 media_ssrc) { big_endian_writer_.WriteU32(media_ssrc); } -void TestRtcpPacketBuilder::AddCast(uint32 sender_ssrc, uint32 media_ssrc) { +void TestRtcpPacketBuilder::AddCast(uint32 sender_ssrc, + uint32 media_ssrc, + uint16 target_delay_ms) { AddRtcpHeader(206, 15); big_endian_writer_.WriteU32(sender_ssrc); big_endian_writer_.WriteU32(media_ssrc); @@ -198,8 +200,8 @@ void TestRtcpPacketBuilder::AddCast(uint32 sender_ssrc, uint32 media_ssrc) { big_endian_writer_.WriteU8('S'); big_endian_writer_.WriteU8('T'); big_endian_writer_.WriteU8(kAckFrameId); - big_endian_writer_.WriteU8(3); // Loss fields. - big_endian_writer_.WriteU16(0); // Reserved. + big_endian_writer_.WriteU8(3); // Loss fields. + big_endian_writer_.WriteU16(target_delay_ms); big_endian_writer_.WriteU8(kLostFrameId); big_endian_writer_.WriteU16(kRtcpCastAllPacketsLost); big_endian_writer_.WriteU8(0); // Lost packet id mask. @@ -211,21 +213,6 @@ void TestRtcpPacketBuilder::AddCast(uint32 sender_ssrc, uint32 media_ssrc) { big_endian_writer_.WriteU8(0); // Lost packet id mask. } -void TestRtcpPacketBuilder::AddSenderLog(uint32 sender_ssrc) { - AddRtcpHeader(204, 1); - big_endian_writer_.WriteU32(sender_ssrc); - big_endian_writer_.WriteU8('C'); - big_endian_writer_.WriteU8('A'); - big_endian_writer_.WriteU8('S'); - big_endian_writer_.WriteU8('T'); -} - -void TestRtcpPacketBuilder::AddSenderFrameLog(uint8 event_id, - uint32 rtp_timestamp) { - big_endian_writer_.WriteU32( - (static_cast<uint32>(event_id) << 24) + (rtp_timestamp & 0xffffff)); -} - void TestRtcpPacketBuilder::AddReceiverLog(uint32 sender_ssrc) { AddRtcpHeader(204, 2); big_endian_writer_.WriteU32(sender_ssrc); @@ -236,7 +223,8 @@ void TestRtcpPacketBuilder::AddReceiverLog(uint32 sender_ssrc) { } void TestRtcpPacketBuilder::AddReceiverFrameLog(uint32 rtp_timestamp, - int num_events, uint32 event_timesamp_base) { + int num_events, + uint32 event_timesamp_base) { big_endian_writer_.WriteU32(rtp_timestamp); big_endian_writer_.WriteU8(static_cast<uint8>(num_events - 1)); big_endian_writer_.WriteU8(static_cast<uint8>(event_timesamp_base >> 16)); @@ -245,14 +233,22 @@ void TestRtcpPacketBuilder::AddReceiverFrameLog(uint32 rtp_timestamp, } void TestRtcpPacketBuilder::AddReceiverEventLog(uint16 event_data, - uint8 event_id, uint16 event_timesamp_delta) { + CastLoggingEvent event, + uint16 event_timesamp_delta) { big_endian_writer_.WriteU16(event_data); + uint8 event_id = ConvertEventTypeToWireFormat(event); uint16 type_and_delta = static_cast<uint16>(event_id) << 12; type_and_delta += event_timesamp_delta & 0x0fff; big_endian_writer_.WriteU16(type_and_delta); } -const uint8* TestRtcpPacketBuilder::Packet() { +scoped_ptr<media::cast::Packet> TestRtcpPacketBuilder::GetPacket() { + PatchLengthField(); + return scoped_ptr<media::cast::Packet>( + new media::cast::Packet(buffer_, buffer_ + Length())); +} + +const uint8* TestRtcpPacketBuilder::Data() { PatchLengthField(); return buffer_; } diff --git a/chromium/media/cast/rtcp/test_rtcp_packet_builder.h b/chromium/media/cast/rtcp/test_rtcp_packet_builder.h index 9b63a37fa4a..d4266670aba 100644 --- a/chromium/media/cast/rtcp/test_rtcp_packet_builder.h +++ b/chromium/media/cast/rtcp/test_rtcp_packet_builder.h @@ -7,8 +7,9 @@ #ifndef MEDIA_CAST_RTCP_TEST_RTCP_PACKET_BUILDER_H_ #define MEDIA_CAST_RTCP_TEST_RTCP_PACKET_BUILDER_H_ +#include "base/big_endian.h" +#include "media/cast/cast_config.h" #include "media/cast/rtcp/rtcp_defines.h" -#include "net/base/big_endian.h" namespace media { namespace cast { @@ -58,7 +59,9 @@ class TestRtcpPacketBuilder { TestRtcpPacketBuilder(); void AddSr(uint32 sender_ssrc, int number_of_report_blocks); - void AddSrWithNtp(uint32 sender_ssrc, uint32 ntp_high, uint32 ntp_low, + void AddSrWithNtp(uint32 sender_ssrc, + uint32 ntp_high, + uint32 ntp_low, uint32 rtp_timestamp); void AddRr(uint32 sender_ssrc, int number_of_report_blocks); void AddRb(uint32 rtp_ssrc); @@ -76,17 +79,18 @@ class TestRtcpPacketBuilder { void AddPli(uint32 sender_ssrc, uint32 media_ssrc); void AddRpsi(uint32 sender_ssrc, uint32 media_ssrc); void AddRemb(uint32 sender_ssrc, uint32 media_ssrc); - void AddCast(uint32 sender_ssrc, uint32 media_ssrc); - void AddSenderLog(uint32 sender_ssrc); - void AddSenderFrameLog(uint8 event_id, uint32 rtp_timestamp); + void AddCast(uint32 sender_ssrc, uint32 media_ssrc, uint16 target_delay_ms); void AddReceiverLog(uint32 sender_ssrc); - void AddReceiverFrameLog(uint32 rtp_timestamp, int num_events, + void AddReceiverFrameLog(uint32 rtp_timestamp, + int num_events, uint32 event_timesamp_base); - void AddReceiverEventLog(uint16 event_data, uint8 event_id, + void AddReceiverEventLog(uint16 event_data, + CastLoggingEvent event, uint16 event_timesamp_delta); - const uint8* Packet(); - int Length() { return kIpPacketSize - big_endian_writer_.remaining(); } + scoped_ptr<Packet> GetPacket(); + const uint8* Data(); + int Length() { return kMaxIpPacketSize - big_endian_writer_.remaining(); } private: void AddRtcpHeader(int payload, int format_or_count); @@ -94,12 +98,14 @@ class TestRtcpPacketBuilder { // Where the length field of the current packet is. // Note: 0 is not a legal value, it is used for "uninitialized". - uint8 buffer_[kIpPacketSize]; + uint8 buffer_[kMaxIpPacketSize]; char* ptr_of_length_; - net::BigEndianWriter big_endian_writer_; + base::BigEndianWriter big_endian_writer_; + + DISALLOW_COPY_AND_ASSIGN(TestRtcpPacketBuilder); }; } // namespace cast } // namespace media -#endif // MEDIA_CAST_RTCP_TEST_RTCP_PACKET_BUILDER_H_ +#endif // MEDIA_CAST_RTCP_TEST_RTCP_PACKET_BUILDER_H_ diff --git a/chromium/media/cast/rtp_receiver/mock_rtp_payload_feedback.cc b/chromium/media/cast/rtp_receiver/mock_rtp_payload_feedback.cc index 8681d087aa3..02b6c0be454 100644 --- a/chromium/media/cast/rtp_receiver/mock_rtp_payload_feedback.cc +++ b/chromium/media/cast/rtp_receiver/mock_rtp_payload_feedback.cc @@ -7,11 +7,9 @@ namespace media { namespace cast { -MockRtpPayloadFeedback::MockRtpPayloadFeedback() { -} +MockRtpPayloadFeedback::MockRtpPayloadFeedback() {} -MockRtpPayloadFeedback::~MockRtpPayloadFeedback() { -} +MockRtpPayloadFeedback::~MockRtpPayloadFeedback() {} } // namespace cast } // namespace media diff --git a/chromium/media/cast/rtp_receiver/mock_rtp_payload_feedback.h b/chromium/media/cast/rtp_receiver/mock_rtp_payload_feedback.h index 003b67bc0da..14e48673bd3 100644 --- a/chromium/media/cast/rtp_receiver/mock_rtp_payload_feedback.h +++ b/chromium/media/cast/rtp_receiver/mock_rtp_payload_feedback.h @@ -16,8 +16,7 @@ class MockRtpPayloadFeedback : public RtpPayloadFeedback { MockRtpPayloadFeedback(); virtual ~MockRtpPayloadFeedback(); - MOCK_METHOD1(CastFeedback, - void(const RtcpCastMessage& cast_feedback)); + MOCK_METHOD1(CastFeedback, void(const RtcpCastMessage& cast_feedback)); }; } // namespace cast diff --git a/chromium/media/cast/rtp_receiver/receiver_stats.cc b/chromium/media/cast/rtp_receiver/receiver_stats.cc index 9d34583a769..7eff86763f8 100644 --- a/chromium/media/cast/rtp_receiver/receiver_stats.cc +++ b/chromium/media/cast/rtp_receiver/receiver_stats.cc @@ -37,15 +37,16 @@ void ReceiverStats::GetStatistics(uint8* fraction_lost, diff = max_sequence_number_ - interval_min_sequence_number_ + 1; } else { diff = kMaxSequenceNumber * (interval_wrap_count_ - 1) + - (max_sequence_number_ - interval_min_sequence_number_ + - kMaxSequenceNumber + 1); + (max_sequence_number_ - interval_min_sequence_number_ + + kMaxSequenceNumber + 1); } if (diff < 1) { *fraction_lost = 0; } else { - *fraction_lost = static_cast<uint8>((256 * (1 - - static_cast<float>(interval_number_packets_) / abs(diff)))); + float tmp_ratio = + (1 - static_cast<float>(interval_number_packets_) / abs(diff)); + *fraction_lost = static_cast<uint8>(256 * tmp_ratio); } } @@ -55,16 +56,17 @@ void ReceiverStats::GetStatistics(uint8* fraction_lost, } else if (sequence_number_cycles_ == 0) { *cumulative_lost = expected_packets_num - total_number_packets_; } else { - *cumulative_lost = kMaxSequenceNumber * (sequence_number_cycles_ - 1) + + *cumulative_lost = + kMaxSequenceNumber * (sequence_number_cycles_ - 1) + (expected_packets_num - total_number_packets_ + kMaxSequenceNumber); } // Extended high sequence number consists of the highest seq number and the // number of cycles (wrap). - *extended_high_sequence_number = (sequence_number_cycles_ << 16) + - max_sequence_number_; + *extended_high_sequence_number = + (sequence_number_cycles_ << 16) + max_sequence_number_; - *jitter = static_cast<uint32>(abs(jitter_.InMillisecondsRoundedUp())); + *jitter = static_cast<uint32>(std::abs(jitter_.InMillisecondsRoundedUp())); // Reset interval values. interval_min_sequence_number_ = 0; @@ -73,7 +75,7 @@ void ReceiverStats::GetStatistics(uint8* fraction_lost, } void ReceiverStats::UpdateStatistics(const RtpCastHeader& header) { - uint16 new_seq_num = header.webrtc.header.sequenceNumber; + const uint16 new_seq_num = header.sequence_number; if (interval_number_packets_ == 0) { // First packet in the interval. @@ -97,10 +99,11 @@ void ReceiverStats::UpdateStatistics(const RtpCastHeader& header) { // Compute Jitter. base::TimeTicks now = clock_->NowTicks(); base::TimeDelta delta_new_timestamp = - base::TimeDelta::FromMilliseconds(header.webrtc.header.timestamp); + base::TimeDelta::FromMilliseconds(header.rtp_timestamp); if (total_number_packets_ > 0) { // Update jitter. - base::TimeDelta delta = (now - last_received_packet_time_) - + base::TimeDelta delta = + (now - last_received_packet_time_) - ((delta_new_timestamp - last_received_timestamp_) / 90); jitter_ += (delta - jitter_) / 16; } diff --git a/chromium/media/cast/rtp_receiver/receiver_stats.h b/chromium/media/cast/rtp_receiver/receiver_stats.h index c91ee507e0c..05a067f7870 100644 --- a/chromium/media/cast/rtp_receiver/receiver_stats.h +++ b/chromium/media/cast/rtp_receiver/receiver_stats.h @@ -7,20 +7,21 @@ #include "base/time/tick_clock.h" #include "base/time/time.h" +#include "media/cast/rtcp/rtcp.h" #include "media/cast/rtp_receiver/rtp_receiver_defines.h" namespace media { namespace cast { -class ReceiverStats { +class ReceiverStats : public RtpReceiverStatistics { public: explicit ReceiverStats(base::TickClock* clock); - ~ReceiverStats(); + virtual ~ReceiverStats() OVERRIDE; - void GetStatistics(uint8* fraction_lost, - uint32* cumulative_lost, // 24 bits valid. - uint32* extended_high_sequence_number, - uint32* jitter); + virtual void GetStatistics(uint8* fraction_lost, + uint32* cumulative_lost, // 24 bits valid. + uint32* extended_high_sequence_number, + uint32* jitter) OVERRIDE; void UpdateStatistics(const RtpCastHeader& header); private: @@ -39,6 +40,8 @@ class ReceiverStats { int interval_min_sequence_number_; int interval_number_packets_; int interval_wrap_count_; + + DISALLOW_COPY_AND_ASSIGN(ReceiverStats); }; } // namespace cast diff --git a/chromium/media/cast/rtp_receiver/receiver_stats_unittest.cc b/chromium/media/cast/rtp_receiver/receiver_stats_unittest.cc index 2788cb592de..98059cdde71 100644 --- a/chromium/media/cast/rtp_receiver/receiver_stats_unittest.cc +++ b/chromium/media/cast/rtp_receiver/receiver_stats_unittest.cc @@ -4,6 +4,8 @@ #include <gtest/gtest.h> +#include <stdint.h> + #include "base/test/simple_test_tick_clock.h" #include "base/time/time.h" #include "media/cast/rtp_receiver/receiver_stats.h" @@ -12,30 +14,24 @@ namespace media { namespace cast { -static const int64 kStartMillisecond = GG_INT64_C(12345678900000); +static const int64 kStartMillisecond = INT64_C(12345678900000); static const uint32 kStdTimeIncrementMs = 33; class ReceiverStatsTest : public ::testing::Test { protected: ReceiverStatsTest() : stats_(&testing_clock_), - rtp_header_(), fraction_lost_(0), cumulative_lost_(0), extended_high_sequence_number_(0), jitter_(0) { testing_clock_.Advance( base::TimeDelta::FromMilliseconds(kStartMillisecond)); - start_time_ = testing_clock_.NowTicks(); + start_time_ = testing_clock_.NowTicks(); delta_increments_ = base::TimeDelta::FromMilliseconds(kStdTimeIncrementMs); } virtual ~ReceiverStatsTest() {} - virtual void SetUp() { - rtp_header_.webrtc.header.sequenceNumber = 0; - rtp_header_.webrtc.header.timestamp = 0; - } - uint32 ExpectedJitter(uint32 const_interval, int num_packets) { float jitter = 0; // Assume timestamps have a constant kStdTimeIncrementMs interval. @@ -56,11 +52,15 @@ class ReceiverStatsTest : public ::testing::Test { base::SimpleTestTickClock testing_clock_; base::TimeTicks start_time_; base::TimeDelta delta_increments_; + + DISALLOW_COPY_AND_ASSIGN(ReceiverStatsTest); }; TEST_F(ReceiverStatsTest, ResetState) { - stats_.GetStatistics(&fraction_lost_, &cumulative_lost_, - &extended_high_sequence_number_, &jitter_); + stats_.GetStatistics(&fraction_lost_, + &cumulative_lost_, + &extended_high_sequence_number_, + &jitter_); EXPECT_EQ(0u, fraction_lost_); EXPECT_EQ(0u, cumulative_lost_); EXPECT_EQ(0u, extended_high_sequence_number_); @@ -72,75 +72,81 @@ TEST_F(ReceiverStatsTest, LossCount) { if (i % 4) stats_.UpdateStatistics(rtp_header_); if (i % 3) { - rtp_header_.webrtc.header.timestamp += 33 * 90; + rtp_header_.rtp_timestamp += 33 * 90; } - ++rtp_header_.webrtc.header.sequenceNumber; + ++rtp_header_.sequence_number; testing_clock_.Advance(delta_increments_); } - stats_.GetStatistics(&fraction_lost_, &cumulative_lost_, - &extended_high_sequence_number_, &jitter_); + stats_.GetStatistics(&fraction_lost_, + &cumulative_lost_, + &extended_high_sequence_number_, + &jitter_); EXPECT_EQ(63u, fraction_lost_); EXPECT_EQ(74u, cumulative_lost_); // Build extended sequence number. - uint32 extended_seq_num = rtp_header_.webrtc.header.sequenceNumber - 1; + const uint32 extended_seq_num = rtp_header_.sequence_number - 1; EXPECT_EQ(extended_seq_num, extended_high_sequence_number_); } TEST_F(ReceiverStatsTest, NoLossWrap) { - rtp_header_.webrtc.header.sequenceNumber = 65500; + rtp_header_.sequence_number = 65500; for (int i = 0; i < 300; ++i) { - stats_.UpdateStatistics(rtp_header_); + stats_.UpdateStatistics(rtp_header_); if (i % 3) { - rtp_header_.webrtc.header.timestamp += 33 * 90; + rtp_header_.rtp_timestamp += 33 * 90; } - ++rtp_header_.webrtc.header.sequenceNumber; + ++rtp_header_.sequence_number; testing_clock_.Advance(delta_increments_); } - stats_.GetStatistics(&fraction_lost_, &cumulative_lost_, - &extended_high_sequence_number_, &jitter_); + stats_.GetStatistics(&fraction_lost_, + &cumulative_lost_, + &extended_high_sequence_number_, + &jitter_); EXPECT_EQ(0u, fraction_lost_); EXPECT_EQ(0u, cumulative_lost_); // Build extended sequence number (one wrap cycle). - uint32 extended_seq_num = (1 << 16) + - rtp_header_.webrtc.header.sequenceNumber - 1; + const uint32 extended_seq_num = (1 << 16) + rtp_header_.sequence_number - 1; EXPECT_EQ(extended_seq_num, extended_high_sequence_number_); } TEST_F(ReceiverStatsTest, LossCountWrap) { - const uint32 start_sequence_number = 65500; - rtp_header_.webrtc.header.sequenceNumber = start_sequence_number; + const uint32 kStartSequenceNumber = 65500; + rtp_header_.sequence_number = kStartSequenceNumber; for (int i = 0; i < 300; ++i) { if (i % 4) stats_.UpdateStatistics(rtp_header_); if (i % 3) // Update timestamp. - ++rtp_header_.webrtc.header.timestamp; - ++rtp_header_.webrtc.header.sequenceNumber; + ++rtp_header_.rtp_timestamp; + ++rtp_header_.sequence_number; testing_clock_.Advance(delta_increments_); } - stats_.GetStatistics(&fraction_lost_, &cumulative_lost_, - &extended_high_sequence_number_, &jitter_); + stats_.GetStatistics(&fraction_lost_, + &cumulative_lost_, + &extended_high_sequence_number_, + &jitter_); EXPECT_EQ(63u, fraction_lost_); EXPECT_EQ(74u, cumulative_lost_); // Build extended sequence number (one wrap cycle). - uint32 extended_seq_num = (1 << 16) + - rtp_header_.webrtc.header.sequenceNumber - 1; + const uint32 extended_seq_num = (1 << 16) + rtp_header_.sequence_number - 1; EXPECT_EQ(extended_seq_num, extended_high_sequence_number_); } TEST_F(ReceiverStatsTest, BasicJitter) { for (int i = 0; i < 300; ++i) { stats_.UpdateStatistics(rtp_header_); - ++rtp_header_.webrtc.header.sequenceNumber; - rtp_header_.webrtc.header.timestamp += 33 * 90; + ++rtp_header_.sequence_number; + rtp_header_.rtp_timestamp += 33 * 90; testing_clock_.Advance(delta_increments_); } - stats_.GetStatistics(&fraction_lost_, &cumulative_lost_, - &extended_high_sequence_number_, &jitter_); + stats_.GetStatistics(&fraction_lost_, + &cumulative_lost_, + &extended_high_sequence_number_, + &jitter_); EXPECT_FALSE(fraction_lost_); EXPECT_FALSE(cumulative_lost_); // Build extended sequence number (one wrap cycle). - uint32 extended_seq_num = rtp_header_.webrtc.header.sequenceNumber - 1; + const uint32 extended_seq_num = rtp_header_.sequence_number - 1; EXPECT_EQ(extended_seq_num, extended_high_sequence_number_); EXPECT_EQ(ExpectedJitter(kStdTimeIncrementMs, 300), jitter_); } @@ -149,21 +155,23 @@ TEST_F(ReceiverStatsTest, NonTrivialJitter) { const int kAdditionalIncrement = 5; for (int i = 0; i < 300; ++i) { stats_.UpdateStatistics(rtp_header_); - ++rtp_header_.webrtc.header.sequenceNumber; - rtp_header_.webrtc.header.timestamp += 33 * 90; + ++rtp_header_.sequence_number; + rtp_header_.rtp_timestamp += 33 * 90; base::TimeDelta additional_delta = base::TimeDelta::FromMilliseconds(kAdditionalIncrement); testing_clock_.Advance(delta_increments_ + additional_delta); } - stats_.GetStatistics(&fraction_lost_, &cumulative_lost_, - &extended_high_sequence_number_, &jitter_); + stats_.GetStatistics(&fraction_lost_, + &cumulative_lost_, + &extended_high_sequence_number_, + &jitter_); EXPECT_FALSE(fraction_lost_); EXPECT_FALSE(cumulative_lost_); // Build extended sequence number (one wrap cycle). - uint32 extended_seq_num = rtp_header_.webrtc.header.sequenceNumber - 1; + const uint32 extended_seq_num = rtp_header_.sequence_number - 1; EXPECT_EQ(extended_seq_num, extended_high_sequence_number_); - EXPECT_EQ( - ExpectedJitter(kStdTimeIncrementMs + kAdditionalIncrement, 300), jitter_); + EXPECT_EQ(ExpectedJitter(kStdTimeIncrementMs + kAdditionalIncrement, 300), + jitter_); } } // namespace cast diff --git a/chromium/media/cast/rtp_receiver/rtp_parser/include/mock/mock_rtp_feedback.h b/chromium/media/cast/rtp_receiver/rtp_parser/include/mock/mock_rtp_feedback.h index b6647a835be..f5edf7c43fe 100644 --- a/chromium/media/cast/rtp_receiver/rtp_parser/include/mock/mock_rtp_feedback.h +++ b/chromium/media/cast/rtp_receiver/rtp_parser/include/mock/mock_rtp_feedback.h @@ -14,19 +14,17 @@ namespace cast { class MockRtpFeedback : public RtpFeedback { public: MOCK_METHOD4(OnInitializeDecoder, - int32(const int8 payloadType, - const int frequency, - const uint8 channels, - const uint32 rate)); + int32(const int8 payloadType, + const int frequency, + const uint8 channels, + const uint32 rate)); - MOCK_METHOD1(OnPacketTimeout, - void(const int32 id)); + MOCK_METHOD1(OnPacketTimeout, void(const int32 id)); MOCK_METHOD2(OnReceivedPacket, - void(const int32 id, const RtpRtcpPacketType packet_type)); + void(const int32 id, const RtpRtcpPacketField packet_type)); MOCK_METHOD2(OnPeriodicDeadOrAlive, void(const int32 id, const RTPAliveType alive)); - MOCK_METHOD2(OnIncomingSSRCChanged, - void(const int32 id, const uint32 ssrc)); + MOCK_METHOD2(OnIncomingSSRCChanged, void(const int32 id, const uint32 ssrc)); MOCK_METHOD3(OnIncomingCSRCChanged, void(const int32 id, const uint32 csrc, const bool added)); }; diff --git a/chromium/media/cast/rtp_receiver/rtp_parser/rtp_parser.cc b/chromium/media/cast/rtp_receiver/rtp_parser/rtp_parser.cc index 6ef20fe64e3..f44e82dac2b 100644 --- a/chromium/media/cast/rtp_receiver/rtp_parser/rtp_parser.cc +++ b/chromium/media/cast/rtp_receiver/rtp_parser/rtp_parser.cc @@ -4,104 +4,113 @@ #include "media/cast/rtp_receiver/rtp_parser/rtp_parser.h" +#include "base/big_endian.h" #include "base/logging.h" #include "media/cast/cast_defines.h" -#include "media/cast/rtp_receiver/rtp_receiver.h" -#include "net/base/big_endian.h" namespace media { namespace cast { -static const size_t kRtpCommonHeaderLength = 12; -static const size_t kRtpCastHeaderLength = 7; +static const size_t kRtpHeaderLength = 12; +static const size_t kCastHeaderLength = 7; +static const uint8 kRtpExtensionBitMask = 0x10; +static const uint8 kRtpMarkerBitMask = 0x80; static const uint8 kCastKeyFrameBitMask = 0x80; static const uint8 kCastReferenceFrameIdBitMask = 0x40; -RtpParser::RtpParser(RtpData* incoming_payload_callback, - const RtpParserConfig parser_config) - : data_callback_(incoming_payload_callback), - parser_config_(parser_config) {} +RtpParser::RtpParser(uint32 expected_sender_ssrc, uint8 expected_payload_type) + : expected_sender_ssrc_(expected_sender_ssrc), + expected_payload_type_(expected_payload_type) {} RtpParser::~RtpParser() {} -bool RtpParser::ParsePacket(const uint8* packet, size_t length, - RtpCastHeader* rtp_header) { - if (length == 0) return false; - // Get RTP general header. - if (!ParseCommon(packet, length, rtp_header)) return false; - if (rtp_header->webrtc.header.payloadType == parser_config_.payload_type && - rtp_header->webrtc.header.ssrc == parser_config_.ssrc) { - return ParseCast(packet + kRtpCommonHeaderLength, - length - kRtpCommonHeaderLength, rtp_header); - } - // Not a valid payload type / ssrc combination. - return false; -} - -bool RtpParser::ParseCommon(const uint8* packet, +bool RtpParser::ParsePacket(const uint8* packet, size_t length, - RtpCastHeader* rtp_header) { - if (length < kRtpCommonHeaderLength) return false; - uint8 version = packet[0] >> 6; - if (version != 2) return false; - uint8 cc = packet[0] & 0x0f; - bool marker = ((packet[1] & 0x80) != 0); - int payload_type = packet[1] & 0x7f; - - uint16 sequence_number; - uint32 rtp_timestamp, ssrc; - net::BigEndianReader big_endian_reader(packet + 2, 10); - big_endian_reader.ReadU16(&sequence_number); - big_endian_reader.ReadU32(&rtp_timestamp); - big_endian_reader.ReadU32(&ssrc); - - if (ssrc != parser_config_.ssrc) return false; - - rtp_header->webrtc.header.markerBit = marker; - rtp_header->webrtc.header.payloadType = payload_type; - rtp_header->webrtc.header.sequenceNumber = sequence_number; - rtp_header->webrtc.header.timestamp = rtp_timestamp; - rtp_header->webrtc.header.ssrc = ssrc; - rtp_header->webrtc.header.numCSRCs = cc; - - uint8 csrc_octs = cc * 4; - rtp_header->webrtc.type.Audio.numEnergy = rtp_header->webrtc.header.numCSRCs; - rtp_header->webrtc.header.headerLength = kRtpCommonHeaderLength + csrc_octs; - rtp_header->webrtc.type.Audio.isCNG = false; - rtp_header->webrtc.type.Audio.channel = parser_config_.audio_channels; - // TODO(pwestin): look at x bit and skip data. - return true; -} - -bool RtpParser::ParseCast(const uint8* packet, - size_t length, - RtpCastHeader* rtp_header) { - if (length < kRtpCastHeaderLength) return false; - - // Extract header. - const uint8* data_ptr = packet; - size_t data_length = length; - rtp_header->is_key_frame = (data_ptr[0] & kCastKeyFrameBitMask); - rtp_header->is_reference = (data_ptr[0] & kCastReferenceFrameIdBitMask); - rtp_header->frame_id = frame_id_wrap_helper_.MapTo32bitsFrameId(data_ptr[1]); - - net::BigEndianReader big_endian_reader(data_ptr + 2, 4); - big_endian_reader.ReadU16(&rtp_header->packet_id); - big_endian_reader.ReadU16(&rtp_header->max_packet_id); - - if (rtp_header->is_reference) { - rtp_header->reference_frame_id = - reference_frame_id_wrap_helper_.MapTo32bitsFrameId(data_ptr[6]); - data_ptr += kRtpCastHeaderLength; - data_length -= kRtpCastHeaderLength; - } else { - data_ptr += kRtpCastHeaderLength - 1; - data_length -= kRtpCastHeaderLength - 1; + RtpCastHeader* header, + const uint8** payload_data, + size_t* payload_size) { + DCHECK(packet); + DCHECK(header); + DCHECK(payload_data); + DCHECK(payload_size); + + if (length < (kRtpHeaderLength + kCastHeaderLength)) + return false; + + base::BigEndianReader reader(reinterpret_cast<const char*>(packet), length); + + // Parse the RTP header. See + // http://en.wikipedia.org/wiki/Real-time_Transport_Protocol for an + // explanation of the standard RTP packet header. + uint8 bits; + if (!reader.ReadU8(&bits)) + return false; + const uint8 version = bits >> 6; + if (version != 2) + return false; + if (bits & kRtpExtensionBitMask) + return false; // We lack the implementation to skip over an extension. + if (!reader.ReadU8(&bits)) + return false; + header->marker = !!(bits & kRtpMarkerBitMask); + header->payload_type = bits & ~kRtpMarkerBitMask; + if (header->payload_type != expected_payload_type_) + return false; // Punt: Unexpected payload type. + if (!reader.ReadU16(&header->sequence_number) || + !reader.ReadU32(&header->rtp_timestamp) || + !reader.ReadU32(&header->sender_ssrc)) { + return false; + } + if (header->sender_ssrc != expected_sender_ssrc_) + return false; // Punt: Sender's SSRC does not match the expected one. + + // Parse the Cast header. Note that, from the RTP protocol's perspective, the + // Cast header is part of the payload (and not meant to be an extension + // header). + if (!reader.ReadU8(&bits)) + return false; + header->is_key_frame = !!(bits & kCastKeyFrameBitMask); + const bool includes_specific_frame_reference = + !!(bits & kCastReferenceFrameIdBitMask); + uint8 truncated_frame_id; + if (!reader.ReadU8(&truncated_frame_id) || + !reader.ReadU16(&header->packet_id) || + !reader.ReadU16(&header->max_packet_id)) { + return false; + } + // Sanity-check: Do the packet ID values make sense w.r.t. each other? + if (header->max_packet_id < header->packet_id) + return false; + uint8 truncated_reference_frame_id; + if (!includes_specific_frame_reference) { + // By default, a key frame only references itself; and non-key frames + // reference their direct predecessor. + truncated_reference_frame_id = truncated_frame_id; + if (!header->is_key_frame) + --truncated_reference_frame_id; + } else if (!reader.ReadU8(&truncated_reference_frame_id)) { + return false; } - if (rtp_header->max_packet_id < rtp_header->packet_id) return false; + // Only the lower 8 bits of the |frame_id| were serialized, so do some magic + // to restore the upper 24 bits. + // + // Note: The call to |frame_id_wrap_helper_| has side effects, so we must not + // call it until we know the entire deserialization will succeed. + header->frame_id = + frame_id_wrap_helper_.MapTo32bitsFrameId(truncated_frame_id); + // When the upper 24 bits are restored to |reference_frame_id|, make sure + // |reference_frame_id| will be strictly less than or equal to |frame_id|. + if (truncated_reference_frame_id <= truncated_frame_id) + header->reference_frame_id = header->frame_id & 0xffffff00; + else + header->reference_frame_id = (header->frame_id & 0xffffff00) - 0x00000100; + header->reference_frame_id |= truncated_reference_frame_id; + + // All remaining data in the packet is the payload. + *payload_data = reinterpret_cast<const uint8*>(reader.ptr()); + *payload_size = reader.remaining(); - data_callback_->OnReceivedPayloadData(data_ptr, data_length, rtp_header); return true; } diff --git a/chromium/media/cast/rtp_receiver/rtp_parser/rtp_parser.gyp b/chromium/media/cast/rtp_receiver/rtp_parser/rtp_parser.gyp deleted file mode 100644 index 258b0bff532..00000000000 --- a/chromium/media/cast/rtp_receiver/rtp_parser/rtp_parser.gyp +++ /dev/null @@ -1,23 +0,0 @@ -# Copyright 2013 The Chromium Authors. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - -{ - 'targets': [ - { - 'target_name': 'cast_rtp_parser', - 'type': 'static_library', - 'include_dirs': [ - '<(DEPTH)/', - '<(DEPTH)/third_party/', - ], - 'sources': [ - 'rtp_parser.cc', - 'rtp_parser.h', - ], # source - 'dependencies': [ - '<(DEPTH)/base/base.gyp:base', - ], - }, - ], -} diff --git a/chromium/media/cast/rtp_receiver/rtp_parser/rtp_parser.h b/chromium/media/cast/rtp_receiver/rtp_parser/rtp_parser.h index 33bc92a6e6e..35118cf1446 100644 --- a/chromium/media/cast/rtp_receiver/rtp_parser/rtp_parser.h +++ b/chromium/media/cast/rtp_receiver/rtp_parser/rtp_parser.h @@ -5,49 +5,40 @@ #ifndef MEDIA_CAST_RTP_RECEIVER_RTP_PARSER_RTP_PARSER_H_ #define MEDIA_CAST_RTP_RECEIVER_RTP_PARSER_RTP_PARSER_H_ -#include "media/cast/net/cast_net_defines.h" #include "media/cast/rtp_receiver/rtp_receiver_defines.h" +#include "media/cast/transport/cast_transport_defines.h" namespace media { namespace cast { -class RtpData; - -struct RtpParserConfig { - RtpParserConfig() { - ssrc = 0; - payload_type = 0; - audio_channels = 0; - } - - uint32 ssrc; - int payload_type; - AudioCodec audio_codec; - VideoCodec video_codec; - int audio_channels; -}; - +// TODO(miu): RtpParser and RtpPacketizer should be consolidated into a single +// module that handles all RTP/Cast packet serialization and deserialization +// throughout the media/cast library. class RtpParser { public: - RtpParser(RtpData* incoming_payload_callback, - const RtpParserConfig parser_config); - - ~RtpParser(); - - bool ParsePacket(const uint8* packet, size_t length, - RtpCastHeader* rtp_header); + RtpParser(uint32 expected_sender_ssrc, uint8 expected_payload_type); + + virtual ~RtpParser(); + + // Parses the |packet|, expecting an RTP header along with a Cast header at + // the beginning of the the RTP payload. This method populates the structure + // pointed to by |rtp_header| and sets the |payload_data| pointer and + // |payload_size| to the memory region within |packet| containing the Cast + // payload data. Returns false if the data appears to be invalid, is not from + // the expected sender (as identified by the SSRC field), or is not the + // expected payload type. + bool ParsePacket(const uint8* packet, + size_t length, + RtpCastHeader* rtp_header, + const uint8** payload_data, + size_t* payload_size); private: - bool ParseCommon(const uint8* packet, size_t length, - RtpCastHeader* rtp_header); - - bool ParseCast(const uint8* packet, size_t length, - RtpCastHeader* rtp_header); + const uint32 expected_sender_ssrc_; + const uint8 expected_payload_type_; + transport::FrameIdWrapHelper frame_id_wrap_helper_; - RtpData* data_callback_; - RtpParserConfig parser_config_; - FrameIdWrapHelper frame_id_wrap_helper_; - FrameIdWrapHelper reference_frame_id_wrap_helper_; + DISALLOW_COPY_AND_ASSIGN(RtpParser); }; } // namespace cast diff --git a/chromium/media/cast/rtp_receiver/rtp_parser/rtp_parser_unittest.cc b/chromium/media/cast/rtp_receiver/rtp_parser/rtp_parser_unittest.cc index c0f91d10fff..47c79139ffc 100644 --- a/chromium/media/cast/rtp_receiver/rtp_parser/rtp_parser_unittest.cc +++ b/chromium/media/cast/rtp_receiver/rtp_parser/rtp_parser_unittest.cc @@ -2,13 +2,12 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include <gtest/gtest.h> - #include "base/memory/scoped_ptr.h" +#include "base/rand_util.h" #include "media/cast/rtp_receiver/rtp_parser/rtp_parser.h" #include "media/cast/rtp_receiver/rtp_parser/test/rtp_packet_builder.h" -#include "media/cast/rtp_receiver/rtp_receiver.h" #include "media/cast/rtp_receiver/rtp_receiver_defines.h" +#include "testing/gtest/include/gtest/gtest.h" namespace media { namespace cast { @@ -20,178 +19,178 @@ static const uint32 kTestTimestamp = 111111; static const uint16 kTestSeqNum = 4321; static const uint8 kRefFrameId = 17; -class RtpDataTest : public RtpData { - public: - RtpDataTest() { - expected_header_.reset(new RtpCastHeader()); - } - - virtual ~RtpDataTest() {} - - void SetExpectedHeader(const RtpCastHeader& cast_header) { - memcpy(expected_header_.get(), &cast_header, sizeof(RtpCastHeader)); - } - - virtual void OnReceivedPayloadData(const uint8* payloadData, - size_t payloadSize, - const RtpCastHeader* rtpHeader) OVERRIDE { - VerifyCommonHeader(*rtpHeader); - VerifyCastHeader(*rtpHeader); - } - - void VerifyCommonHeader(const RtpCastHeader& parsed_header) { - EXPECT_EQ(expected_header_->packet_id == expected_header_->max_packet_id, - parsed_header.webrtc.header.markerBit); - EXPECT_EQ(kTestPayloadType, parsed_header.webrtc.header.payloadType); - EXPECT_EQ(kTestSsrc, parsed_header.webrtc.header.ssrc); - EXPECT_EQ(0, parsed_header.webrtc.header.numCSRCs); - } - - void VerifyCastHeader(const RtpCastHeader& parsed_header) { - EXPECT_EQ(expected_header_->is_key_frame, parsed_header.is_key_frame); - EXPECT_EQ(expected_header_->frame_id, parsed_header.frame_id); - EXPECT_EQ(expected_header_->packet_id, parsed_header.packet_id); - EXPECT_EQ(expected_header_->max_packet_id, parsed_header.max_packet_id); - EXPECT_EQ(expected_header_->is_reference, parsed_header.is_reference); - } - - private: - scoped_ptr<RtpCastHeader> expected_header_; -}; - class RtpParserTest : public ::testing::Test { protected: - RtpParserTest() { - PopulateConfig(); - rtp_data_.reset(new RtpDataTest()); - rtp_parser_.reset(new RtpParser(rtp_data_.get(), config_)); - } - - virtual ~RtpParserTest() {} - - virtual void SetUp() { - cast_header_.is_reference = true; - cast_header_.reference_frame_id = kRefFrameId; + RtpParserTest() : rtp_parser_(kTestSsrc, kTestPayloadType) { packet_builder_.SetSsrc(kTestSsrc); - packet_builder_.SetReferenceFrameId(kRefFrameId, true); packet_builder_.SetSequenceNumber(kTestSeqNum); packet_builder_.SetTimestamp(kTestTimestamp); packet_builder_.SetPayloadType(kTestPayloadType); packet_builder_.SetMarkerBit(true); // Only one packet. + cast_header_.sender_ssrc = kTestSsrc; + cast_header_.sequence_number = kTestSeqNum; + cast_header_.rtp_timestamp = kTestTimestamp; + cast_header_.payload_type = kTestPayloadType; + cast_header_.marker = true; + } + + virtual ~RtpParserTest() {} + + void ExpectParsesPacket() { + RtpCastHeader parsed_header; + const uint8* payload = NULL; + size_t payload_size = -1; + EXPECT_TRUE(rtp_parser_.ParsePacket( + packet_, kPacketLength, &parsed_header, &payload, &payload_size)); + + EXPECT_EQ(cast_header_.marker, parsed_header.marker); + EXPECT_EQ(cast_header_.payload_type, parsed_header.payload_type); + EXPECT_EQ(cast_header_.sequence_number, parsed_header.sequence_number); + EXPECT_EQ(cast_header_.rtp_timestamp, parsed_header.rtp_timestamp); + EXPECT_EQ(cast_header_.sender_ssrc, parsed_header.sender_ssrc); + + EXPECT_EQ(cast_header_.is_key_frame, parsed_header.is_key_frame); + EXPECT_EQ(cast_header_.frame_id, parsed_header.frame_id); + EXPECT_EQ(cast_header_.packet_id, parsed_header.packet_id); + EXPECT_EQ(cast_header_.max_packet_id, parsed_header.max_packet_id); + EXPECT_EQ(cast_header_.reference_frame_id, + parsed_header.reference_frame_id); + + EXPECT_TRUE(!!payload); + EXPECT_NE(static_cast<size_t>(-1), payload_size); } - void PopulateConfig() { - config_.payload_type = kTestPayloadType; - config_.ssrc = kTestSsrc; + void ExpectDoesNotParsePacket() { + RtpCastHeader parsed_header; + const uint8* payload = NULL; + size_t payload_size = -1; + EXPECT_FALSE(rtp_parser_.ParsePacket( + packet_, kPacketLength, &parsed_header, &payload, &payload_size)); } - scoped_ptr<RtpDataTest> rtp_data_; RtpPacketBuilder packet_builder_; - scoped_ptr<RtpParser> rtp_parser_; - RtpParserConfig config_; + uint8 packet_[kPacketLength]; + RtpParser rtp_parser_; RtpCastHeader cast_header_; }; TEST_F(RtpParserTest, ParseDefaultCastPacket) { - // Build generic data packet. - uint8 packet[kPacketLength]; - packet_builder_.BuildHeader(packet, kPacketLength); - // Parse packet as is. - RtpCastHeader rtp_header; - rtp_data_->SetExpectedHeader(cast_header_); - EXPECT_TRUE(rtp_parser_->ParsePacket(packet, kPacketLength, &rtp_header)); + packet_builder_.BuildHeader(packet_, kPacketLength); + ExpectParsesPacket(); } TEST_F(RtpParserTest, ParseNonDefaultCastPacket) { - // Build generic data packet. - uint8 packet[kPacketLength]; packet_builder_.SetKeyFrame(true); - packet_builder_.SetFrameId(10); + packet_builder_.SetFrameIds(10, 10); packet_builder_.SetPacketId(5); packet_builder_.SetMaxPacketId(15); packet_builder_.SetMarkerBit(false); - packet_builder_.BuildHeader(packet, kPacketLength); + packet_builder_.BuildHeader(packet_, kPacketLength); cast_header_.is_key_frame = true; cast_header_.frame_id = 10; + cast_header_.reference_frame_id = 10; cast_header_.packet_id = 5; cast_header_.max_packet_id = 15; - rtp_data_->SetExpectedHeader(cast_header_); - // Parse packet as is. - RtpCastHeader rtp_header; - EXPECT_TRUE(rtp_parser_->ParsePacket(packet, kPacketLength, &rtp_header)); + cast_header_.marker = false; + ExpectParsesPacket(); } TEST_F(RtpParserTest, TooBigPacketId) { - // Build generic data packet. - uint8 packet[kPacketLength]; packet_builder_.SetKeyFrame(true); - packet_builder_.SetFrameId(10); + packet_builder_.SetFrameIds(10, 10); packet_builder_.SetPacketId(15); packet_builder_.SetMaxPacketId(5); - packet_builder_.BuildHeader(packet, kPacketLength); - // Parse packet as is. - RtpCastHeader rtp_header; - EXPECT_FALSE(rtp_parser_->ParsePacket(packet, kPacketLength, &rtp_header)); + packet_builder_.BuildHeader(packet_, kPacketLength); + cast_header_.is_key_frame = true; + cast_header_.frame_id = 10; + cast_header_.reference_frame_id = 10; + cast_header_.packet_id = 15; + cast_header_.max_packet_id = 5; + ExpectDoesNotParsePacket(); } TEST_F(RtpParserTest, MaxPacketId) { - // Build generic data packet. - uint8 packet[kPacketLength]; packet_builder_.SetKeyFrame(true); - packet_builder_.SetFrameId(10); + packet_builder_.SetFrameIds(10, 10); packet_builder_.SetPacketId(65535); packet_builder_.SetMaxPacketId(65535); - packet_builder_.BuildHeader(packet, kPacketLength); + packet_builder_.BuildHeader(packet_, kPacketLength); cast_header_.is_key_frame = true; cast_header_.frame_id = 10; + cast_header_.reference_frame_id = 10; cast_header_.packet_id = 65535; cast_header_.max_packet_id = 65535; - rtp_data_->SetExpectedHeader(cast_header_); - // Parse packet as is. - RtpCastHeader rtp_header; - EXPECT_TRUE(rtp_parser_->ParsePacket(packet, kPacketLength, &rtp_header)); + ExpectParsesPacket(); } TEST_F(RtpParserTest, InvalidPayloadType) { - // Build generic data packet. - uint8 packet[kPacketLength]; packet_builder_.SetKeyFrame(true); - packet_builder_.SetFrameId(10); + packet_builder_.SetFrameIds(10, 10); packet_builder_.SetPacketId(65535); packet_builder_.SetMaxPacketId(65535); packet_builder_.SetPayloadType(kTestPayloadType - 1); - packet_builder_.BuildHeader(packet, kPacketLength); - // Parse packet as is. - RtpCastHeader rtp_header; - EXPECT_FALSE(rtp_parser_->ParsePacket(packet, kPacketLength, &rtp_header)); + packet_builder_.BuildHeader(packet_, kPacketLength); + cast_header_.is_key_frame = true; + cast_header_.frame_id = 10; + cast_header_.reference_frame_id = 10; + cast_header_.packet_id = 65535; + cast_header_.max_packet_id = 65535; + cast_header_.payload_type = kTestPayloadType - 1; + ExpectDoesNotParsePacket(); } TEST_F(RtpParserTest, InvalidSsrc) { - // Build generic data packet. - uint8 packet[kPacketLength]; packet_builder_.SetKeyFrame(true); - packet_builder_.SetFrameId(10); + packet_builder_.SetFrameIds(10, 10); packet_builder_.SetPacketId(65535); packet_builder_.SetMaxPacketId(65535); packet_builder_.SetSsrc(kTestSsrc - 1); - packet_builder_.BuildHeader(packet, kPacketLength); - // Parse packet as is. - RtpCastHeader rtp_header; - EXPECT_FALSE(rtp_parser_->ParsePacket(packet, kPacketLength, &rtp_header)); + packet_builder_.BuildHeader(packet_, kPacketLength); + cast_header_.is_key_frame = true; + cast_header_.frame_id = 10; + cast_header_.reference_frame_id = 10; + cast_header_.packet_id = 65535; + cast_header_.max_packet_id = 65535; + cast_header_.sender_ssrc = kTestSsrc - 1; + ExpectDoesNotParsePacket(); +} + +TEST_F(RtpParserTest, ParseCastPacketWithSpecificFrameReference) { + packet_builder_.SetFrameIds(kRefFrameId + 3, kRefFrameId); + packet_builder_.BuildHeader(packet_, kPacketLength); + cast_header_.frame_id = kRefFrameId + 3; + cast_header_.reference_frame_id = kRefFrameId; + ExpectParsesPacket(); +} + +TEST_F(RtpParserTest, ParseExpandingFrameIdTo32Bits) { + const uint32 kMaxFrameId = 1000; + packet_builder_.SetKeyFrame(true); + cast_header_.is_key_frame = true; + for (uint32 frame_id = 0; frame_id <= kMaxFrameId; ++frame_id) { + packet_builder_.SetFrameIds(frame_id, frame_id); + packet_builder_.BuildHeader(packet_, kPacketLength); + cast_header_.frame_id = frame_id; + cast_header_.reference_frame_id = frame_id; + ExpectParsesPacket(); + } } -TEST_F(RtpParserTest, ParseCastPacketWithoutReference) { - cast_header_.is_reference = false; - cast_header_.reference_frame_id = 0; - packet_builder_.SetReferenceFrameId(kRefFrameId, false); - - // Build generic data packet. - uint8 packet[kPacketLength]; - packet_builder_.BuildHeader(packet, kPacketLength); - // Parse packet as is. - RtpCastHeader rtp_header; - rtp_data_->SetExpectedHeader(cast_header_); - EXPECT_TRUE(rtp_parser_->ParsePacket(packet, kPacketLength, &rtp_header)); +TEST_F(RtpParserTest, ParseExpandingReferenceFrameIdTo32Bits) { + const uint32 kMaxFrameId = 1000; + const uint32 kMaxBackReferenceOffset = 10; + packet_builder_.SetKeyFrame(false); + cast_header_.is_key_frame = false; + for (uint32 frame_id = kMaxBackReferenceOffset; + frame_id <= kMaxFrameId; ++frame_id) { + const uint32 reference_frame_id = + frame_id - base::RandInt(1, kMaxBackReferenceOffset); + packet_builder_.SetFrameIds(frame_id, reference_frame_id); + packet_builder_.BuildHeader(packet_, kPacketLength); + cast_header_.frame_id = frame_id; + cast_header_.reference_frame_id = reference_frame_id; + ExpectParsesPacket(); + } } } // namespace cast diff --git a/chromium/media/cast/rtp_receiver/rtp_receiver.cc b/chromium/media/cast/rtp_receiver/rtp_receiver.cc deleted file mode 100644 index 3c804d9bd9b..00000000000 --- a/chromium/media/cast/rtp_receiver/rtp_receiver.cc +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "media/cast/rtp_receiver/rtp_receiver.h" - -#include "base/logging.h" -#include "media/cast/rtp_receiver/receiver_stats.h" -#include "media/cast/rtp_receiver/rtp_parser/rtp_parser.h" -#include "media/cast/rtp_receiver/rtp_receiver_defines.h" -#include "net/base/big_endian.h" - -namespace media { -namespace cast { - -RtpReceiver::RtpReceiver(base::TickClock* clock, - const AudioReceiverConfig* audio_config, - const VideoReceiverConfig* video_config, - RtpData* incoming_payload_callback) { - DCHECK(incoming_payload_callback) << "Invalid argument"; - DCHECK(audio_config || video_config) << "Invalid argument"; - - // Configure parser. - RtpParserConfig config; - if (audio_config) { - config.ssrc = audio_config->incoming_ssrc; - config.payload_type = audio_config->rtp_payload_type; - config.audio_codec = audio_config->codec; - config.audio_channels = audio_config->channels; - } else { - config.ssrc = video_config->incoming_ssrc; - config.payload_type = video_config->rtp_payload_type; - config.video_codec = video_config->codec; - } - stats_.reset(new ReceiverStats(clock)); - parser_.reset(new RtpParser(incoming_payload_callback, config)); -} - -RtpReceiver::~RtpReceiver() {} - -// static -uint32 RtpReceiver::GetSsrcOfSender(const uint8* rtcp_buffer, size_t length) { - DCHECK_GE(length, kMinLengthOfRtp) << "Invalid RTP packet"; - uint32 ssrc_of_sender; - net::BigEndianReader big_endian_reader(rtcp_buffer, length); - big_endian_reader.Skip(8); // Skip header - big_endian_reader.ReadU32(&ssrc_of_sender); - return ssrc_of_sender; -} - -bool RtpReceiver::ReceivedPacket(const uint8* packet, size_t length) { - RtpCastHeader rtp_header; - if (!parser_->ParsePacket(packet, length, &rtp_header)) return false; - - stats_->UpdateStatistics(rtp_header); - return true; -} - -void RtpReceiver::GetStatistics(uint8* fraction_lost, - uint32* cumulative_lost, - uint32* extended_high_sequence_number, - uint32* jitter) { - stats_->GetStatistics(fraction_lost, - cumulative_lost, - extended_high_sequence_number, - jitter); -} - -} // namespace cast -} // namespace media diff --git a/chromium/media/cast/rtp_receiver/rtp_receiver.gyp b/chromium/media/cast/rtp_receiver/rtp_receiver.gyp deleted file mode 100644 index b612964c070..00000000000 --- a/chromium/media/cast/rtp_receiver/rtp_receiver.gyp +++ /dev/null @@ -1,26 +0,0 @@ -# Copyright 2013 The Chromium Authors. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - -{ - 'targets': [ - { - 'target_name': 'cast_rtp_receiver', - 'type': 'static_library', - 'include_dirs': [ - '<(DEPTH)/', - '<(DEPTH)/third_party/', - ], - 'sources': [ - 'receiver_stats.cc', - 'receiver_stats.h', - 'rtp_receiver.cc', - 'rtp_receiver.h', - ], # source - 'dependencies': [ - '<(DEPTH)/base/base.gyp:base', - 'rtp_parser/rtp_parser.gyp:*', - ], - }, - ], -} diff --git a/chromium/media/cast/rtp_receiver/rtp_receiver.h b/chromium/media/cast/rtp_receiver/rtp_receiver.h deleted file mode 100644 index 5639d7d8c36..00000000000 --- a/chromium/media/cast/rtp_receiver/rtp_receiver.h +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -// Interface to the rtp receiver. - -#ifndef MEDIA_CAST_RTP_RECEIVER_RTP_RECEIVER_H_ -#define MEDIA_CAST_RTP_RECEIVER_RTP_RECEIVER_H_ - -#include "base/memory/scoped_ptr.h" -#include "media/cast/cast_config.h" -#include "media/cast/rtcp/rtcp.h" -#include "media/cast/rtp_receiver/rtp_receiver_defines.h" - -namespace media { -namespace cast { - -class RtpData { - public: - virtual void OnReceivedPayloadData(const uint8* payload_data, - size_t payload_size, - const RtpCastHeader* rtp_header) = 0; - - protected: - virtual ~RtpData() {} -}; - -class ReceiverStats; -class RtpParser; - -class RtpReceiver { - public: - RtpReceiver(base::TickClock* clock, - const AudioReceiverConfig* audio_config, - const VideoReceiverConfig* video_config, - RtpData* incoming_payload_callback); - ~RtpReceiver(); - - static uint32 GetSsrcOfSender(const uint8* rtcp_buffer, size_t length); - - bool ReceivedPacket(const uint8* packet, size_t length); - - void GetStatistics(uint8* fraction_lost, - uint32* cumulative_lost, // 24 bits valid. - uint32* extended_high_sequence_number, - uint32* jitter); - - private: - scoped_ptr<ReceiverStats> stats_; - scoped_ptr<RtpParser> parser_; -}; - -} // namespace cast -} // namespace media - -#endif // MEDIA_CAST_RTP_RECEIVER_RTP_RECEIVER_H_ diff --git a/chromium/media/cast/rtp_receiver/rtp_receiver_defines.cc b/chromium/media/cast/rtp_receiver/rtp_receiver_defines.cc new file mode 100644 index 00000000000..e42b2b733c7 --- /dev/null +++ b/chromium/media/cast/rtp_receiver/rtp_receiver_defines.cc @@ -0,0 +1,25 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/cast/rtp_receiver/rtp_receiver_defines.h" + +namespace media { +namespace cast { + +RtpCastHeader::RtpCastHeader() + : marker(false), + payload_type(0), + sequence_number(0), + rtp_timestamp(0), + sender_ssrc(0), + is_key_frame(false), + frame_id(0), + packet_id(0), + max_packet_id(0), + reference_frame_id(0) {} + +RtpPayloadFeedback::~RtpPayloadFeedback() {} + +} // namespace cast +} // namespace media diff --git a/chromium/media/cast/rtp_receiver/rtp_receiver_defines.h b/chromium/media/cast/rtp_receiver/rtp_receiver_defines.h index ae957e3ae6b..d907436f489 100644 --- a/chromium/media/cast/rtp_receiver/rtp_receiver_defines.h +++ b/chromium/media/cast/rtp_receiver/rtp_receiver_defines.h @@ -8,29 +8,25 @@ #include "base/basictypes.h" #include "media/cast/cast_config.h" #include "media/cast/rtcp/rtcp_defines.h" -#include "third_party/webrtc/modules/interface/module_common_types.h" namespace media { namespace cast { -const uint8 kRtpMarkerBitMask = 0x80; - struct RtpCastHeader { - RtpCastHeader() { - is_key_frame = false; - frame_id = 0; - packet_id = 0; - max_packet_id = 0; - is_reference = false; - reference_frame_id = 0; - } - webrtc::WebRtcRTPHeader webrtc; + RtpCastHeader(); + + // Elements from RTP packet header. + bool marker; + uint8 payload_type; + uint16 sequence_number; + uint32 rtp_timestamp; + uint32 sender_ssrc; + + // Elements from Cast header (at beginning of RTP payload). bool is_key_frame; uint32 frame_id; uint16 packet_id; uint16 max_packet_id; - bool is_reference; // Set to true if the previous frame is not available, - // and the reference frame id is available. uint32 reference_frame_id; }; @@ -39,7 +35,7 @@ class RtpPayloadFeedback { virtual void CastFeedback(const RtcpCastMessage& cast_feedback) = 0; protected: - virtual ~RtpPayloadFeedback() {} + virtual ~RtpPayloadFeedback(); }; } // namespace cast diff --git a/chromium/media/cast/rtp_timestamp_helper.cc b/chromium/media/cast/rtp_timestamp_helper.cc new file mode 100644 index 00000000000..3349e7b33fd --- /dev/null +++ b/chromium/media/cast/rtp_timestamp_helper.cc @@ -0,0 +1,36 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/cast/rtp_timestamp_helper.h" + +namespace media { +namespace cast { + +RtpTimestampHelper::RtpTimestampHelper(int frequency) + : frequency_(frequency), + last_rtp_timestamp_(0) { +} + +RtpTimestampHelper::~RtpTimestampHelper() { +} + +bool RtpTimestampHelper::GetCurrentTimeAsRtpTimestamp( + const base::TimeTicks& now, uint32* rtp_timestamp) const { + if (last_capture_time_.is_null()) + return false; + const base::TimeDelta elapsed_time = now - last_capture_time_; + const int64 rtp_delta = + elapsed_time * frequency_ / base::TimeDelta::FromSeconds(1); + *rtp_timestamp = last_rtp_timestamp_ + static_cast<uint32>(rtp_delta); + return true; +} + +void RtpTimestampHelper::StoreLatestTime( + base::TimeTicks capture_time, uint32 rtp_timestamp) { + last_capture_time_ = capture_time; + last_rtp_timestamp_ = rtp_timestamp; +} + +} // namespace cast +} // namespace media diff --git a/chromium/media/cast/rtp_timestamp_helper.h b/chromium/media/cast/rtp_timestamp_helper.h new file mode 100644 index 00000000000..b9c650c5063 --- /dev/null +++ b/chromium/media/cast/rtp_timestamp_helper.h @@ -0,0 +1,41 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_CAST_RTP_TIMESTAMP_HELPER_H_ +#define MEDIA_CAST_RTP_TIMESTAMP_HELPER_H_ + +#include "base/basictypes.h" +#include "base/time/time.h" + +namespace media { +namespace cast { + +// A helper class used to convert current time ticks into RTP timestamp. +class RtpTimestampHelper { + public: + explicit RtpTimestampHelper(int frequency); + ~RtpTimestampHelper(); + + // Compute a RTP timestamp using current time, last encoded time and + // last encoded RTP timestamp. + // Return true if |rtp_timestamp| is computed. + bool GetCurrentTimeAsRtpTimestamp(const base::TimeTicks& now, + uint32* rtp_timestamp) const; + + // Store the capture time and the corresponding RTP timestamp for the + // last encoded frame. + void StoreLatestTime(base::TimeTicks capture_time, uint32 rtp_timestamp); + + private: + int frequency_; + base::TimeTicks last_capture_time_; + uint32 last_rtp_timestamp_; + + DISALLOW_COPY_AND_ASSIGN(RtpTimestampHelper); +}; + +} // namespace cast +} // namespace media + +#endif // MEDIA_CAST_CAST_DEFINES_H_ diff --git a/chromium/media/cast/test/transport/transport.gyp b/chromium/media/cast/test/transport/transport.gyp deleted file mode 100644 index 79be3d28e6d..00000000000 --- a/chromium/media/cast/test/transport/transport.gyp +++ /dev/null @@ -1,22 +0,0 @@ -# Copyright 2013 The Chromium Authors. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - -{ - 'targets': [ - { - 'target_name': 'cast_transport', - 'type': 'static_library', - 'include_dirs': [ - '<(DEPTH)/', - ], - 'sources': [ - 'transport.cc', - 'transport.h', - ], # source - 'dependencies': [ - '<(DEPTH)/net/net.gyp:net', - ], - }, - ], -}
\ No newline at end of file diff --git a/chromium/media/cast/test/utility/utility.gyp b/chromium/media/cast/test/utility/utility.gyp deleted file mode 100644 index 021c2d9a416..00000000000 --- a/chromium/media/cast/test/utility/utility.gyp +++ /dev/null @@ -1,28 +0,0 @@ -# Copyright 2013 The Chromium Authors. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - -{ - 'targets': [ - { - 'target_name': 'cast_test_utility', - 'type': 'static_library', - 'include_dirs': [ - '<(DEPTH)/', - ], - 'dependencies': [ - '<(DEPTH)/ui/gfx/gfx.gyp:gfx', - '<(DEPTH)/testing/gtest.gyp:gtest', - '<(DEPTH)/third_party/libyuv/libyuv.gyp:libyuv', - - ], - 'sources': [ - 'input_helper.cc', - 'input_helper.h', - '<(DEPTH)/media/cast/test/audio_utility.cc', - '<(DEPTH)/media/cast/test/fake_task_runner.cc', - '<(DEPTH)/media/cast/test/video_utility.cc', - ], # source - }, - ], -}
\ No newline at end of file diff --git a/chromium/media/cast/transport/cast_transport_config.cc b/chromium/media/cast/transport/cast_transport_config.cc new file mode 100644 index 00000000000..16e90347137 --- /dev/null +++ b/chromium/media/cast/transport/cast_transport_config.cc @@ -0,0 +1,82 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/cast/transport/cast_transport_config.h" + +namespace media { +namespace cast { +namespace transport { + +namespace { +const int kDefaultRtpMaxDelayMs = 100; +} // namespace + +RtpConfig::RtpConfig() + : ssrc(0), + max_delay_ms(kDefaultRtpMaxDelayMs), + payload_type(0) {} + +RtpConfig::~RtpConfig() {} + +CastTransportRtpConfig::CastTransportRtpConfig() + : max_outstanding_frames(-1) {} + +CastTransportRtpConfig::~CastTransportRtpConfig() {} + +CastTransportAudioConfig::CastTransportAudioConfig() + : codec(kOpus), frequency(0), channels(0) {} + +CastTransportAudioConfig::~CastTransportAudioConfig() {} + +CastTransportVideoConfig::CastTransportVideoConfig() : codec(kVp8) {} + +CastTransportVideoConfig::~CastTransportVideoConfig() {} + +EncodedFrame::EncodedFrame() + : dependency(UNKNOWN_DEPENDENCY), + frame_id(0), + referenced_frame_id(0), + rtp_timestamp(0) {} + +EncodedFrame::~EncodedFrame() {} + +void EncodedFrame::CopyMetadataTo(EncodedFrame* dest) const { + DCHECK(dest); + dest->dependency = this->dependency; + dest->frame_id = this->frame_id; + dest->referenced_frame_id = this->referenced_frame_id; + dest->rtp_timestamp = this->rtp_timestamp; + dest->reference_time = this->reference_time; +} + +RtcpSenderInfo::RtcpSenderInfo() + : ntp_seconds(0), + ntp_fraction(0), + rtp_timestamp(0), + send_packet_count(0), + send_octet_count(0) {} +RtcpSenderInfo::~RtcpSenderInfo() {} + +RtcpReportBlock::RtcpReportBlock() + : remote_ssrc(0), + media_ssrc(0), + fraction_lost(0), + cumulative_lost(0), + extended_high_sequence_number(0), + jitter(0), + last_sr(0), + delay_since_last_sr(0) {} +RtcpReportBlock::~RtcpReportBlock() {} + +RtcpDlrrReportBlock::RtcpDlrrReportBlock() + : last_rr(0), delay_since_last_rr(0) {} +RtcpDlrrReportBlock::~RtcpDlrrReportBlock() {} + +SendRtcpFromRtpSenderData::SendRtcpFromRtpSenderData() + : packet_type_flags(0), sending_ssrc(0) {} +SendRtcpFromRtpSenderData::~SendRtcpFromRtpSenderData() {} + +} // namespace transport +} // namespace cast +} // namespace media diff --git a/chromium/media/cast/transport/cast_transport_config.h b/chromium/media/cast/transport/cast_transport_config.h new file mode 100644 index 00000000000..96b771acb99 --- /dev/null +++ b/chromium/media/cast/transport/cast_transport_config.h @@ -0,0 +1,221 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_CAST_TRANSPORT_CAST_TRANSPORT_CONFIG_H_ +#define MEDIA_CAST_TRANSPORT_CAST_TRANSPORT_CONFIG_H_ + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/callback.h" +#include "base/memory/linked_ptr.h" +#include "base/memory/ref_counted.h" +#include "base/stl_util.h" +#include "media/cast/transport/cast_transport_defines.h" +#include "net/base/ip_endpoint.h" + +namespace media { +namespace cast { +namespace transport { + +enum RtcpMode { + kRtcpCompound, // Compound RTCP mode is described by RFC 4585. + kRtcpReducedSize, // Reduced-size RTCP mode is described by RFC 5506. +}; + +enum VideoCodec { + kUnknownVideoCodec, + kFakeSoftwareVideo, + kVp8, + kH264, + kVideoCodecLast = kH264 +}; + +enum AudioCodec { + kUnknownAudioCodec, + kOpus, + kPcm16, + kAudioCodecLast = kPcm16 +}; + +struct RtpConfig { + RtpConfig(); + ~RtpConfig(); + uint32 ssrc; + int max_delay_ms; + int payload_type; + std::string aes_key; // Binary string of size kAesKeySize. + std::string aes_iv_mask; // Binary string of size kAesBlockSize. +}; + +struct CastTransportRtpConfig { + CastTransportRtpConfig(); + ~CastTransportRtpConfig(); + RtpConfig config; + int max_outstanding_frames; +}; + +struct CastTransportAudioConfig { + CastTransportAudioConfig(); + ~CastTransportAudioConfig(); + + CastTransportRtpConfig rtp; + AudioCodec codec; + int frequency; + int channels; +}; + +struct CastTransportVideoConfig { + CastTransportVideoConfig(); + ~CastTransportVideoConfig(); + + CastTransportRtpConfig rtp; + VideoCodec codec; +}; + +// A combination of metadata and data for one encoded frame. This can contain +// audio data or video data or other. +struct EncodedFrame { + enum Dependency { + // "null" value, used to indicate whether |dependency| has been set. + UNKNOWN_DEPENDENCY, + + // Not decodable without the reference frame indicated by + // |referenced_frame_id|. + DEPENDENT, + + // Independently decodable. + INDEPENDENT, + + // Independently decodable, and no future frames will depend on any frames + // before this one. + KEY, + + DEPENDENCY_LAST = KEY + }; + + EncodedFrame(); + ~EncodedFrame(); + + // Convenience accessors to data as an array of uint8 elements. + const uint8* bytes() const { + return reinterpret_cast<uint8*>(string_as_array( + const_cast<std::string*>(&data))); + } + uint8* mutable_bytes() { + return reinterpret_cast<uint8*>(string_as_array(&data)); + } + + // Copies all data members except |data| to |dest|. + // Does not modify |dest->data|. + void CopyMetadataTo(EncodedFrame* dest) const; + + // This frame's dependency relationship with respect to other frames. + Dependency dependency; + + // The label associated with this frame. Implies an ordering relative to + // other frames in the same stream. + uint32 frame_id; + + // The label associated with the frame upon which this frame depends. If + // this frame does not require any other frame in order to become decodable + // (e.g., key frames), |referenced_frame_id| must equal |frame_id|. + uint32 referenced_frame_id; + + // The stream timestamp, on the timeline of the signal data. For example, RTP + // timestamps for audio are usually defined as the total number of audio + // samples encoded in all prior frames. A playback system uses this value to + // detect gaps in the stream, and otherwise stretch the signal to match + // playout targets. + uint32 rtp_timestamp; + + // The common reference clock timestamp for this frame. This value originates + // from a sender and is used to provide lip synchronization between streams in + // a receiver. Thus, in the sender context, this is set to the time at which + // the frame was captured/recorded. In the receiver context, this is set to + // the target playout time. Over a sequence of frames, this time value is + // expected to drift with respect to the elapsed time implied by the RTP + // timestamps; and it may not necessarily increment with precise regularity. + base::TimeTicks reference_time; + + // The encoded signal data. + std::string data; +}; + +typedef std::vector<uint8> Packet; +typedef scoped_refptr<base::RefCountedData<Packet> > PacketRef; +typedef std::vector<PacketRef> PacketList; + +typedef base::Callback<void(scoped_ptr<Packet> packet)> PacketReceiverCallback; + +class PacketSender { + public: + // Send a packet to the network. Returns false if the network is blocked + // and we should wait for |cb| to be called. It is not allowed to called + // SendPacket again until |cb| has been called. Any other errors that + // occur will be reported through side channels, in such cases, this function + // will return true indicating that the channel is not blocked. + virtual bool SendPacket(PacketRef packet, const base::Closure& cb) = 0; + virtual ~PacketSender() {} +}; + +struct RtcpSenderInfo { + RtcpSenderInfo(); + ~RtcpSenderInfo(); + // First three members are used for lipsync. + // First two members are used for rtt. + uint32 ntp_seconds; + uint32 ntp_fraction; + uint32 rtp_timestamp; + uint32 send_packet_count; + size_t send_octet_count; +}; + +struct RtcpReportBlock { + RtcpReportBlock(); + ~RtcpReportBlock(); + uint32 remote_ssrc; // SSRC of sender of this report. + uint32 media_ssrc; // SSRC of the RTP packet sender. + uint8 fraction_lost; + uint32 cumulative_lost; // 24 bits valid. + uint32 extended_high_sequence_number; + uint32 jitter; + uint32 last_sr; + uint32 delay_since_last_sr; +}; + +struct RtcpDlrrReportBlock { + RtcpDlrrReportBlock(); + ~RtcpDlrrReportBlock(); + uint32 last_rr; + uint32 delay_since_last_rr; +}; + +// This is only needed because IPC messages don't support more than +// 5 arguments. +struct SendRtcpFromRtpSenderData { + SendRtcpFromRtpSenderData(); + ~SendRtcpFromRtpSenderData(); + uint32 packet_type_flags; + uint32 sending_ssrc; + std::string c_name; + uint32 ntp_seconds; + uint32 ntp_fraction; + uint32 rtp_timestamp; +}; + +inline bool operator==(RtcpSenderInfo lhs, RtcpSenderInfo rhs) { + return lhs.ntp_seconds == rhs.ntp_seconds && + lhs.ntp_fraction == rhs.ntp_fraction && + lhs.rtp_timestamp == rhs.rtp_timestamp && + lhs.send_packet_count == rhs.send_packet_count && + lhs.send_octet_count == rhs.send_octet_count; +} + +} // namespace transport +} // namespace cast +} // namespace media + +#endif // MEDIA_CAST_TRANSPORT_CAST_TRANSPORT_CONFIG_H_ diff --git a/chromium/media/cast/transport/cast_transport_defines.h b/chromium/media/cast/transport/cast_transport_defines.h new file mode 100644 index 00000000000..a34f7c539ab --- /dev/null +++ b/chromium/media/cast/transport/cast_transport_defines.h @@ -0,0 +1,169 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_CAST_TRANSPORT_CAST_TRANSPORT_DEFINES_H_ +#define MEDIA_CAST_TRANSPORT_CAST_TRANSPORT_DEFINES_H_ + +#include <stdint.h> + +#include <map> +#include <set> +#include <string> + +#include "base/basictypes.h" +#include "base/time/time.h" + +namespace media { +namespace cast { +namespace transport { + +// TODO(mikhal): Implement and add more types. +enum CastTransportStatus { + TRANSPORT_AUDIO_UNINITIALIZED = 0, + TRANSPORT_VIDEO_UNINITIALIZED, + TRANSPORT_AUDIO_INITIALIZED, + TRANSPORT_VIDEO_INITIALIZED, + TRANSPORT_INVALID_CRYPTO_CONFIG, + TRANSPORT_SOCKET_ERROR, + CAST_TRANSPORT_STATUS_LAST = TRANSPORT_SOCKET_ERROR +}; + +const size_t kMaxIpPacketSize = 1500; +// Each uint16 represents one packet id within a cast frame. +typedef std::set<uint16> PacketIdSet; +// Each uint8 represents one cast frame. +typedef std::map<uint8, PacketIdSet> MissingFramesAndPacketsMap; + +// Crypto. +const size_t kAesBlockSize = 16; +const size_t kAesKeySize = 16; + +inline std::string GetAesNonce(uint32 frame_id, const std::string& iv_mask) { + std::string aes_nonce(kAesBlockSize, 0); + + // Serializing frame_id in big-endian order (aes_nonce[8] is the most + // significant byte of frame_id). + aes_nonce[11] = frame_id & 0xff; + aes_nonce[10] = (frame_id >> 8) & 0xff; + aes_nonce[9] = (frame_id >> 16) & 0xff; + aes_nonce[8] = (frame_id >> 24) & 0xff; + + for (size_t i = 0; i < kAesBlockSize; ++i) { + aes_nonce[i] ^= iv_mask[i]; + } + return aes_nonce; +} + +// Rtcp defines. + +enum RtcpPacketFields { + kPacketTypeLow = 194, // SMPTE time-code mapping. + kPacketTypeInterArrivalJitterReport = 195, + kPacketTypeSenderReport = 200, + kPacketTypeReceiverReport = 201, + kPacketTypeSdes = 202, + kPacketTypeBye = 203, + kPacketTypeApplicationDefined = 204, + kPacketTypeGenericRtpFeedback = 205, + kPacketTypePayloadSpecific = 206, + kPacketTypeXr = 207, + kPacketTypeHigh = 210, // Port Mapping. +}; + +enum RtcpPacketField { + kRtcpSr = 0x0002, + kRtcpRr = 0x0004, + kRtcpBye = 0x0008, + kRtcpPli = 0x0010, + kRtcpNack = 0x0020, + kRtcpFir = 0x0040, + kRtcpSrReq = 0x0200, + kRtcpDlrr = 0x0400, + kRtcpRrtr = 0x0800, + kRtcpRpsi = 0x8000, + kRtcpRemb = 0x10000, + kRtcpCast = 0x20000, + kRtcpSenderLog = 0x40000, + kRtcpReceiverLog = 0x80000, + }; + +// Each uint16 represents one packet id within a cast frame. +typedef std::set<uint16> PacketIdSet; +// Each uint8 represents one cast frame. +typedef std::map<uint8, PacketIdSet> MissingFramesAndPacketsMap; + +// TODO(miu): UGLY IN-LINE DEFINITION IN HEADER FILE! Move to appropriate +// location, separated into .h and .cc files. +class FrameIdWrapHelper { + public: + FrameIdWrapHelper() + : first_(true), frame_id_wrap_count_(0), range_(kLowRange) {} + + uint32 MapTo32bitsFrameId(const uint8 over_the_wire_frame_id) { + if (first_) { + first_ = false; + if (over_the_wire_frame_id == 0xff) { + // Special case for startup. + return kStartFrameId; + } + } + + uint32 wrap_count = frame_id_wrap_count_; + switch (range_) { + case kLowRange: + if (over_the_wire_frame_id > kLowRangeThreshold && + over_the_wire_frame_id < kHighRangeThreshold) { + range_ = kMiddleRange; + } + if (over_the_wire_frame_id >= kHighRangeThreshold) { + // Wrap count was incremented in High->Low transition, but this frame + // is 'old', actually from before the wrap count got incremented. + --wrap_count; + } + break; + case kMiddleRange: + if (over_the_wire_frame_id >= kHighRangeThreshold) { + range_ = kHighRange; + } + break; + case kHighRange: + if (over_the_wire_frame_id <= kLowRangeThreshold) { + // Wrap-around detected. + range_ = kLowRange; + ++frame_id_wrap_count_; + // Frame triggering wrap-around so wrap count should be incremented as + // as well to match |frame_id_wrap_count_|. + ++wrap_count; + } + break; + } + return (wrap_count << 8) + over_the_wire_frame_id; + } + + private: + enum Range { kLowRange, kMiddleRange, kHighRange, }; + + static const uint8 kLowRangeThreshold = 63; + static const uint8 kHighRangeThreshold = 192; + static const uint32 kStartFrameId = UINT32_C(0xffffffff); + + bool first_; + uint32 frame_id_wrap_count_; + Range range_; + + DISALLOW_COPY_AND_ASSIGN(FrameIdWrapHelper); +}; + +inline uint32 GetVideoRtpTimestamp(const base::TimeTicks& time_ticks) { + base::TimeTicks zero_time; + base::TimeDelta recorded_delta = time_ticks - zero_time; + // Timestamp is in 90 KHz for video. + return static_cast<uint32>(recorded_delta.InMilliseconds() * 90); +} + +} // namespace transport +} // namespace cast +} // namespace media + +#endif // MEDIA_CAST_TRANSPORT_CAST_TRANSPORT_DEFINES_H_ diff --git a/chromium/media/cast/transport/cast_transport_sender.h b/chromium/media/cast/transport/cast_transport_sender.h new file mode 100644 index 00000000000..e88f2f4f098 --- /dev/null +++ b/chromium/media/cast/transport/cast_transport_sender.h @@ -0,0 +1,113 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This is the main interface for the cast transport sender. It accepts encoded +// frames (both audio and video), encrypts their encoded data, packetizes them +// and feeds them into a transport (e.g., UDP). + +// Construction of the Cast Sender and the Cast Transport Sender should be done +// in the following order: +// 1. Create CastTransportSender. +// 2. Create CastSender (accepts CastTransportSender as an input). +// 3. Call CastTransportSender::SetPacketReceiver to ensure that the packets +// received by the CastTransportSender will be sent to the CastSender. +// Steps 3 can be done interchangeably. + +// Destruction: The CastTransportSender is assumed to be valid as long as the +// CastSender is alive. Therefore the CastSender should be destructed before the +// CastTransportSender. +// This also works when the CastSender acts as a receiver for the RTCP packets +// due to the weak pointers in the ReceivedPacket method in cast_sender_impl.cc. + +#ifndef MEDIA_CAST_TRANSPORT_CAST_TRANSPORT_SENDER_H_ +#define MEDIA_CAST_TRANSPORT_CAST_TRANSPORT_SENDER_H_ + +#include "base/basictypes.h" +#include "base/callback.h" +#include "base/single_thread_task_runner.h" +#include "base/threading/non_thread_safe.h" +#include "base/time/tick_clock.h" +#include "media/cast/logging/logging_defines.h" +#include "media/cast/transport/cast_transport_config.h" +#include "media/cast/transport/cast_transport_defines.h" + +namespace net { +class NetLog; +} // namespace net + +namespace media { +namespace cast { +namespace transport { + +// Following the initialization of either audio or video an initialization +// status will be sent via this callback. +typedef base::Callback<void(CastTransportStatus status)> + CastTransportStatusCallback; + +typedef base::Callback<void(const std::vector<PacketEvent>&)> + BulkRawEventsCallback; + +// The application should only trigger this class from the transport thread. +class CastTransportSender : public base::NonThreadSafe { + public: + static scoped_ptr<CastTransportSender> Create( + net::NetLog* net_log, + base::TickClock* clock, + const net::IPEndPoint& remote_end_point, + const CastTransportStatusCallback& status_callback, + const BulkRawEventsCallback& raw_events_callback, + base::TimeDelta raw_events_callback_interval, + const scoped_refptr<base::SingleThreadTaskRunner>& transport_task_runner); + + virtual ~CastTransportSender() {} + + // Audio/Video initialization. + // Encoded frames cannot be transmitted until the relevant initialize method + // is called. Usually called by CastSender. + virtual void InitializeAudio(const CastTransportAudioConfig& config) = 0; + + virtual void InitializeVideo(const CastTransportVideoConfig& config) = 0; + + // Sets the Cast packet receiver. Should be called after creation on the + // Cast sender. Packets won't be received until this function is called. + virtual void SetPacketReceiver( + const PacketReceiverCallback& packet_receiver) = 0; + + // The following two functions handle the encoded media frames (audio and + // video) to be processed. + // Frames will be encrypted, packetized and transmitted to the network. + virtual void InsertCodedAudioFrame(const EncodedFrame& audio_frame) = 0; + virtual void InsertCodedVideoFrame(const EncodedFrame& video_frame) = 0; + + // Builds an RTCP packet and sends it to the network. + // |ntp_seconds|, |ntp_fraction| and |rtp_timestamp| are used in the + // RTCP Sender Report. + virtual void SendRtcpFromRtpSender(uint32 packet_type_flags, + uint32 ntp_seconds, + uint32 ntp_fraction, + uint32 rtp_timestamp, + const RtcpDlrrReportBlock& dlrr, + uint32 sending_ssrc, + const std::string& c_name) = 0; + + // Retransmission request. + // |missing_packets| includes the list of frames and packets in each + // frame to be re-transmitted. + // If |cancel_rtx_if_not_in_list| is used as an optimization to cancel + // pending re-transmission requests of packets not listed in + // |missing_packets|. If the requested packet(s) were sent recently + // (how long is specified by |dedupe_window|) then this re-transmit + // will be ignored. + virtual void ResendPackets( + bool is_audio, + const MissingFramesAndPacketsMap& missing_packets, + bool cancel_rtx_if_not_in_list, + base::TimeDelta dedupe_window) = 0; +}; + +} // namespace transport +} // namespace cast +} // namespace media + +#endif // MEDIA_CAST_TRANSPORT_CAST_TRANSPORT_SENDER_H_ diff --git a/chromium/media/cast/transport/cast_transport_sender_impl.cc b/chromium/media/cast/transport/cast_transport_sender_impl.cc new file mode 100644 index 00000000000..6fd848f27bf --- /dev/null +++ b/chromium/media/cast/transport/cast_transport_sender_impl.cc @@ -0,0 +1,212 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/cast/transport/cast_transport_sender_impl.h" + +#include "base/single_thread_task_runner.h" +#include "media/cast/transport/cast_transport_config.h" +#include "media/cast/transport/cast_transport_defines.h" +#include "net/base/net_util.h" + +namespace media { +namespace cast { +namespace transport { + +scoped_ptr<CastTransportSender> CastTransportSender::Create( + net::NetLog* net_log, + base::TickClock* clock, + const net::IPEndPoint& remote_end_point, + const CastTransportStatusCallback& status_callback, + const BulkRawEventsCallback& raw_events_callback, + base::TimeDelta raw_events_callback_interval, + const scoped_refptr<base::SingleThreadTaskRunner>& transport_task_runner) { + return scoped_ptr<CastTransportSender>( + new CastTransportSenderImpl(net_log, + clock, + remote_end_point, + status_callback, + raw_events_callback, + raw_events_callback_interval, + transport_task_runner.get(), + NULL)); +} + +CastTransportSenderImpl::CastTransportSenderImpl( + net::NetLog* net_log, + base::TickClock* clock, + const net::IPEndPoint& remote_end_point, + const CastTransportStatusCallback& status_callback, + const BulkRawEventsCallback& raw_events_callback, + base::TimeDelta raw_events_callback_interval, + const scoped_refptr<base::SingleThreadTaskRunner>& transport_task_runner, + PacketSender* external_transport) + : clock_(clock), + status_callback_(status_callback), + transport_task_runner_(transport_task_runner), + transport_(external_transport ? NULL + : new UdpTransport(net_log, + transport_task_runner, + net::IPEndPoint(), + remote_end_point, + status_callback)), + logging_(), + pacer_(clock, + &logging_, + external_transport ? external_transport : transport_.get(), + transport_task_runner), + rtcp_builder_(&pacer_), + raw_events_callback_(raw_events_callback) { + DCHECK(clock_); + if (!raw_events_callback_.is_null()) { + DCHECK(raw_events_callback_interval > base::TimeDelta()); + event_subscriber_.reset(new SimpleEventSubscriber); + logging_.AddRawEventSubscriber(event_subscriber_.get()); + raw_events_timer_.Start(FROM_HERE, + raw_events_callback_interval, + this, + &CastTransportSenderImpl::SendRawEvents); + } + if (transport_) { + // The default DSCP value for cast is AF41. Which gives it a higher + // priority over other traffic. + transport_->SetDscp(net::DSCP_AF41); + } +} + +CastTransportSenderImpl::~CastTransportSenderImpl() { + if (event_subscriber_.get()) + logging_.RemoveRawEventSubscriber(event_subscriber_.get()); +} + +void CastTransportSenderImpl::InitializeAudio( + const CastTransportAudioConfig& config) { + LOG_IF(WARNING, config.rtp.config.aes_key.empty() || + config.rtp.config.aes_iv_mask.empty()) + << "Unsafe to send audio with encryption DISABLED."; + if (!audio_encryptor_.Initialize(config.rtp.config.aes_key, + config.rtp.config.aes_iv_mask)) { + status_callback_.Run(TRANSPORT_AUDIO_UNINITIALIZED); + return; + } + audio_sender_.reset(new RtpSender(clock_, transport_task_runner_, &pacer_)); + if (audio_sender_->InitializeAudio(config)) { + pacer_.RegisterAudioSsrc(config.rtp.config.ssrc); + status_callback_.Run(TRANSPORT_AUDIO_INITIALIZED); + } else { + audio_sender_.reset(); + status_callback_.Run(TRANSPORT_AUDIO_UNINITIALIZED); + } +} + +void CastTransportSenderImpl::InitializeVideo( + const CastTransportVideoConfig& config) { + LOG_IF(WARNING, config.rtp.config.aes_key.empty() || + config.rtp.config.aes_iv_mask.empty()) + << "Unsafe to send video with encryption DISABLED."; + if (!video_encryptor_.Initialize(config.rtp.config.aes_key, + config.rtp.config.aes_iv_mask)) { + status_callback_.Run(TRANSPORT_VIDEO_UNINITIALIZED); + return; + } + video_sender_.reset(new RtpSender(clock_, transport_task_runner_, &pacer_)); + if (video_sender_->InitializeVideo(config)) { + pacer_.RegisterVideoSsrc(config.rtp.config.ssrc); + status_callback_.Run(TRANSPORT_VIDEO_INITIALIZED); + } else { + video_sender_.reset(); + status_callback_.Run(TRANSPORT_VIDEO_UNINITIALIZED); + } +} + +void CastTransportSenderImpl::SetPacketReceiver( + const PacketReceiverCallback& packet_receiver) { + transport_->StartReceiving(packet_receiver); +} + +namespace { +void EncryptAndSendFrame(const EncodedFrame& frame, + TransportEncryptionHandler* encryptor, + RtpSender* sender) { + if (encryptor->initialized()) { + EncodedFrame encrypted_frame; + frame.CopyMetadataTo(&encrypted_frame); + if (encryptor->Encrypt(frame.frame_id, frame.data, &encrypted_frame.data)) { + sender->SendFrame(encrypted_frame); + } else { + LOG(ERROR) << "Encryption failed. Not sending frame with ID " + << frame.frame_id; + } + } else { + sender->SendFrame(frame); + } +} +} // namespace + +void CastTransportSenderImpl::InsertCodedAudioFrame( + const EncodedFrame& audio_frame) { + DCHECK(audio_sender_) << "Audio sender uninitialized"; + EncryptAndSendFrame(audio_frame, &audio_encryptor_, audio_sender_.get()); +} + +void CastTransportSenderImpl::InsertCodedVideoFrame( + const EncodedFrame& video_frame) { + DCHECK(video_sender_) << "Video sender uninitialized"; + EncryptAndSendFrame(video_frame, &video_encryptor_, video_sender_.get()); +} + +void CastTransportSenderImpl::SendRtcpFromRtpSender( + uint32 packet_type_flags, + uint32 ntp_seconds, + uint32 ntp_fraction, + uint32 rtp_timestamp, + const RtcpDlrrReportBlock& dlrr, + uint32 sending_ssrc, + const std::string& c_name) { + RtcpSenderInfo sender_info; + sender_info.ntp_seconds = ntp_seconds; + sender_info.ntp_fraction = ntp_fraction; + sender_info.rtp_timestamp = rtp_timestamp; + if (audio_sender_ && audio_sender_->ssrc() == sending_ssrc) { + sender_info.send_packet_count = audio_sender_->send_packet_count(); + sender_info.send_octet_count = audio_sender_->send_octet_count(); + } else if (video_sender_ && video_sender_->ssrc() == sending_ssrc) { + sender_info.send_packet_count = video_sender_->send_packet_count(); + sender_info.send_octet_count = video_sender_->send_octet_count(); + } else { + LOG(ERROR) << "Sending RTCP with an invalid SSRC."; + return; + } + rtcp_builder_.SendRtcpFromRtpSender( + packet_type_flags, sender_info, dlrr, sending_ssrc, c_name); +} + +void CastTransportSenderImpl::ResendPackets( + bool is_audio, + const MissingFramesAndPacketsMap& missing_packets, + bool cancel_rtx_if_not_in_list, + base::TimeDelta dedupe_window) { + if (is_audio) { + DCHECK(audio_sender_) << "Audio sender uninitialized"; + audio_sender_->ResendPackets(missing_packets, + cancel_rtx_if_not_in_list, + dedupe_window); + } else { + DCHECK(video_sender_) << "Video sender uninitialized"; + video_sender_->ResendPackets(missing_packets, + cancel_rtx_if_not_in_list, + dedupe_window); + } +} + +void CastTransportSenderImpl::SendRawEvents() { + DCHECK(event_subscriber_.get()); + DCHECK(!raw_events_callback_.is_null()); + std::vector<PacketEvent> packet_events; + event_subscriber_->GetPacketEventsAndReset(&packet_events); + raw_events_callback_.Run(packet_events); +} + +} // namespace transport +} // namespace cast +} // namespace media diff --git a/chromium/media/cast/transport/cast_transport_sender_impl.h b/chromium/media/cast/transport/cast_transport_sender_impl.h new file mode 100644 index 00000000000..035ef844b68 --- /dev/null +++ b/chromium/media/cast/transport/cast_transport_sender_impl.h @@ -0,0 +1,110 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_CAST_TRANSPORT_CAST_TRANSPORT_IMPL_H_ +#define MEDIA_CAST_TRANSPORT_CAST_TRANSPORT_IMPL_H_ + +#include "base/callback.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/time/tick_clock.h" +#include "base/time/time.h" +#include "base/timer/timer.h" +#include "media/cast/logging/logging_defines.h" +#include "media/cast/logging/simple_event_subscriber.h" +#include "media/cast/transport/cast_transport_config.h" +#include "media/cast/transport/cast_transport_sender.h" +#include "media/cast/transport/pacing/paced_sender.h" +#include "media/cast/transport/rtcp/rtcp_builder.h" +#include "media/cast/transport/rtp_sender/rtp_sender.h" +#include "media/cast/transport/utility/transport_encryption_handler.h" + +namespace media { +namespace cast { +namespace transport { + +class CastTransportSenderImpl : public CastTransportSender { + public: + // external_transport is only used for testing. + // Note that SetPacketReceiver does not work if an external + // transport is provided. + // |raw_events_callback|: Raw events will be returned on this callback + // which will be invoked every |raw_events_callback_interval|. + // This can be a null callback, i.e. if user is not interested in raw events. + // |raw_events_callback_interval|: This can be |base::TimeDelta()| if + // |raw_events_callback| is a null callback. + CastTransportSenderImpl( + net::NetLog* net_log, + base::TickClock* clock, + const net::IPEndPoint& remote_end_point, + const CastTransportStatusCallback& status_callback, + const BulkRawEventsCallback& raw_events_callback, + base::TimeDelta raw_events_callback_interval, + const scoped_refptr<base::SingleThreadTaskRunner>& transport_task_runner, + PacketSender* external_transport); + + virtual ~CastTransportSenderImpl(); + + virtual void InitializeAudio(const CastTransportAudioConfig& config) OVERRIDE; + + virtual void InitializeVideo(const CastTransportVideoConfig& config) OVERRIDE; + + // CastTransportSender implementation. + virtual void SetPacketReceiver(const PacketReceiverCallback& packet_receiver) + OVERRIDE; + + virtual void InsertCodedAudioFrame(const EncodedFrame& audio_frame) OVERRIDE; + virtual void InsertCodedVideoFrame(const EncodedFrame& video_frame) OVERRIDE; + + virtual void SendRtcpFromRtpSender(uint32 packet_type_flags, + uint32 ntp_seconds, + uint32 ntp_fraction, + uint32 rtp_timestamp, + const RtcpDlrrReportBlock& dlrr, + uint32 sending_ssrc, + const std::string& c_name) OVERRIDE; + + virtual void ResendPackets(bool is_audio, + const MissingFramesAndPacketsMap& missing_packets, + bool cancel_rtx_if_not_in_list, + base::TimeDelta dedupe_window) + OVERRIDE; + + private: + // If |raw_events_callback_| is non-null, calls it with events collected + // by |event_subscriber_| since last call. + void SendRawEvents(); + + base::TickClock* clock_; // Not owned by this class. + CastTransportStatusCallback status_callback_; + scoped_refptr<base::SingleThreadTaskRunner> transport_task_runner_; + + scoped_ptr<UdpTransport> transport_; + LoggingImpl logging_; + PacedSender pacer_; + RtcpBuilder rtcp_builder_; + scoped_ptr<RtpSender> audio_sender_; + scoped_ptr<RtpSender> video_sender_; + + // Encrypts data in EncodedFrames before they are sent. Note that it's + // important for the encryption to happen here, in code that would execute in + // the main browser process, for security reasons. This helps to mitigate + // the damage that could be caused by a compromised renderer process. + TransportEncryptionHandler audio_encryptor_; + TransportEncryptionHandler video_encryptor_; + + // This is non-null iff |raw_events_callback_| is non-null. + scoped_ptr<SimpleEventSubscriber> event_subscriber_; + base::RepeatingTimer<CastTransportSenderImpl> raw_events_timer_; + + BulkRawEventsCallback raw_events_callback_; + + DISALLOW_COPY_AND_ASSIGN(CastTransportSenderImpl); +}; + +} // namespace transport +} // namespace cast +} // namespace media + +#endif // MEDIA_CAST_TRANSPORT_CAST_TRANSPORT_IMPL_H_ diff --git a/chromium/media/cast/transport/cast_transport_sender_impl_unittest.cc b/chromium/media/cast/transport/cast_transport_sender_impl_unittest.cc new file mode 100644 index 00000000000..67eb39a47aa --- /dev/null +++ b/chromium/media/cast/transport/cast_transport_sender_impl_unittest.cc @@ -0,0 +1,113 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <gtest/gtest.h> +#include <stdint.h> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "base/test/simple_test_tick_clock.h" +#include "media/cast/cast_config.h" +#include "media/cast/rtcp/rtcp.h" +#include "media/cast/test/fake_single_thread_task_runner.h" +#include "media/cast/transport/cast_transport_config.h" +#include "media/cast/transport/cast_transport_sender_impl.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace media { +namespace cast { +namespace transport { + +static const int64 kStartMillisecond = INT64_C(12345678900000); + +class FakePacketSender : public transport::PacketSender { + public: + FakePacketSender() {} + + virtual bool SendPacket(PacketRef packet, const base::Closure& cb) OVERRIDE { + return true; + } +}; + +class CastTransportSenderImplTest : public ::testing::Test { + protected: + CastTransportSenderImplTest() + : num_times_callback_called_(0) { + testing_clock_.Advance( + base::TimeDelta::FromMilliseconds(kStartMillisecond)); + task_runner_ = new test::FakeSingleThreadTaskRunner(&testing_clock_); + } + + virtual ~CastTransportSenderImplTest() {} + + void InitWithoutLogging() { + transport_sender_.reset( + new CastTransportSenderImpl(NULL, + &testing_clock_, + net::IPEndPoint(), + base::Bind(&UpdateCastTransportStatus), + BulkRawEventsCallback(), + base::TimeDelta(), + task_runner_, + &transport_)); + task_runner_->RunTasks(); + } + + void InitWithLogging() { + transport_sender_.reset(new CastTransportSenderImpl( + NULL, + &testing_clock_, + net::IPEndPoint(), + base::Bind(&UpdateCastTransportStatus), + base::Bind(&CastTransportSenderImplTest::LogRawEvents, + base::Unretained(this)), + base::TimeDelta::FromMilliseconds(10), + task_runner_, + &transport_)); + task_runner_->RunTasks(); + } + + void LogRawEvents(const std::vector<PacketEvent>& packet_events) { + num_times_callback_called_++; + if (num_times_callback_called_ == 3) { + run_loop_.Quit(); + } + } + + static void UpdateCastTransportStatus(transport::CastTransportStatus status) { + } + + base::SimpleTestTickClock testing_clock_; + scoped_refptr<test::FakeSingleThreadTaskRunner> task_runner_; + scoped_ptr<CastTransportSenderImpl> transport_sender_; + FakePacketSender transport_; + base::MessageLoopForIO message_loop_; + base::RunLoop run_loop_; + int num_times_callback_called_; +}; + +TEST_F(CastTransportSenderImplTest, InitWithoutLogging) { + InitWithoutLogging(); + message_loop_.PostDelayedTask(FROM_HERE, + run_loop_.QuitClosure(), + base::TimeDelta::FromMilliseconds(50)); + run_loop_.Run(); + EXPECT_EQ(0, num_times_callback_called_); +} + +TEST_F(CastTransportSenderImplTest, InitWithLogging) { + InitWithLogging(); + message_loop_.PostDelayedTask(FROM_HERE, + run_loop_.QuitClosure(), + base::TimeDelta::FromMilliseconds(50)); + run_loop_.Run(); + EXPECT_GT(num_times_callback_called_, 1); +} + +} // namespace transport +} // namespace cast +} // namespace media diff --git a/chromium/media/cast/net/frame_id_wrap_helper_test.cc b/chromium/media/cast/transport/frame_id_wrap_helper_test.cc index f6b89b01d22..3a2060d3aaf 100644 --- a/chromium/media/cast/net/frame_id_wrap_helper_test.cc +++ b/chromium/media/cast/transport/frame_id_wrap_helper_test.cc @@ -3,10 +3,11 @@ // found in the LICENSE file. #include <gtest/gtest.h> -#include "media/cast/net/cast_net_defines.h" +#include "media/cast/transport/cast_transport_defines.h" namespace media { namespace cast { +namespace transport { class FrameIdWrapHelperTest : public ::testing::Test { protected: @@ -14,6 +15,8 @@ class FrameIdWrapHelperTest : public ::testing::Test { virtual ~FrameIdWrapHelperTest() {} FrameIdWrapHelper frame_id_wrap_helper_; + + DISALLOW_COPY_AND_ASSIGN(FrameIdWrapHelperTest); }; TEST_F(FrameIdWrapHelperTest, FirstFrame) { @@ -23,8 +26,8 @@ TEST_F(FrameIdWrapHelperTest, FirstFrame) { TEST_F(FrameIdWrapHelperTest, Rollover) { uint32 new_frame_id = 0u; for (int i = 0; i <= 256; ++i) { - new_frame_id = frame_id_wrap_helper_.MapTo32bitsFrameId( - static_cast<uint8>(i)); + new_frame_id = + frame_id_wrap_helper_.MapTo32bitsFrameId(static_cast<uint8>(i)); } EXPECT_EQ(256u, new_frame_id); } @@ -32,8 +35,8 @@ TEST_F(FrameIdWrapHelperTest, Rollover) { TEST_F(FrameIdWrapHelperTest, OutOfOrder) { uint32 new_frame_id = 0u; for (int i = 0; i < 255; ++i) { - new_frame_id = frame_id_wrap_helper_.MapTo32bitsFrameId( - static_cast<uint8>(i)); + new_frame_id = + frame_id_wrap_helper_.MapTo32bitsFrameId(static_cast<uint8>(i)); } EXPECT_EQ(254u, new_frame_id); new_frame_id = frame_id_wrap_helper_.MapTo32bitsFrameId(0u); @@ -44,5 +47,6 @@ TEST_F(FrameIdWrapHelperTest, OutOfOrder) { EXPECT_EQ(257u, new_frame_id); } +} // namespace transport } // namespace cast } // namespace media diff --git a/chromium/media/cast/net/pacing/mock_paced_packet_sender.cc b/chromium/media/cast/transport/pacing/mock_paced_packet_sender.cc index 6caf8f6390e..5e325f02335 100644 --- a/chromium/media/cast/net/pacing/mock_paced_packet_sender.cc +++ b/chromium/media/cast/transport/pacing/mock_paced_packet_sender.cc @@ -2,16 +2,16 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "media/cast/net/pacing/mock_paced_packet_sender.h" +#include "media/cast/transport/pacing/mock_paced_packet_sender.h" namespace media { namespace cast { +namespace transport { -MockPacedPacketSender::MockPacedPacketSender() { -} +MockPacedPacketSender::MockPacedPacketSender() {} -MockPacedPacketSender::~MockPacedPacketSender() { -} +MockPacedPacketSender::~MockPacedPacketSender() {} +} // namespace transport } // namespace cast } // namespace media diff --git a/chromium/media/cast/transport/pacing/mock_paced_packet_sender.h b/chromium/media/cast/transport/pacing/mock_paced_packet_sender.h new file mode 100644 index 00000000000..20b76470351 --- /dev/null +++ b/chromium/media/cast/transport/pacing/mock_paced_packet_sender.h @@ -0,0 +1,31 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_CAST_TRANSPORT_PACING_MOCK_PACED_PACKET_SENDER_H_ +#define MEDIA_CAST_TRANSPORT_PACING_MOCK_PACED_PACKET_SENDER_H_ + +#include "media/cast/transport/pacing/paced_sender.h" +#include "testing/gmock/include/gmock/gmock.h" + +namespace media { +namespace cast { +namespace transport { + +class MockPacedPacketSender : public PacedPacketSender { + public: + MockPacedPacketSender(); + virtual ~MockPacedPacketSender(); + + MOCK_METHOD1(SendPackets, bool(const SendPacketVector& packets)); + MOCK_METHOD2(ResendPackets, bool(const SendPacketVector& packets, + base::TimeDelta dedupe_window)); + MOCK_METHOD2(SendRtcpPacket, bool(unsigned int ssrc, PacketRef packet)); + MOCK_METHOD1(CancelSendingPacket, void(const PacketKey& packet_key)); +}; + +} // namespace transport +} // namespace cast +} // namespace media + +#endif // MEDIA_CAST_TRANSPORT_PACING_MOCK_PACED_PACKET_SENDER_H_ diff --git a/chromium/media/cast/transport/pacing/paced_sender.cc b/chromium/media/cast/transport/pacing/paced_sender.cc new file mode 100644 index 00000000000..20cbde85be9 --- /dev/null +++ b/chromium/media/cast/transport/pacing/paced_sender.cc @@ -0,0 +1,260 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/cast/transport/pacing/paced_sender.h" + +#include "base/big_endian.h" +#include "base/bind.h" +#include "base/message_loop/message_loop.h" + +namespace media { +namespace cast { +namespace transport { + +namespace { + +static const int64 kPacingIntervalMs = 10; +// Each frame will be split into no more than kPacingMaxBurstsPerFrame +// bursts of packets. +static const size_t kPacingMaxBurstsPerFrame = 3; +static const size_t kTargetBurstSize = 10; +static const size_t kMaxBurstSize = 20; +static const size_t kMaxDedupeWindowMs = 500; + +} // namespace + +// static +PacketKey PacedPacketSender::MakePacketKey(const base::TimeTicks& ticks, + uint32 ssrc, + uint16 packet_id) { + return std::make_pair(ticks, std::make_pair(ssrc, packet_id)); +} + +PacedSender::PacedSender( + base::TickClock* clock, + LoggingImpl* logging, + PacketSender* transport, + const scoped_refptr<base::SingleThreadTaskRunner>& transport_task_runner) + : clock_(clock), + logging_(logging), + transport_(transport), + transport_task_runner_(transport_task_runner), + audio_ssrc_(0), + video_ssrc_(0), + max_burst_size_(kTargetBurstSize), + next_max_burst_size_(kTargetBurstSize), + next_next_max_burst_size_(kTargetBurstSize), + current_burst_size_(0), + state_(State_Unblocked), + weak_factory_(this) { +} + +PacedSender::~PacedSender() {} + +void PacedSender::RegisterAudioSsrc(uint32 audio_ssrc) { + audio_ssrc_ = audio_ssrc; +} + +void PacedSender::RegisterVideoSsrc(uint32 video_ssrc) { + video_ssrc_ = video_ssrc; +} + +bool PacedSender::SendPackets(const SendPacketVector& packets) { + if (packets.empty()) { + return true; + } + for (size_t i = 0; i < packets.size(); i++) { + packet_list_[packets[i].first] = + make_pair(PacketType_Normal, packets[i].second); + } + if (state_ == State_Unblocked) { + SendStoredPackets(); + } + return true; +} + +bool PacedSender::ResendPackets(const SendPacketVector& packets, + base::TimeDelta dedupe_window) { + if (packets.empty()) { + return true; + } + base::TimeTicks now = clock_->NowTicks(); + for (size_t i = 0; i < packets.size(); i++) { + std::map<PacketKey, base::TimeTicks>::const_iterator j = + sent_time_.find(packets[i].first); + + if (j != sent_time_.end() && now - j->second < dedupe_window) { + LogPacketEvent(packets[i].second->data, PACKET_RTX_REJECTED); + continue; + } + + packet_list_[packets[i].first] = + make_pair(PacketType_Resend, packets[i].second); + } + if (state_ == State_Unblocked) { + SendStoredPackets(); + } + return true; +} + +bool PacedSender::SendRtcpPacket(uint32 ssrc, PacketRef packet) { + if (state_ == State_TransportBlocked) { + packet_list_[PacedPacketSender::MakePacketKey(base::TimeTicks(), ssrc, 0)] = + make_pair(PacketType_RTCP, packet); + } else { + // We pass the RTCP packets straight through. + if (!transport_->SendPacket( + packet, + base::Bind(&PacedSender::SendStoredPackets, + weak_factory_.GetWeakPtr()))) { + state_ = State_TransportBlocked; + } + + } + return true; +} + +void PacedSender::CancelSendingPacket(const PacketKey& packet_key) { + packet_list_.erase(packet_key); +} + +PacketRef PacedSender::GetNextPacket(PacketType* packet_type, + PacketKey* packet_key) { + std::map<PacketKey, std::pair<PacketType, PacketRef> >::iterator i; + i = packet_list_.begin(); + DCHECK(i != packet_list_.end()); + *packet_type = i->second.first; + *packet_key = i->first; + PacketRef ret = i->second.second; + packet_list_.erase(i); + return ret; +} + +bool PacedSender::empty() const { + return packet_list_.empty(); +} + +size_t PacedSender::size() const { + return packet_list_.size(); +} + +// This function can be called from three places: +// 1. User called one of the Send* functions and we were in an unblocked state. +// 2. state_ == State_TransportBlocked and the transport is calling us to +// let us know that it's ok to send again. +// 3. state_ == State_BurstFull and there are still packets to send. In this +// case we called PostDelayedTask on this function to start a new burst. +void PacedSender::SendStoredPackets() { + State previous_state = state_; + state_ = State_Unblocked; + if (empty()) { + return; + } + + base::TimeTicks now = clock_->NowTicks(); + // I don't actually trust that PostDelayTask(x - now) will mean that + // now >= x when the call happens, so check if the previous state was + // State_BurstFull too. + if (now >= burst_end_ || previous_state == State_BurstFull) { + // Start a new burst. + current_burst_size_ = 0; + burst_end_ = now + base::TimeDelta::FromMilliseconds(kPacingIntervalMs); + + // The goal here is to try to send out the queued packets over the next + // three bursts, while trying to keep the burst size below 10 if possible. + // We have some evidence that sending more than 12 packets in a row doesn't + // work very well, but we don't actually know why yet. Sending out packets + // sooner is better than sending out packets later as that gives us more + // time to re-send them if needed. So if we have less than 30 packets, just + // send 10 at a time. If we have less than 60 packets, send n / 3 at a time. + // if we have more than 60, we send 20 at a time. 20 packets is ~24Mbit/s + // which is more bandwidth than the cast library should need, and sending + // out more data per second is unlikely to be helpful. + size_t max_burst_size = std::min( + kMaxBurstSize, + std::max(kTargetBurstSize, size() / kPacingMaxBurstsPerFrame)); + + // If the queue is long, issue a warning. Try to limit the number of + // warnings issued by only issuing the warning when the burst size + // grows. Otherwise we might get 100 warnings per second. + if (max_burst_size > next_next_max_burst_size_ && size() > 100) { + LOG(WARNING) << "Packet queue is very long:" << size(); + } + + max_burst_size_ = std::max(next_max_burst_size_, max_burst_size); + next_max_burst_size_ = std::max(next_next_max_burst_size_, max_burst_size); + next_next_max_burst_size_ = max_burst_size; + } + + base::Closure cb = base::Bind(&PacedSender::SendStoredPackets, + weak_factory_.GetWeakPtr()); + while (!empty()) { + if (current_burst_size_ >= max_burst_size_) { + transport_task_runner_->PostDelayedTask(FROM_HERE, + cb, + burst_end_ - now); + state_ = State_BurstFull; + return; + } + PacketType packet_type; + PacketKey packet_key; + PacketRef packet = GetNextPacket(&packet_type, &packet_key); + sent_time_[packet_key] = now; + sent_time_buffer_[packet_key] = now; + + switch (packet_type) { + case PacketType_Resend: + LogPacketEvent(packet->data, PACKET_RETRANSMITTED); + break; + case PacketType_Normal: + LogPacketEvent(packet->data, PACKET_SENT_TO_NETWORK); + break; + case PacketType_RTCP: + break; + } + if (!transport_->SendPacket(packet, cb)) { + state_ = State_TransportBlocked; + return; + } + current_burst_size_++; + } + // Keep ~0.5 seconds of data (1000 packets) + if (sent_time_buffer_.size() >= + kMaxBurstSize * kMaxDedupeWindowMs / kPacingIntervalMs) { + sent_time_.swap(sent_time_buffer_); + sent_time_buffer_.clear(); + } + DCHECK_LE(sent_time_buffer_.size(), + kMaxBurstSize * kMaxDedupeWindowMs / kPacingIntervalMs); + DCHECK_LE(sent_time_.size(), + 2 * kMaxBurstSize * kMaxDedupeWindowMs / kPacingIntervalMs); + state_ = State_Unblocked; +} + +void PacedSender::LogPacketEvent(const Packet& packet, CastLoggingEvent event) { + // Get SSRC from packet and compare with the audio_ssrc / video_ssrc to see + // if the packet is audio or video. + DCHECK_GE(packet.size(), 12u); + base::BigEndianReader reader(reinterpret_cast<const char*>(&packet[8]), 4); + uint32 ssrc; + bool success = reader.ReadU32(&ssrc); + DCHECK(success); + bool is_audio; + if (ssrc == audio_ssrc_) { + is_audio = true; + } else if (ssrc == video_ssrc_) { + is_audio = false; + } else { + DVLOG(3) << "Got unknown ssrc " << ssrc << " when logging packet event"; + return; + } + + EventMediaType media_type = is_audio ? AUDIO_EVENT : VIDEO_EVENT; + logging_->InsertSinglePacketEvent(clock_->NowTicks(), event, media_type, + packet); +} + +} // namespace transport +} // namespace cast +} // namespace media diff --git a/chromium/media/cast/transport/pacing/paced_sender.h b/chromium/media/cast/transport/pacing/paced_sender.h new file mode 100644 index 00000000000..9fc0c8b8b85 --- /dev/null +++ b/chromium/media/cast/transport/pacing/paced_sender.h @@ -0,0 +1,147 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_CAST_TRANSPORT_PACING_PACED_SENDER_H_ +#define MEDIA_CAST_TRANSPORT_PACING_PACED_SENDER_H_ + +#include <list> +#include <vector> + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/single_thread_task_runner.h" +#include "base/threading/non_thread_safe.h" +#include "base/time/default_tick_clock.h" +#include "base/time/tick_clock.h" +#include "base/time/time.h" +#include "media/cast/transport/cast_transport_config.h" +#include "media/cast/transport/transport/udp_transport.h" + +namespace media { +namespace cast { + +class LoggingImpl; + +namespace transport { + +// Use std::pair for free comparison operators. +// { capture_time, ssrc, packet_id } +// The PacketKey is designed to meet two criteria: +// 1. When we re-send the same packet again, we can use the packet key +// to identify it so that we can de-duplicate packets in the queue. +// 2. The sort order of the PacketKey determines the order that packets +// are sent out. Using the capture_time as the first member basically +// means that older packets are sent first. +typedef std::pair<base::TimeTicks, std::pair<uint32, uint16> > PacketKey; +typedef std::vector<std::pair<PacketKey, PacketRef> > SendPacketVector; + +// We have this pure virtual class to enable mocking. +class PacedPacketSender { + public: + virtual bool SendPackets(const SendPacketVector& packets) = 0; + virtual bool ResendPackets(const SendPacketVector& packets, + base::TimeDelta dedupe_window) = 0; + virtual bool SendRtcpPacket(uint32 ssrc, PacketRef packet) = 0; + virtual void CancelSendingPacket(const PacketKey& packet_key) = 0; + + virtual ~PacedPacketSender() {} + + static PacketKey MakePacketKey(const base::TimeTicks& ticks, + uint32 ssrc, + uint16 packet_id); +}; + +class PacedSender : public PacedPacketSender, + public base::NonThreadSafe, + public base::SupportsWeakPtr<PacedSender> { + public: + // The |external_transport| should only be used by the Cast receiver and for + // testing. + PacedSender( + base::TickClock* clock, + LoggingImpl* logging, + PacketSender* external_transport, + const scoped_refptr<base::SingleThreadTaskRunner>& transport_task_runner); + + virtual ~PacedSender(); + + // These must be called before non-RTCP packets are sent. + void RegisterAudioSsrc(uint32 audio_ssrc); + void RegisterVideoSsrc(uint32 video_ssrc); + + // PacedPacketSender implementation. + virtual bool SendPackets(const SendPacketVector& packets) OVERRIDE; + virtual bool ResendPackets(const SendPacketVector& packets, + base::TimeDelta dedupe_window) OVERRIDE; + virtual bool SendRtcpPacket(uint32 ssrc, PacketRef packet) OVERRIDE; + virtual void CancelSendingPacket(const PacketKey& packet_key) OVERRIDE; + + private: + // Actually sends the packets to the transport. + void SendStoredPackets(); + void LogPacketEvent(const Packet& packet, CastLoggingEvent event); + + enum PacketType { + PacketType_RTCP, + PacketType_Resend, + PacketType_Normal + }; + enum State { + // In an unblocked state, we can send more packets. + // We have to check the current time against |burst_end_| to see if we are + // appending to the current burst or if we can start a new one. + State_Unblocked, + // In this state, we are waiting for a callback from the udp transport. + // This happens when the OS-level buffer is full. Once we receive the + // callback, we go to State_Unblocked and see if we can write more packets + // to the current burst. (Or the next burst if enough time has passed.) + State_TransportBlocked, + // Once we've written enough packets for a time slice, we go into this + // state and PostDelayTask a call to ourselves to wake up when we can + // send more data. + State_BurstFull + }; + + bool empty() const; + size_t size() const; + + // Returns the next packet to send. RTCP packets have highest priority, + // resend packets have second highest priority and then comes everything + // else. + PacketRef GetNextPacket(PacketType* packet_type, + PacketKey* packet_key); + + base::TickClock* const clock_; // Not owned by this class. + LoggingImpl* const logging_; // Not owned by this class. + PacketSender* transport_; // Not owned by this class. + scoped_refptr<base::SingleThreadTaskRunner> transport_task_runner_; + uint32 audio_ssrc_; + uint32 video_ssrc_; + std::map<PacketKey, std::pair<PacketType, PacketRef> > packet_list_; + std::map<PacketKey, base::TimeTicks> sent_time_; + std::map<PacketKey, base::TimeTicks> sent_time_buffer_; + + // Maximum burst size for the next three bursts. + size_t max_burst_size_; + size_t next_max_burst_size_; + size_t next_next_max_burst_size_; + // Number of packets already sent in the current burst. + size_t current_burst_size_; + // This is when the current burst ends. + base::TimeTicks burst_end_; + + State state_; + + // NOTE: Weak pointers must be invalidated before all other member variables. + base::WeakPtrFactory<PacedSender> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(PacedSender); +}; + +} // namespace transport +} // namespace cast +} // namespace media + +#endif // MEDIA_CAST_TRANSPORT_PACING_PACED_SENDER_H_ diff --git a/chromium/media/cast/transport/pacing/paced_sender_unittest.cc b/chromium/media/cast/transport/pacing/paced_sender_unittest.cc new file mode 100644 index 00000000000..5e24fca4b56 --- /dev/null +++ b/chromium/media/cast/transport/pacing/paced_sender_unittest.cc @@ -0,0 +1,351 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <stdint.h> + +#include "base/big_endian.h" +#include "base/test/simple_test_tick_clock.h" +#include "media/cast/logging/simple_event_subscriber.h" +#include "media/cast/test/fake_single_thread_task_runner.h" +#include "media/cast/transport/pacing/paced_sender.h" +#include "testing/gmock/include/gmock/gmock.h" + +namespace media { +namespace cast { +namespace transport { + +using testing::_; + +static const uint8 kValue = 123; +static const size_t kSize1 = 100; +static const size_t kSize2 = 101; +static const size_t kSize3 = 102; +static const size_t kSize4 = 103; +static const size_t kNackSize = 104; +static const int64 kStartMillisecond = INT64_C(12345678900000); +static const uint32 kVideoSsrc = 0x1234; +static const uint32 kAudioSsrc = 0x5678; + +class TestPacketSender : public PacketSender { + public: + TestPacketSender() {} + + virtual bool SendPacket(PacketRef packet, const base::Closure& cb) OVERRIDE { + EXPECT_FALSE(expected_packet_size_.empty()); + size_t expected_packet_size = expected_packet_size_.front(); + expected_packet_size_.pop_front(); + EXPECT_EQ(expected_packet_size, packet->data.size()); + return true; + } + + void AddExpectedSize(int expected_packet_size, int repeat_count) { + for (int i = 0; i < repeat_count; ++i) { + expected_packet_size_.push_back(expected_packet_size); + } + } + + public: + std::list<int> expected_packet_size_; + + DISALLOW_COPY_AND_ASSIGN(TestPacketSender); +}; + +class PacedSenderTest : public ::testing::Test { + protected: + PacedSenderTest() { + logging_.AddRawEventSubscriber(&subscriber_); + testing_clock_.Advance( + base::TimeDelta::FromMilliseconds(kStartMillisecond)); + task_runner_ = new test::FakeSingleThreadTaskRunner(&testing_clock_); + paced_sender_.reset(new PacedSender( + &testing_clock_, &logging_, &mock_transport_, task_runner_)); + paced_sender_->RegisterAudioSsrc(kAudioSsrc); + paced_sender_->RegisterVideoSsrc(kVideoSsrc); + } + + virtual ~PacedSenderTest() { + logging_.RemoveRawEventSubscriber(&subscriber_); + } + + static void UpdateCastTransportStatus(transport::CastTransportStatus status) { + NOTREACHED(); + } + + SendPacketVector CreateSendPacketVector(size_t packet_size, + int num_of_packets_in_frame, + bool audio) { + DCHECK_GE(packet_size, 12u); + SendPacketVector packets; + base::TimeTicks frame_tick = testing_clock_.NowTicks(); + // Advance the clock so that we don't get the same frame_tick + // next time this function is called. + testing_clock_.Advance(base::TimeDelta::FromMilliseconds(1)); + for (int i = 0; i < num_of_packets_in_frame; ++i) { + PacketKey key = PacedPacketSender::MakePacketKey( + frame_tick, + audio ? kAudioSsrc : kVideoSsrc, // ssrc + i); + + PacketRef packet(new base::RefCountedData<Packet>); + packet->data.resize(packet_size, kValue); + // Write ssrc to packet so that it can be recognized as a + // "video frame" for logging purposes. + base::BigEndianWriter writer( + reinterpret_cast<char*>(&packet->data[8]), 4); + bool success = writer.WriteU32(audio ? kAudioSsrc : kVideoSsrc); + DCHECK(success); + packets.push_back(std::make_pair(key, packet)); + } + return packets; + } + + // Use this function to drain the packet list in PacedSender without having + // to test the pacing implementation details. + bool RunUntilEmpty(int max_tries) { + for (int i = 0; i < max_tries; i++) { + testing_clock_.Advance(base::TimeDelta::FromMilliseconds(10)); + task_runner_->RunTasks(); + if (mock_transport_.expected_packet_size_.empty()) + return true; + i++; + } + + return mock_transport_.expected_packet_size_.empty(); + } + + LoggingImpl logging_; + SimpleEventSubscriber subscriber_; + base::SimpleTestTickClock testing_clock_; + TestPacketSender mock_transport_; + scoped_refptr<test::FakeSingleThreadTaskRunner> task_runner_; + scoped_ptr<PacedSender> paced_sender_; + + DISALLOW_COPY_AND_ASSIGN(PacedSenderTest); +}; + +TEST_F(PacedSenderTest, PassThroughRtcp) { + mock_transport_.AddExpectedSize(kSize1, 2); + SendPacketVector packets = CreateSendPacketVector(kSize1, 1, true); + + EXPECT_TRUE(paced_sender_->SendPackets(packets)); + EXPECT_TRUE(paced_sender_->ResendPackets(packets, base::TimeDelta())); + + mock_transport_.AddExpectedSize(kSize2, 1); + Packet tmp(kSize2, kValue); + EXPECT_TRUE(paced_sender_->SendRtcpPacket( + 1, + new base::RefCountedData<Packet>(tmp))); +} + +TEST_F(PacedSenderTest, BasicPace) { + int num_of_packets = 27; + SendPacketVector packets = CreateSendPacketVector(kSize1, + num_of_packets, + false); + + mock_transport_.AddExpectedSize(kSize1, 10); + EXPECT_TRUE(paced_sender_->SendPackets(packets)); + + // Check that we get the next burst. + mock_transport_.AddExpectedSize(kSize1, 10); + + base::TimeDelta timeout = base::TimeDelta::FromMilliseconds(10); + testing_clock_.Advance(timeout); + task_runner_->RunTasks(); + + // If we call process too early make sure we don't send any packets. + timeout = base::TimeDelta::FromMilliseconds(5); + testing_clock_.Advance(timeout); + task_runner_->RunTasks(); + + // Check that we get the next burst. + mock_transport_.AddExpectedSize(kSize1, 7); + testing_clock_.Advance(timeout); + task_runner_->RunTasks(); + + // Check that we don't get any more packets. + EXPECT_TRUE(RunUntilEmpty(3)); + + std::vector<PacketEvent> packet_events; + subscriber_.GetPacketEventsAndReset(&packet_events); + EXPECT_EQ(num_of_packets, static_cast<int>(packet_events.size())); + int sent_to_network_event_count = 0; + for (std::vector<PacketEvent>::iterator it = packet_events.begin(); + it != packet_events.end(); + ++it) { + if (it->type == PACKET_SENT_TO_NETWORK) + sent_to_network_event_count++; + else + FAIL() << "Got unexpected event type " << CastLoggingToString(it->type); + } + EXPECT_EQ(num_of_packets, sent_to_network_event_count); +} + +TEST_F(PacedSenderTest, PaceWithNack) { + // Testing what happen when we get multiple NACK requests for a fully lost + // frames just as we sent the first packets in a frame. + int num_of_packets_in_frame = 12; + int num_of_packets_in_nack = 12; + + SendPacketVector nack_packets = + CreateSendPacketVector(kNackSize, num_of_packets_in_nack, false); + + SendPacketVector first_frame_packets = + CreateSendPacketVector(kSize1, num_of_packets_in_frame, false); + + SendPacketVector second_frame_packets = + CreateSendPacketVector(kSize2, num_of_packets_in_frame, true); + + // Check that the first burst of the frame go out on the wire. + mock_transport_.AddExpectedSize(kSize1, 10); + EXPECT_TRUE(paced_sender_->SendPackets(first_frame_packets)); + + // Add first NACK request. + EXPECT_TRUE(paced_sender_->ResendPackets(nack_packets, base::TimeDelta())); + + // Check that we get the first NACK burst. + mock_transport_.AddExpectedSize(kNackSize, 10); + base::TimeDelta timeout = base::TimeDelta::FromMilliseconds(10); + testing_clock_.Advance(timeout); + task_runner_->RunTasks(); + + // Add second NACK request. + EXPECT_TRUE(paced_sender_->ResendPackets(nack_packets, base::TimeDelta())); + + // Check that we get the next NACK burst. + mock_transport_.AddExpectedSize(kNackSize, 10); + testing_clock_.Advance(timeout); + task_runner_->RunTasks(); + + // End of NACK plus two packets from the oldest frame. + // Note that two of the NACKs have been de-duped. + mock_transport_.AddExpectedSize(kNackSize, 2); + mock_transport_.AddExpectedSize(kSize1, 2); + testing_clock_.Advance(timeout); + task_runner_->RunTasks(); + + // Add second frame. + // Make sure we don't delay the second frame due to the previous packets. + mock_transport_.AddExpectedSize(kSize2, 10); + EXPECT_TRUE(paced_sender_->SendPackets(second_frame_packets)); + + // Last packets of frame 2. + mock_transport_.AddExpectedSize(kSize2, 2); + testing_clock_.Advance(timeout); + task_runner_->RunTasks(); + + // No more packets. + EXPECT_TRUE(RunUntilEmpty(5)); + + std::vector<PacketEvent> packet_events; + subscriber_.GetPacketEventsAndReset(&packet_events); + int expected_video_network_event_count = num_of_packets_in_frame; + int expected_video_retransmitted_event_count = 2 * num_of_packets_in_nack; + expected_video_retransmitted_event_count -= 2; // 2 packets deduped + int expected_audio_network_event_count = num_of_packets_in_frame; + EXPECT_EQ(expected_video_network_event_count + + expected_video_retransmitted_event_count + + expected_audio_network_event_count, + static_cast<int>(packet_events.size())); + int audio_network_event_count = 0; + int video_network_event_count = 0; + int video_retransmitted_event_count = 0; + for (std::vector<PacketEvent>::iterator it = packet_events.begin(); + it != packet_events.end(); + ++it) { + if (it->type == PACKET_SENT_TO_NETWORK) { + if (it->media_type == VIDEO_EVENT) + video_network_event_count++; + else + audio_network_event_count++; + } else if (it->type == PACKET_RETRANSMITTED) { + if (it->media_type == VIDEO_EVENT) + video_retransmitted_event_count++; + } else { + FAIL() << "Got unexpected event type " << CastLoggingToString(it->type); + } + } + EXPECT_EQ(expected_audio_network_event_count, audio_network_event_count); + EXPECT_EQ(expected_video_network_event_count, video_network_event_count); + EXPECT_EQ(expected_video_retransmitted_event_count, + video_retransmitted_event_count); +} + +TEST_F(PacedSenderTest, PaceWith60fps) { + // Testing what happen when we get multiple NACK requests for a fully lost + // frames just as we sent the first packets in a frame. + int num_of_packets_in_frame = 17; + + SendPacketVector first_frame_packets = + CreateSendPacketVector(kSize1, num_of_packets_in_frame, false); + + SendPacketVector second_frame_packets = + CreateSendPacketVector(kSize2, num_of_packets_in_frame, false); + + SendPacketVector third_frame_packets = + CreateSendPacketVector(kSize3, num_of_packets_in_frame, false); + + SendPacketVector fourth_frame_packets = + CreateSendPacketVector(kSize4, num_of_packets_in_frame, false); + + base::TimeDelta timeout_10ms = base::TimeDelta::FromMilliseconds(10); + + // Check that the first burst of the frame go out on the wire. + mock_transport_.AddExpectedSize(kSize1, 10); + EXPECT_TRUE(paced_sender_->SendPackets(first_frame_packets)); + + mock_transport_.AddExpectedSize(kSize1, 7); + testing_clock_.Advance(timeout_10ms); + task_runner_->RunTasks(); + + testing_clock_.Advance(base::TimeDelta::FromMilliseconds(6)); + + // Add second frame, after 16 ms. + mock_transport_.AddExpectedSize(kSize2, 3); + EXPECT_TRUE(paced_sender_->SendPackets(second_frame_packets)); + testing_clock_.Advance(base::TimeDelta::FromMilliseconds(4)); + + mock_transport_.AddExpectedSize(kSize2, 10); + testing_clock_.Advance(timeout_10ms); + task_runner_->RunTasks(); + + mock_transport_.AddExpectedSize(kSize2, 4); + testing_clock_.Advance(timeout_10ms); + task_runner_->RunTasks(); + + testing_clock_.Advance(base::TimeDelta::FromMilliseconds(3)); + + // Add third frame, after 33 ms. + mock_transport_.AddExpectedSize(kSize3, 6); + EXPECT_TRUE(paced_sender_->SendPackets(third_frame_packets)); + + mock_transport_.AddExpectedSize(kSize3, 10); + testing_clock_.Advance(base::TimeDelta::FromMilliseconds(7)); + task_runner_->RunTasks(); + + // Add fourth frame, after 50 ms. + EXPECT_TRUE(paced_sender_->SendPackets(fourth_frame_packets)); + + mock_transport_.AddExpectedSize(kSize3, 1); + mock_transport_.AddExpectedSize(kSize4, 9); + testing_clock_.Advance(timeout_10ms); + task_runner_->RunTasks(); + + mock_transport_.AddExpectedSize(kSize4, 8); + testing_clock_.Advance(timeout_10ms); + task_runner_->RunTasks(); + + testing_clock_.Advance(timeout_10ms); + task_runner_->RunTasks(); + + testing_clock_.Advance(timeout_10ms); + task_runner_->RunTasks(); + + // No more packets. + EXPECT_TRUE(RunUntilEmpty(5)); +} + +} // namespace transport +} // namespace cast +} // namespace media diff --git a/chromium/media/cast/transport/rtcp/rtcp_builder.cc b/chromium/media/cast/transport/rtcp/rtcp_builder.cc new file mode 100644 index 00000000000..b8875fc96bd --- /dev/null +++ b/chromium/media/cast/transport/rtcp/rtcp_builder.cc @@ -0,0 +1,197 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/cast/transport/rtcp/rtcp_builder.h" + +#include <algorithm> +#include <string> +#include <vector> + +#include "base/big_endian.h" +#include "base/logging.h" +#include "media/cast/transport/cast_transport_defines.h" +#include "media/cast/transport/pacing/paced_sender.h" + +namespace media { +namespace cast { +namespace transport { + +RtcpBuilder::RtcpBuilder(PacedSender* const outgoing_transport) + : transport_(outgoing_transport), + ssrc_(0) { +} + +RtcpBuilder::~RtcpBuilder() {} + +void RtcpBuilder::SendRtcpFromRtpSender( + uint32 packet_type_flags, + const RtcpSenderInfo& sender_info, + const RtcpDlrrReportBlock& dlrr, + uint32 sending_ssrc, + const std::string& c_name) { + if (packet_type_flags & kRtcpRr || + packet_type_flags & kRtcpPli || + packet_type_flags & kRtcpRrtr || + packet_type_flags & kRtcpCast || + packet_type_flags & kRtcpReceiverLog || + packet_type_flags & kRtcpRpsi || + packet_type_flags & kRtcpRemb || + packet_type_flags & kRtcpNack) { + NOTREACHED() << "Invalid argument"; + } + ssrc_ = sending_ssrc; + c_name_ = c_name; + PacketRef packet(new base::RefCountedData<Packet>); + packet->data.reserve(kMaxIpPacketSize); + if (packet_type_flags & kRtcpSr) { + if (!BuildSR(sender_info, &packet->data)) return; + if (!BuildSdec(&packet->data)) return; + } + if (packet_type_flags & kRtcpBye) { + if (!BuildBye(&packet->data)) return; + } + if (packet_type_flags & kRtcpDlrr) { + if (!BuildDlrrRb(dlrr, &packet->data)) return; + } + if (packet->data.empty()) + return; // Sanity - don't send empty packets. + + transport_->SendRtcpPacket(ssrc_, packet); +} + +bool RtcpBuilder::BuildSR(const RtcpSenderInfo& sender_info, + Packet* packet) const { + // Sender report. + size_t start_size = packet->size(); + if (start_size + 52 > kMaxIpPacketSize) { + DLOG(FATAL) << "Not enough buffer space"; + return false; + } + + uint16 number_of_rows = 6; + packet->resize(start_size + 28); + + base::BigEndianWriter big_endian_writer( + reinterpret_cast<char*>(&((*packet)[start_size])), 28); + big_endian_writer.WriteU8(0x80); + big_endian_writer.WriteU8(kPacketTypeSenderReport); + big_endian_writer.WriteU16(number_of_rows); + big_endian_writer.WriteU32(ssrc_); + big_endian_writer.WriteU32(sender_info.ntp_seconds); + big_endian_writer.WriteU32(sender_info.ntp_fraction); + big_endian_writer.WriteU32(sender_info.rtp_timestamp); + big_endian_writer.WriteU32(sender_info.send_packet_count); + big_endian_writer.WriteU32(static_cast<uint32>(sender_info.send_octet_count)); + return true; +} + +bool RtcpBuilder::BuildSdec(Packet* packet) const { + size_t start_size = packet->size(); + if (start_size + 12 + c_name_.length() > kMaxIpPacketSize) { + DLOG(FATAL) << "Not enough buffer space"; + return false; + } + + // SDES Source Description. + packet->resize(start_size + 10); + + base::BigEndianWriter big_endian_writer( + reinterpret_cast<char*>(&((*packet)[start_size])), 10); + // We always need to add one SDES CNAME. + big_endian_writer.WriteU8(0x80 + 1); + big_endian_writer.WriteU8(kPacketTypeSdes); + + // Handle SDES length later on. + uint32 sdes_length_position = static_cast<uint32>(start_size) + 3; + big_endian_writer.WriteU16(0); + big_endian_writer.WriteU32(ssrc_); // Add our own SSRC. + big_endian_writer.WriteU8(1); // CNAME = 1 + big_endian_writer.WriteU8(static_cast<uint8>(c_name_.length())); + + size_t sdes_length = 10 + c_name_.length(); + packet->insert(packet->end(), c_name_.c_str(), + c_name_.c_str() + c_name_.length()); + + size_t padding = 0; + + // We must have a zero field even if we have an even multiple of 4 bytes. + if ((packet->size() % 4) == 0) { + padding++; + packet->push_back(0); + } + while ((packet->size() % 4) != 0) { + padding++; + packet->push_back(0); + } + sdes_length += padding; + + // In 32-bit words minus one and we don't count the header. + uint8 buffer_length = static_cast<uint8>((sdes_length / 4) - 1); + (*packet)[sdes_length_position] = buffer_length; + return true; +} + +bool RtcpBuilder::BuildBye(Packet* packet) const { + size_t start_size = packet->size(); + if (start_size + 8 > kMaxIpPacketSize) { + DLOG(FATAL) << "Not enough buffer space"; + return false; + } + + packet->resize(start_size + 8); + + base::BigEndianWriter big_endian_writer( + reinterpret_cast<char*>(&((*packet)[start_size])), 8); + big_endian_writer.WriteU8(0x80 + 1); + big_endian_writer.WriteU8(kPacketTypeBye); + big_endian_writer.WriteU16(1); // Length. + big_endian_writer.WriteU32(ssrc_); // Add our own SSRC. + return true; +} + +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |V=2|P|reserved | PT=XR=207 | length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | BT=5 | reserved | block length | + +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ + | SSRC_1 (SSRC of first receiver) | sub- + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ block + | last RR (LRR) | 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | delay since last RR (DLRR) | + +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +*/ +bool RtcpBuilder::BuildDlrrRb(const RtcpDlrrReportBlock& dlrr, + Packet* packet) const { + size_t start_size = packet->size(); + if (start_size + 24 > kMaxIpPacketSize) { + DLOG(FATAL) << "Not enough buffer space"; + return false; + } + + packet->resize(start_size + 24); + + base::BigEndianWriter big_endian_writer( + reinterpret_cast<char*>(&((*packet)[start_size])), 24); + big_endian_writer.WriteU8(0x80); + big_endian_writer.WriteU8(kPacketTypeXr); + big_endian_writer.WriteU16(5); // Length. + big_endian_writer.WriteU32(ssrc_); // Add our own SSRC. + big_endian_writer.WriteU8(5); // Add block type. + big_endian_writer.WriteU8(0); // Add reserved. + big_endian_writer.WriteU16(3); // Block length. + big_endian_writer.WriteU32(ssrc_); // Add the media (received RTP) SSRC. + big_endian_writer.WriteU32(dlrr.last_rr); + big_endian_writer.WriteU32(dlrr.delay_since_last_rr); + return true; +} + +} // namespace transport +} // namespace cast +} // namespace media diff --git a/chromium/media/cast/transport/rtcp/rtcp_builder.h b/chromium/media/cast/transport/rtcp/rtcp_builder.h new file mode 100644 index 00000000000..f095ae9ee54 --- /dev/null +++ b/chromium/media/cast/transport/rtcp/rtcp_builder.h @@ -0,0 +1,49 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_CAST_TRANSPORT_RTCP_RTCP_BUILDER_H_ +#define MEDIA_CAST_TRANSPORT_RTCP_RTCP_BUILDER_H_ + +#include <list> +#include <string> +#include <vector> + +#include "media/cast/transport/cast_transport_defines.h" +#include "media/cast/transport/pacing/paced_sender.h" + +namespace media { +namespace cast { +namespace transport { + +class RtcpBuilder { + public: + explicit RtcpBuilder(PacedSender* const paced_packet_sender); + + virtual ~RtcpBuilder(); + + void SendRtcpFromRtpSender(uint32 packet_type_flags, + const RtcpSenderInfo& sender_info, + const RtcpDlrrReportBlock& dlrr, + uint32 ssrc, + const std::string& c_name); + + private: + bool BuildSR(const RtcpSenderInfo& sender_info, Packet* packet) const; + bool BuildSdec(Packet* packet) const; + bool BuildBye(Packet* packet) const; + bool BuildDlrrRb(const RtcpDlrrReportBlock& dlrr, + Packet* packet) const; + + PacedSender* const transport_; // Not owned by this class. + uint32 ssrc_; + std::string c_name_; + + DISALLOW_COPY_AND_ASSIGN(RtcpBuilder); +}; + +} // namespace transport +} // namespace cast +} // namespace media + +#endif // MEDIA_CAST_TRANSPORT_RTCP_RTCP_BUILDER_H_ diff --git a/chromium/media/cast/transport/rtcp/rtcp_builder_unittest.cc b/chromium/media/cast/transport/rtcp/rtcp_builder_unittest.cc new file mode 100644 index 00000000000..0322612f27e --- /dev/null +++ b/chromium/media/cast/transport/rtcp/rtcp_builder_unittest.cc @@ -0,0 +1,164 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/memory/scoped_ptr.h" +#include "base/test/simple_test_tick_clock.h" +#include "media/cast/cast_defines.h" +#include "media/cast/cast_environment.h" +#include "media/cast/rtcp/rtcp_utility.h" +#include "media/cast/rtcp/test_rtcp_packet_builder.h" +#include "media/cast/test/fake_single_thread_task_runner.h" +#include "media/cast/transport/pacing/paced_sender.h" +#include "media/cast/transport/rtcp/rtcp_builder.h" +#include "testing/gmock/include/gmock/gmock.h" + +namespace media { +namespace cast { + +namespace { +static const uint32 kSendingSsrc = 0x12345678; +static const std::string kCName("test@10.1.1.1"); +} // namespace + +class TestRtcpTransport : public PacedPacketSender { + public: + TestRtcpTransport() + : expected_packet_length_(0), + packet_count_(0) { + } + + virtual bool SendRtcpPacket(const Packet& packet) OVERRIDE { + EXPECT_EQ(expected_packet_length_, packet.size()); + EXPECT_EQ(0, memcmp(expected_packet_, &(packet[0]), packet.size())); + packet_count_++; + return true; + } + + virtual bool SendPackets(const PacketList& packets) OVERRIDE { + return false; + } + + virtual bool ResendPackets(const PacketList& packets) OVERRIDE { + return false; + } + + void SetExpectedRtcpPacket(const uint8* rtcp_buffer, size_t length) { + expected_packet_length_ = length; + memcpy(expected_packet_, rtcp_buffer, length); + } + + int packet_count() const { return packet_count_; } + + private: + uint8 expected_packet_[kMaxIpPacketSize]; + size_t expected_packet_length_; + int packet_count_; +}; + +class RtcpBuilderTest : public ::testing::Test { + protected: + RtcpBuilderTest() + : task_runner_(new test::FakeSingleThreadTaskRunner(&testing_clock_)), + cast_environment_(new CastEnvironment(&testing_clock_, task_runner_, + task_runner_, task_runner_, task_runner_, task_runner_, + GetDefaultCastSenderLoggingConfig())), + rtcp_builder_(new RtcpBuilder(&test_transport_, kSendingSsrc, kCName)) { + } + + base::SimpleTestTickClock testing_clock_; + TestRtcpTransport test_transport_; + scoped_refptr<test::FakeSingleThreadTaskRunner> task_runner_; + scoped_refptr<CastEnvironment> cast_environment_; + scoped_ptr<RtcpBuilder> rtcp_builder_; +}; + +TEST_F(RtcpBuilderTest, RtcpSenderReport) { + RtcpSenderInfo sender_info; + sender_info.ntp_seconds = kNtpHigh; + sender_info.ntp_fraction = kNtpLow; + sender_info.rtp_timestamp = kRtpTimestamp; + sender_info.send_packet_count = kSendPacketCount; + sender_info.send_octet_count = kSendOctetCount; + + // Sender report + c_name. + TestRtcpPacketBuilder p; + p.AddSr(kSendingSsrc, 0); + p.AddSdesCname(kSendingSsrc, kCName); + test_transport_.SetExpectedRtcpPacket(p.Packet(), p.Length()); + + rtcp_builder_->SendRtcpFromRtpSender(RtcpBuilder::kRtcpSr, + &sender_info, + NULL, + NULL, + kSendingSsrc, + kCName); + + EXPECT_EQ(1, test_transport_.packet_count()); +} + +TEST_F(RtcpBuilderTest, RtcpSenderReportWithDlrr) { + RtcpSenderInfo sender_info; + sender_info.ntp_seconds = kNtpHigh; + sender_info.ntp_fraction = kNtpLow; + sender_info.rtp_timestamp = kRtpTimestamp; + sender_info.send_packet_count = kSendPacketCount; + sender_info.send_octet_count = kSendOctetCount; + + // Sender report + c_name + dlrr. + TestRtcpPacketBuilder p1; + p1.AddSr(kSendingSsrc, 0); + p1.AddSdesCname(kSendingSsrc, kCName); + p1.AddXrHeader(kSendingSsrc); + p1.AddXrDlrrBlock(kSendingSsrc); + test_transport_.SetExpectedRtcpPacket(p1.Packet(), p1.Length()); + + RtcpDlrrReportBlock dlrr_rb; + dlrr_rb.last_rr = kLastRr; + dlrr_rb.delay_since_last_rr = kDelayLastRr; + + rtcp_builder_->SendRtcpFromRtpSender( + RtcpBuilder::kRtcpSr | RtcpBuilder::kRtcpDlrr, + &sender_info, + &dlrr_rb, + NULL, + kSendingSsrc, + kCName); + + EXPECT_EQ(1, test_transport_.packet_count()); +} + +TEST_F(RtcpBuilderTest, RtcpSenderReportWithDlrr) { + RtcpSenderInfo sender_info; + sender_info.ntp_seconds = kNtpHigh; + sender_info.ntp_fraction = kNtpLow; + sender_info.rtp_timestamp = kRtpTimestamp; + sender_info.send_packet_count = kSendPacketCount; + sender_info.send_octet_count = kSendOctetCount; + + // Sender report + c_name + dlrr + sender log. + TestRtcpPacketBuilder p; + p.AddSr(kSendingSsrc, 0); + p.AddSdesCname(kSendingSsrc, kCName); + p.AddXrHeader(kSendingSsrc); + p.AddXrDlrrBlock(kSendingSsrc); + + test_transport_.SetExpectedRtcpPacket(p.Packet(), p.Length()); + + RtcpDlrrReportBlock dlrr_rb; + dlrr_rb.last_rr = kLastRr; + dlrr_rb.delay_since_last_rr = kDelayLastRr; + + rtcp_builder_->SendRtcpFromRtpSender( + RtcpBuilder::kRtcpSr | RtcpBuilder::kRtcpDlrr | + RtcpBuilder::kRtcpSenderLog, + &sender_info, + &dlrr_rb, + kSendingSsrc, + kCName); + + EXPECT_EQ(1, test_transport_.packet_count()); +} + +} // namespace cast +} // namespace media diff --git a/chromium/media/cast/transport/rtp_sender/packet_storage/packet_storage.cc b/chromium/media/cast/transport/rtp_sender/packet_storage/packet_storage.cc new file mode 100644 index 00000000000..a748baa27ab --- /dev/null +++ b/chromium/media/cast/transport/rtp_sender/packet_storage/packet_storage.cc @@ -0,0 +1,65 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/cast/transport/rtp_sender/packet_storage/packet_storage.h" + +#include <string> + +#include "base/logging.h" + +namespace media { +namespace cast { +namespace transport { + +PacketStorage::PacketStorage(size_t stored_frames) + : max_stored_frames_(stored_frames), + first_frame_id_in_list_(0), + last_frame_id_in_list_(0) { +} + +PacketStorage::~PacketStorage() { +} + +bool PacketStorage::IsValid() const { + return max_stored_frames_ > 0 && + static_cast<int>(max_stored_frames_) <= kMaxUnackedFrames; +} + +size_t PacketStorage::GetNumberOfStoredFrames() const { + return frames_.size(); +} + +void PacketStorage::StoreFrame(uint32 frame_id, + const SendPacketVector& packets) { + if (frames_.empty()) { + first_frame_id_in_list_ = frame_id; + } else { + // Make sure frame IDs are consecutive. + DCHECK_EQ(last_frame_id_in_list_ + 1, frame_id); + } + + // Save new frame to the end of the list. + last_frame_id_in_list_ = frame_id; + frames_.push_back(packets); + + // Evict the oldest frame if the list is too long. + if (frames_.size() > max_stored_frames_) { + frames_.pop_front(); + ++first_frame_id_in_list_; + } +} + +const SendPacketVector* PacketStorage::GetFrame8(uint8 frame_id_8bits) const { + // The requested frame ID has only 8-bits so convert the first frame ID + // in list to match. + uint8 index_8bits = first_frame_id_in_list_ & 0xFF; + index_8bits = frame_id_8bits - index_8bits; + if (index_8bits >= frames_.size()) + return NULL; + return &(frames_[index_8bits]); +} + +} // namespace transport +} // namespace cast +} // namespace media diff --git a/chromium/media/cast/transport/rtp_sender/packet_storage/packet_storage.h b/chromium/media/cast/transport/rtp_sender/packet_storage/packet_storage.h new file mode 100644 index 00000000000..037ead1edf6 --- /dev/null +++ b/chromium/media/cast/transport/rtp_sender/packet_storage/packet_storage.h @@ -0,0 +1,62 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_CAST_TRANSPORT_RTP_SENDER_PACKET_STORAGE_PACKET_STORAGE_H_ +#define MEDIA_CAST_TRANSPORT_RTP_SENDER_PACKET_STORAGE_PACKET_STORAGE_H_ + +#include <deque> +#include <list> +#include <map> +#include <vector> + +#include "base/basictypes.h" +#include "base/memory/linked_ptr.h" +#include "base/memory/scoped_ptr.h" +#include "base/time/tick_clock.h" +#include "base/time/time.h" +#include "media/cast/transport/cast_transport_config.h" +#include "media/cast/transport/cast_transport_defines.h" +#include "media/cast/transport/pacing/paced_sender.h" + +namespace media { +namespace cast { +namespace transport { + +// Stores a list of frames. Each frame consists a list of packets. +typedef std::deque<SendPacketVector> FrameQueue; + +class PacketStorage { + public: + explicit PacketStorage(size_t stored_frames); + virtual ~PacketStorage(); + + // Returns true if this class is configured correctly. + // (stored frames > 0 && stored_frames < kMaxStoredFrames) + bool IsValid() const; + + // Store all of the packets for a frame. + void StoreFrame(uint32 frame_id, const SendPacketVector& packets); + + // Returns a list of packets for a frame indexed by a 8-bits ID. + // It is the lowest 8 bits of a frame ID. + // Returns NULL if the frame cannot be found. + const SendPacketVector* GetFrame8(uint8 frame_id_8bits) const; + + // Get the number of stored frames. + size_t GetNumberOfStoredFrames() const; + + private: + const size_t max_stored_frames_; + FrameQueue frames_; + uint32 first_frame_id_in_list_; + uint32 last_frame_id_in_list_; + + DISALLOW_COPY_AND_ASSIGN(PacketStorage); +}; + +} // namespace transport +} // namespace cast +} // namespace media + +#endif // MEDIA_CAST_TRANSPORT_RTP_SENDER_PACKET_STORAGE_PACKET_STORAGE_H_ diff --git a/chromium/media/cast/transport/rtp_sender/packet_storage/packet_storage_unittest.cc b/chromium/media/cast/transport/rtp_sender/packet_storage/packet_storage_unittest.cc new file mode 100644 index 00000000000..298942c80a5 --- /dev/null +++ b/chromium/media/cast/transport/rtp_sender/packet_storage/packet_storage_unittest.cc @@ -0,0 +1,115 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/cast/transport/rtp_sender/packet_storage/packet_storage.h" + +#include <stdint.h> + +#include <vector> + +#include "base/test/simple_test_tick_clock.h" +#include "base/time/time.h" +#include "testing/gmock/include/gmock/gmock.h" + +namespace media { +namespace cast { +namespace transport { + +static size_t kStoredFrames = 10; + +// Generate |number_of_frames| and store into |*storage|. +// First frame has 1 packet, second frame has 2 packets, etc. +static void StoreFrames(size_t number_of_frames, + uint32 first_frame_id, + PacketStorage* storage) { + const base::TimeTicks kTicks; + const int kSsrc = 1; + for (size_t i = 0; i < number_of_frames; ++i) { + SendPacketVector packets; + // First frame has 1 packet, second frame has 2 packets, etc. + const size_t kNumberOfPackets = i + 1; + for (size_t j = 0; j < kNumberOfPackets; ++j) { + Packet test_packet(1, 0); + packets.push_back( + std::make_pair( + PacedPacketSender::MakePacketKey(kTicks, kSsrc, j), + new base::RefCountedData<Packet>(test_packet))); + } + storage->StoreFrame(first_frame_id, packets); + ++first_frame_id; + } +} + +TEST(PacketStorageTest, NumberOfStoredFrames) { + PacketStorage storage(kStoredFrames); + + uint32 frame_id = 0; + frame_id = ~frame_id; // The maximum value of uint32. + StoreFrames(200, frame_id, &storage); + EXPECT_EQ(kStoredFrames, storage.GetNumberOfStoredFrames()); +} + +TEST(PacketStorageTest, GetFrameWrapAround8bits) { + PacketStorage storage(kStoredFrames); + + const uint32 kFirstFrameId = 250; + StoreFrames(kStoredFrames, kFirstFrameId, &storage); + EXPECT_EQ(kStoredFrames, storage.GetNumberOfStoredFrames()); + + // Expect we get the correct frames by looking at the number of + // packets. + uint32 frame_id = kFirstFrameId; + for (size_t i = 0; i < kStoredFrames; ++i) { + ASSERT_TRUE(storage.GetFrame8(frame_id)); + EXPECT_EQ(i + 1, storage.GetFrame8(frame_id)->size()); + ++frame_id; + } +} + +TEST(PacketStorageTest, GetFrameWrapAround32bits) { + PacketStorage storage(kStoredFrames); + + // First frame ID is close to the maximum value of uint32. + uint32 first_frame_id = 0xffffffff - 5; + StoreFrames(kStoredFrames, first_frame_id, &storage); + EXPECT_EQ(kStoredFrames, storage.GetNumberOfStoredFrames()); + + // Expect we get the correct frames by looking at the number of + // packets. + uint32 frame_id = first_frame_id; + for (size_t i = 0; i < kStoredFrames; ++i) { + ASSERT_TRUE(storage.GetFrame8(frame_id)); + EXPECT_EQ(i + 1, storage.GetFrame8(frame_id)->size()); + ++frame_id; + } +} + +TEST(PacketStorageTest, GetFrameTooOld) { + PacketStorage storage(kStoredFrames); + + // First frame ID is close to the maximum value of uint32. + uint32 first_frame_id = 0xffffffff - 5; + + // Store two times the capacity. + StoreFrames(2 * kStoredFrames, first_frame_id, &storage); + EXPECT_EQ(kStoredFrames, storage.GetNumberOfStoredFrames()); + + uint32 frame_id = first_frame_id; + // Old frames are evicted. + for (size_t i = 0; i < kStoredFrames; ++i) { + EXPECT_FALSE(storage.GetFrame8(frame_id)); + ++frame_id; + } + // Check recent frames are there. + for (size_t i = 0; i < kStoredFrames; ++i) { + ASSERT_TRUE(storage.GetFrame8(frame_id)); + EXPECT_EQ(kStoredFrames + i + 1, + storage.GetFrame8(frame_id)->size()); + ++frame_id; + } +} + +} // namespace transport +} // namespace cast +} // namespace media diff --git a/chromium/media/cast/transport/rtp_sender/rtp_packetizer/rtp_packetizer.cc b/chromium/media/cast/transport/rtp_sender/rtp_packetizer/rtp_packetizer.cc new file mode 100644 index 00000000000..d40f99f1446 --- /dev/null +++ b/chromium/media/cast/transport/rtp_sender/rtp_packetizer/rtp_packetizer.cc @@ -0,0 +1,137 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/cast/transport/rtp_sender/rtp_packetizer/rtp_packetizer.h" + +#include "base/big_endian.h" +#include "base/logging.h" +#include "media/cast/transport/pacing/paced_sender.h" + +namespace media { +namespace cast { +namespace transport { + +static const uint16 kCommonRtpHeaderLength = 12; +static const uint16 kCastRtpHeaderLength = 7; +static const uint8 kCastKeyFrameBitMask = 0x80; +static const uint8 kCastReferenceFrameIdBitMask = 0x40; +static const uint8 kRtpMarkerBitMask = 0x80; + +RtpPacketizerConfig::RtpPacketizerConfig() + : audio(false), + payload_type(-1), + max_payload_length(kMaxIpPacketSize - 28), // Default is IP-v4/UDP. + sequence_number(0), + frequency(8000), + ssrc(0), + channels(0) {} + +RtpPacketizerConfig::~RtpPacketizerConfig() {} + +RtpPacketizer::RtpPacketizer(PacedSender* const transport, + PacketStorage* packet_storage, + RtpPacketizerConfig rtp_packetizer_config) + : config_(rtp_packetizer_config), + transport_(transport), + packet_storage_(packet_storage), + sequence_number_(config_.sequence_number), + rtp_timestamp_(0), + packet_id_(0), + send_packet_count_(0), + send_octet_count_(0) { + DCHECK(transport) << "Invalid argument"; +} + +RtpPacketizer::~RtpPacketizer() {} + +uint16 RtpPacketizer::NextSequenceNumber() { + ++sequence_number_; + return sequence_number_ - 1; +} + +void RtpPacketizer::SendFrameAsPackets(const EncodedFrame& frame) { + uint16 rtp_header_length = kCommonRtpHeaderLength + kCastRtpHeaderLength; + uint16 max_length = config_.max_payload_length - rtp_header_length - 1; + rtp_timestamp_ = frame.rtp_timestamp; + + // Split the payload evenly (round number up). + size_t num_packets = (frame.data.size() + max_length) / max_length; + size_t payload_length = (frame.data.size() + num_packets) / num_packets; + DCHECK_LE(payload_length, max_length) << "Invalid argument"; + + SendPacketVector packets; + + size_t remaining_size = frame.data.size(); + std::string::const_iterator data_iter = frame.data.begin(); + while (remaining_size > 0) { + PacketRef packet(new base::RefCountedData<Packet>); + + if (remaining_size < payload_length) { + payload_length = remaining_size; + } + remaining_size -= payload_length; + BuildCommonRTPheader( + &packet->data, remaining_size == 0, frame.rtp_timestamp); + + // Build Cast header. + // TODO(miu): Should we always set the ref frame bit and the ref_frame_id? + DCHECK_NE(frame.dependency, EncodedFrame::UNKNOWN_DEPENDENCY); + packet->data.push_back( + ((frame.dependency == EncodedFrame::KEY) ? kCastKeyFrameBitMask : 0) | + kCastReferenceFrameIdBitMask); + packet->data.push_back(static_cast<uint8>(frame.frame_id)); + size_t start_size = packet->data.size(); + packet->data.resize(start_size + 4); + base::BigEndianWriter big_endian_writer( + reinterpret_cast<char*>(&(packet->data[start_size])), 4); + big_endian_writer.WriteU16(packet_id_); + big_endian_writer.WriteU16(static_cast<uint16>(num_packets - 1)); + packet->data.push_back(static_cast<uint8>(frame.referenced_frame_id)); + + // Copy payload data. + packet->data.insert(packet->data.end(), + data_iter, + data_iter + payload_length); + data_iter += payload_length; + + const PacketKey key = + PacedPacketSender::MakePacketKey(frame.reference_time, + config_.ssrc, + packet_id_++); + packets.push_back(make_pair(key, packet)); + + // Update stats. + ++send_packet_count_; + send_octet_count_ += payload_length; + } + DCHECK(packet_id_ == num_packets) << "Invalid state"; + + packet_storage_->StoreFrame(frame.frame_id, packets); + + // Send to network. + transport_->SendPackets(packets); + + // Prepare for next frame. + packet_id_ = 0; +} + +void RtpPacketizer::BuildCommonRTPheader(Packet* packet, + bool marker_bit, + uint32 time_stamp) { + packet->push_back(0x80); + packet->push_back(static_cast<uint8>(config_.payload_type) | + (marker_bit ? kRtpMarkerBitMask : 0)); + size_t start_size = packet->size(); + packet->resize(start_size + 10); + base::BigEndianWriter big_endian_writer( + reinterpret_cast<char*>(&((*packet)[start_size])), 10); + big_endian_writer.WriteU16(sequence_number_); + big_endian_writer.WriteU32(time_stamp); + big_endian_writer.WriteU32(config_.ssrc); + ++sequence_number_; +} + +} // namespace transport +} // namespace cast +} // namespace media diff --git a/chromium/media/cast/transport/rtp_sender/rtp_packetizer/rtp_packetizer.h b/chromium/media/cast/transport/rtp_sender/rtp_packetizer/rtp_packetizer.h new file mode 100644 index 00000000000..ebdbf010183 --- /dev/null +++ b/chromium/media/cast/transport/rtp_sender/rtp_packetizer/rtp_packetizer.h @@ -0,0 +1,86 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_CAST_TRANSPORT_RTP_SENDER_RTP_PACKETIZER_RTP_PACKETIZER_H_ +#define MEDIA_CAST_TRANSPORT_RTP_SENDER_RTP_PACKETIZER_RTP_PACKETIZER_H_ + +#include <cmath> +#include <list> +#include <map> + +#include "base/time/time.h" +#include "media/cast/transport/rtp_sender/packet_storage/packet_storage.h" + +namespace base { +class TickClock; +} + +namespace media { +namespace cast { + +namespace transport { + +class PacedSender; + +struct RtpPacketizerConfig { + RtpPacketizerConfig(); + ~RtpPacketizerConfig(); + + // General. + bool audio; + int payload_type; + uint16 max_payload_length; + uint16 sequence_number; + int frequency; + + // SSRC. + unsigned int ssrc; + + // Video. + VideoCodec video_codec; + + // Audio. + uint8 channels; + AudioCodec audio_codec; +}; + +// This object is only called from the main cast thread. +// This class break encoded audio and video frames into packets and add an RTP +// header to each packet. +class RtpPacketizer { + public: + RtpPacketizer(PacedSender* const transport, + PacketStorage* packet_storage, + RtpPacketizerConfig rtp_packetizer_config); + ~RtpPacketizer(); + + void SendFrameAsPackets(const EncodedFrame& frame); + + // Return the next sequence number, and increment by one. Enables unique + // incremental sequence numbers for every packet (including retransmissions). + uint16 NextSequenceNumber(); + + size_t send_packet_count() const { return send_packet_count_; } + size_t send_octet_count() const { return send_octet_count_; } + + private: + void BuildCommonRTPheader(Packet* packet, bool marker_bit, uint32 time_stamp); + + RtpPacketizerConfig config_; + PacedSender* const transport_; // Not owned by this class. + PacketStorage* packet_storage_; + + uint16 sequence_number_; + uint32 rtp_timestamp_; + uint16 packet_id_; + + size_t send_packet_count_; + size_t send_octet_count_; +}; + +} // namespace transport +} // namespace cast +} // namespace media + +#endif // MEDIA_CAST_TRANSPORT_RTP_SENDER_RTP_PACKETIZER_RTP_PACKETIZER_H_ diff --git a/chromium/media/cast/transport/rtp_sender/rtp_packetizer/rtp_packetizer_unittest.cc b/chromium/media/cast/transport/rtp_sender/rtp_packetizer/rtp_packetizer_unittest.cc new file mode 100644 index 00000000000..64def4ce7fa --- /dev/null +++ b/chromium/media/cast/transport/rtp_sender/rtp_packetizer/rtp_packetizer_unittest.cc @@ -0,0 +1,175 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/cast/transport/rtp_sender/rtp_packetizer/rtp_packetizer.h" + +#include <stdint.h> + +#include "base/memory/scoped_ptr.h" +#include "base/test/simple_test_tick_clock.h" +#include "media/cast/logging/simple_event_subscriber.h" +#include "media/cast/test/fake_single_thread_task_runner.h" +#include "media/cast/transport/pacing/paced_sender.h" +#include "media/cast/transport/rtp_sender/packet_storage/packet_storage.h" +#include "media/cast/transport/rtp_sender/rtp_packetizer/test/rtp_header_parser.h" +#include "testing/gmock/include/gmock/gmock.h" + +namespace media { +namespace cast { +namespace transport { + +namespace { +static const int kPayload = 127; +static const uint32 kTimestampMs = 10; +static const uint16 kSeqNum = 33; +static const int kMaxPacketLength = 1500; +static const int kSsrc = 0x12345; +static const unsigned int kFrameSize = 5000; +static const uint32 kStartFrameId = UINT32_C(0xffffffff); +} + +class TestRtpPacketTransport : public PacketSender { + public: + explicit TestRtpPacketTransport(RtpPacketizerConfig config) + : config_(config), + sequence_number_(kSeqNum), + packets_sent_(0), + expected_number_of_packets_(0), + expected_packet_id_(0), + expected_frame_id_(0), + expectd_rtp_timestamp_(0) {} + + void VerifyRtpHeader(const RtpCastTestHeader& rtp_header) { + VerifyCommonRtpHeader(rtp_header); + VerifyCastRtpHeader(rtp_header); + } + + void VerifyCommonRtpHeader(const RtpCastTestHeader& rtp_header) { + EXPECT_EQ(kPayload, rtp_header.payload_type); + EXPECT_EQ(sequence_number_, rtp_header.sequence_number); + EXPECT_EQ(expectd_rtp_timestamp_, rtp_header.rtp_timestamp); + EXPECT_EQ(config_.ssrc, rtp_header.ssrc); + EXPECT_EQ(0, rtp_header.num_csrcs); + } + + void VerifyCastRtpHeader(const RtpCastTestHeader& rtp_header) { + EXPECT_FALSE(rtp_header.is_key_frame); + EXPECT_EQ(expected_frame_id_, rtp_header.frame_id); + EXPECT_EQ(expected_packet_id_, rtp_header.packet_id); + EXPECT_EQ(expected_number_of_packets_ - 1, rtp_header.max_packet_id); + EXPECT_TRUE(rtp_header.is_reference); + EXPECT_EQ(expected_frame_id_ - 1u, rtp_header.reference_frame_id); + } + + virtual bool SendPacket(PacketRef packet, const base::Closure& cb) OVERRIDE { + ++packets_sent_; + RtpHeaderParser parser(packet->data.data(), packet->data.size()); + RtpCastTestHeader rtp_header; + parser.Parse(&rtp_header); + VerifyRtpHeader(rtp_header); + ++sequence_number_; + ++expected_packet_id_; + return true; + } + + size_t number_of_packets_received() const { return packets_sent_; } + + void set_expected_number_of_packets(size_t expected_number_of_packets) { + expected_number_of_packets_ = expected_number_of_packets; + } + + void set_rtp_timestamp(uint32 rtp_timestamp) { + expectd_rtp_timestamp_ = rtp_timestamp; + } + + RtpPacketizerConfig config_; + uint32 sequence_number_; + size_t packets_sent_; + size_t number_of_packets_; + size_t expected_number_of_packets_; + // Assuming packets arrive in sequence. + int expected_packet_id_; + uint32 expected_frame_id_; + uint32 expectd_rtp_timestamp_; + + DISALLOW_COPY_AND_ASSIGN(TestRtpPacketTransport); +}; + +class RtpPacketizerTest : public ::testing::Test { + protected: + RtpPacketizerTest() + : task_runner_(new test::FakeSingleThreadTaskRunner(&testing_clock_)), + video_frame_(), + packet_storage_(200) { + config_.sequence_number = kSeqNum; + config_.ssrc = kSsrc; + config_.payload_type = kPayload; + config_.max_payload_length = kMaxPacketLength; + transport_.reset(new TestRtpPacketTransport(config_)); + pacer_.reset(new PacedSender( + &testing_clock_, &logging_, transport_.get(), task_runner_)); + pacer_->RegisterVideoSsrc(config_.ssrc); + rtp_packetizer_.reset(new RtpPacketizer( + pacer_.get(), &packet_storage_, config_)); + video_frame_.dependency = EncodedFrame::DEPENDENT; + video_frame_.frame_id = 0; + video_frame_.referenced_frame_id = kStartFrameId; + video_frame_.data.assign(kFrameSize, 123); + video_frame_.rtp_timestamp = + GetVideoRtpTimestamp(testing_clock_.NowTicks()); + } + + void RunTasks(int during_ms) { + for (int i = 0; i < during_ms; ++i) { + // Call process the timers every 1 ms. + testing_clock_.Advance(base::TimeDelta::FromMilliseconds(1)); + task_runner_->RunTasks(); + } + } + + base::SimpleTestTickClock testing_clock_; + scoped_refptr<test::FakeSingleThreadTaskRunner> task_runner_; + EncodedFrame video_frame_; + PacketStorage packet_storage_; + RtpPacketizerConfig config_; + scoped_ptr<TestRtpPacketTransport> transport_; + LoggingImpl logging_; + scoped_ptr<PacedSender> pacer_; + scoped_ptr<RtpPacketizer> rtp_packetizer_; + + DISALLOW_COPY_AND_ASSIGN(RtpPacketizerTest); +}; + +TEST_F(RtpPacketizerTest, SendStandardPackets) { + size_t expected_num_of_packets = kFrameSize / kMaxPacketLength + 1; + transport_->set_expected_number_of_packets(expected_num_of_packets); + transport_->set_rtp_timestamp(video_frame_.rtp_timestamp); + + testing_clock_.Advance(base::TimeDelta::FromMilliseconds(kTimestampMs)); + video_frame_.reference_time = testing_clock_.NowTicks(); + rtp_packetizer_->SendFrameAsPackets(video_frame_); + RunTasks(33 + 1); + EXPECT_EQ(expected_num_of_packets, transport_->number_of_packets_received()); +} + +TEST_F(RtpPacketizerTest, Stats) { + EXPECT_FALSE(rtp_packetizer_->send_packet_count()); + EXPECT_FALSE(rtp_packetizer_->send_octet_count()); + // Insert packets at varying lengths. + size_t expected_num_of_packets = kFrameSize / kMaxPacketLength + 1; + transport_->set_expected_number_of_packets(expected_num_of_packets); + transport_->set_rtp_timestamp(video_frame_.rtp_timestamp); + + testing_clock_.Advance(base::TimeDelta::FromMilliseconds(kTimestampMs)); + video_frame_.reference_time = testing_clock_.NowTicks(); + rtp_packetizer_->SendFrameAsPackets(video_frame_); + RunTasks(33 + 1); + EXPECT_EQ(expected_num_of_packets, rtp_packetizer_->send_packet_count()); + EXPECT_EQ(kFrameSize, rtp_packetizer_->send_octet_count()); + EXPECT_EQ(expected_num_of_packets, transport_->number_of_packets_received()); +} + +} // namespace transport +} // namespace cast +} // namespace media diff --git a/chromium/media/cast/transport/rtp_sender/rtp_sender.cc b/chromium/media/cast/transport/rtp_sender/rtp_sender.cc new file mode 100644 index 00000000000..b807b347576 --- /dev/null +++ b/chromium/media/cast/transport/rtp_sender/rtp_sender.cc @@ -0,0 +1,150 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/cast/transport/rtp_sender/rtp_sender.h" + +#include "base/big_endian.h" +#include "base/logging.h" +#include "base/rand_util.h" +#include "media/cast/transport/cast_transport_defines.h" +#include "media/cast/transport/pacing/paced_sender.h" + +namespace media { +namespace cast { +namespace transport { + +namespace { + +// If there is only one referecne to the packet then copy the +// reference and return. +// Otherwise return a deep copy of the packet. +PacketRef FastCopyPacket(const PacketRef& packet) { + if (packet->HasOneRef()) + return packet; + return make_scoped_refptr(new base::RefCountedData<Packet>(packet->data)); +} + +} // namespace + +RtpSender::RtpSender( + base::TickClock* clock, + const scoped_refptr<base::SingleThreadTaskRunner>& transport_task_runner, + PacedSender* const transport) + : clock_(clock), + transport_(transport), + transport_task_runner_(transport_task_runner), + weak_factory_(this) { + // Randomly set sequence number start value. + config_.sequence_number = base::RandInt(0, 65535); +} + +RtpSender::~RtpSender() {} + +bool RtpSender::InitializeAudio(const CastTransportAudioConfig& config) { + storage_.reset(new PacketStorage(config.rtp.max_outstanding_frames)); + if (!storage_->IsValid()) { + return false; + } + config_.audio = true; + config_.ssrc = config.rtp.config.ssrc; + config_.payload_type = config.rtp.config.payload_type; + config_.frequency = config.frequency; + config_.audio_codec = config.codec; + packetizer_.reset(new RtpPacketizer(transport_, storage_.get(), config_)); + return true; +} + +bool RtpSender::InitializeVideo(const CastTransportVideoConfig& config) { + storage_.reset(new PacketStorage(config.rtp.max_outstanding_frames)); + if (!storage_->IsValid()) { + return false; + } + config_.audio = false; + config_.ssrc = config.rtp.config.ssrc; + config_.payload_type = config.rtp.config.payload_type; + config_.frequency = kVideoFrequency; + config_.video_codec = config.codec; + packetizer_.reset(new RtpPacketizer(transport_, storage_.get(), config_)); + return true; +} + +void RtpSender::SendFrame(const EncodedFrame& frame) { + DCHECK(packetizer_); + packetizer_->SendFrameAsPackets(frame); +} + +void RtpSender::ResendPackets( + const MissingFramesAndPacketsMap& missing_frames_and_packets, + bool cancel_rtx_if_not_in_list, + base::TimeDelta dedupe_window) { + DCHECK(storage_); + // Iterate over all frames in the list. + for (MissingFramesAndPacketsMap::const_iterator it = + missing_frames_and_packets.begin(); + it != missing_frames_and_packets.end(); + ++it) { + SendPacketVector packets_to_resend; + uint8 frame_id = it->first; + // Set of packets that the receiver wants us to re-send. + // If empty, we need to re-send all packets for this frame. + const PacketIdSet& missing_packet_set = it->second; + + bool resend_all = missing_packet_set.find(kRtcpCastAllPacketsLost) != + missing_packet_set.end(); + bool resend_last = missing_packet_set.find(kRtcpCastLastPacket) != + missing_packet_set.end(); + + const SendPacketVector* stored_packets = storage_->GetFrame8(frame_id); + if (!stored_packets) + continue; + + for (SendPacketVector::const_iterator it = stored_packets->begin(); + it != stored_packets->end(); ++it) { + const PacketKey& packet_key = it->first; + const uint16 packet_id = packet_key.second.second; + + // Should we resend the packet? + bool resend = resend_all; + + // Should we resend it because it's in the missing_packet_set? + if (!resend && + missing_packet_set.find(packet_id) != missing_packet_set.end()) { + resend = true; + } + + // If we were asked to resend the last packet, check if it's the + // last packet. + if (!resend && resend_last && (it + 1) == stored_packets->end()) { + resend = true; + } + + if (resend) { + // Resend packet to the network. + VLOG(3) << "Resend " << static_cast<int>(frame_id) << ":" + << packet_id; + // Set a unique incremental sequence number for every packet. + PacketRef packet_copy = FastCopyPacket(it->second); + UpdateSequenceNumber(&packet_copy->data); + packets_to_resend.push_back(std::make_pair(packet_key, packet_copy)); + } else if (cancel_rtx_if_not_in_list) { + transport_->CancelSendingPacket(it->first); + } + } + transport_->ResendPackets(packets_to_resend, dedupe_window); + } +} + +void RtpSender::UpdateSequenceNumber(Packet* packet) { + // TODO(miu): This is an abstraction violation. This needs to be a part of + // the overall packet (de)serialization consolidation. + static const int kByteOffsetToSequenceNumber = 2; + base::BigEndianWriter big_endian_writer( + reinterpret_cast<char*>((&packet->front()) + kByteOffsetToSequenceNumber), + sizeof(uint16)); + big_endian_writer.WriteU16(packetizer_->NextSequenceNumber()); +} + +} // namespace transport +} // namespace cast +} // namespace media diff --git a/chromium/media/cast/transport/rtp_sender/rtp_sender.h b/chromium/media/cast/transport/rtp_sender/rtp_sender.h new file mode 100644 index 00000000000..e65326abf16 --- /dev/null +++ b/chromium/media/cast/transport/rtp_sender/rtp_sender.h @@ -0,0 +1,85 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file contains the interface to the cast RTP sender. + +#ifndef MEDIA_CAST_TRANSPORT_RTP_SENDER_RTP_SENDER_H_ +#define MEDIA_CAST_TRANSPORT_RTP_SENDER_RTP_SENDER_H_ + +#include <map> +#include <set> + +#include "base/memory/scoped_ptr.h" +#include "base/time/tick_clock.h" +#include "base/time/time.h" +#include "base/memory/weak_ptr.h" +#include "media/cast/cast_config.h" +#include "media/cast/cast_environment.h" +#include "media/cast/transport/cast_transport_defines.h" +#include "media/cast/transport/cast_transport_sender.h" +#include "media/cast/transport/pacing/paced_sender.h" +#include "media/cast/transport/rtp_sender/packet_storage/packet_storage.h" +#include "media/cast/transport/rtp_sender/rtp_packetizer/rtp_packetizer.h" + +namespace media { +namespace cast { + +namespace transport { + +// This object is only called from the main cast thread. +// This class handles splitting encoded audio and video frames into packets and +// add an RTP header to each packet. The sent packets are stored until they are +// acknowledged by the remote peer or timed out. +class RtpSender { + public: + RtpSender( + base::TickClock* clock, + const scoped_refptr<base::SingleThreadTaskRunner>& transport_task_runner, + PacedSender* const transport); + + ~RtpSender(); + + // Initialize audio stack. Audio must be initialized prior to sending encoded + // audio frames. Returns false if configuration is invalid. + bool InitializeAudio(const CastTransportAudioConfig& config); + + // Initialize video stack. Video must be initialized prior to sending encoded + // video frames. Returns false if configuration is invalid. + bool InitializeVideo(const CastTransportVideoConfig& config); + + void SendFrame(const EncodedFrame& frame); + + void ResendPackets(const MissingFramesAndPacketsMap& missing_packets, + bool cancel_rtx_if_not_in_list, + base::TimeDelta dedupe_window); + + size_t send_packet_count() const { + return packetizer_ ? packetizer_->send_packet_count() : 0; + } + size_t send_octet_count() const { + return packetizer_ ? packetizer_->send_octet_count() : 0; + } + uint32 ssrc() const { return config_.ssrc; } + + private: + void UpdateSequenceNumber(Packet* packet); + + base::TickClock* clock_; // Not owned by this class. + RtpPacketizerConfig config_; + scoped_ptr<RtpPacketizer> packetizer_; + scoped_ptr<PacketStorage> storage_; + PacedSender* const transport_; + scoped_refptr<base::SingleThreadTaskRunner> transport_task_runner_; + + // NOTE: Weak pointers must be invalidated before all other member variables. + base::WeakPtrFactory<RtpSender> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(RtpSender); +}; + +} // namespace transport +} // namespace cast +} // namespace media + +#endif // MEDIA_CAST_TRANSPORT_RTP_SENDER_RTP_SENDER_H_ diff --git a/chromium/media/cast/transport/transport/udp_transport.cc b/chromium/media/cast/transport/transport/udp_transport.cc new file mode 100644 index 00000000000..9669b17d438 --- /dev/null +++ b/chromium/media/cast/transport/transport/udp_transport.cc @@ -0,0 +1,242 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/cast/transport/transport/udp_transport.h" + +#include <algorithm> +#include <string> + +#include "base/bind.h" +#include "base/logging.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/rand_util.h" +#include "net/base/io_buffer.h" +#include "net/base/net_errors.h" +#include "net/base/rand_callback.h" + +namespace media { +namespace cast { +namespace transport { + +namespace { +const int kMaxPacketSize = 1500; + +bool IsEmpty(const net::IPEndPoint& addr) { + net::IPAddressNumber empty_addr(addr.address().size()); + return std::equal( + empty_addr.begin(), empty_addr.end(), addr.address().begin()) && + !addr.port(); +} + +bool IsEqual(const net::IPEndPoint& addr1, const net::IPEndPoint& addr2) { + return addr1.port() == addr2.port() && std::equal(addr1.address().begin(), + addr1.address().end(), + addr2.address().begin()); +} +} // namespace + +UdpTransport::UdpTransport( + net::NetLog* net_log, + const scoped_refptr<base::SingleThreadTaskRunner>& io_thread_proxy, + const net::IPEndPoint& local_end_point, + const net::IPEndPoint& remote_end_point, + const CastTransportStatusCallback& status_callback) + : io_thread_proxy_(io_thread_proxy), + local_addr_(local_end_point), + remote_addr_(remote_end_point), + udp_socket_(new net::UDPSocket(net::DatagramSocket::DEFAULT_BIND, + net::RandIntCallback(), + net_log, + net::NetLog::Source())), + send_pending_(false), + receive_pending_(false), + client_connected_(false), + next_dscp_value_(net::DSCP_NO_CHANGE), + status_callback_(status_callback), + weak_factory_(this) { + DCHECK(!IsEmpty(local_end_point) || !IsEmpty(remote_end_point)); +} + +UdpTransport::~UdpTransport() {} + +void UdpTransport::StartReceiving( + const PacketReceiverCallback& packet_receiver) { + DCHECK(io_thread_proxy_->RunsTasksOnCurrentThread()); + + packet_receiver_ = packet_receiver; + udp_socket_->AllowAddressReuse(); + udp_socket_->SetMulticastLoopbackMode(true); + if (!IsEmpty(local_addr_)) { + if (udp_socket_->Bind(local_addr_) < 0) { + status_callback_.Run(TRANSPORT_SOCKET_ERROR); + LOG(ERROR) << "Failed to bind local address."; + return; + } + } else if (!IsEmpty(remote_addr_)) { + if (udp_socket_->Connect(remote_addr_) < 0) { + status_callback_.Run(TRANSPORT_SOCKET_ERROR); + LOG(ERROR) << "Failed to connect to remote address."; + return; + } + client_connected_ = true; + } else { + NOTREACHED() << "Either local or remote address has to be defined."; + } + + ScheduleReceiveNextPacket(); +} + +void UdpTransport::SetDscp(net::DiffServCodePoint dscp) { + DCHECK(io_thread_proxy_->RunsTasksOnCurrentThread()); + next_dscp_value_ = dscp; +} + +void UdpTransport::ScheduleReceiveNextPacket() { + DCHECK(io_thread_proxy_->RunsTasksOnCurrentThread()); + if (!packet_receiver_.is_null() && !receive_pending_) { + receive_pending_ = true; + io_thread_proxy_->PostTask(FROM_HERE, + base::Bind(&UdpTransport::ReceiveNextPacket, + weak_factory_.GetWeakPtr(), + net::ERR_IO_PENDING)); + } +} + +void UdpTransport::ReceiveNextPacket(int length_or_status) { + DCHECK(io_thread_proxy_->RunsTasksOnCurrentThread()); + + // Loop while UdpSocket is delivering data synchronously. When it responds + // with a "pending" status, break and expect this method to be called back in + // the future when a packet is ready. + while (true) { + if (length_or_status == net::ERR_IO_PENDING) { + next_packet_.reset(new Packet(kMaxPacketSize)); + recv_buf_ = new net::WrappedIOBuffer( + reinterpret_cast<char*>(&next_packet_->front())); + length_or_status = udp_socket_->RecvFrom( + recv_buf_, + kMaxPacketSize, + &recv_addr_, + base::Bind(&UdpTransport::ReceiveNextPacket, + weak_factory_.GetWeakPtr())); + if (length_or_status == net::ERR_IO_PENDING) { + receive_pending_ = true; + return; + } + } + + // Note: At this point, either a packet is ready or an error has occurred. + if (length_or_status < 0) { + VLOG(1) << "Failed to receive packet: Status code is " + << length_or_status; + status_callback_.Run(TRANSPORT_SOCKET_ERROR); + receive_pending_ = false; + return; + } + + // Confirm the packet has come from the expected remote address; otherwise, + // ignore it. If this is the first packet being received and no remote + // address has been set, set the remote address and expect all future + // packets to come from the same one. + // TODO(hubbe): We should only do this if the caller used a valid ssrc. + if (IsEmpty(remote_addr_)) { + remote_addr_ = recv_addr_; + VLOG(1) << "Setting remote address from first received packet: " + << remote_addr_.ToString(); + } else if (!IsEqual(remote_addr_, recv_addr_)) { + VLOG(1) << "Ignoring packet received from an unrecognized address: " + << recv_addr_.ToString() << "."; + length_or_status = net::ERR_IO_PENDING; + continue; + } + + next_packet_->resize(length_or_status); + packet_receiver_.Run(next_packet_.Pass()); + length_or_status = net::ERR_IO_PENDING; + } +} + +bool UdpTransport::SendPacket(PacketRef packet, const base::Closure& cb) { + DCHECK(io_thread_proxy_->RunsTasksOnCurrentThread()); + + DCHECK(!send_pending_); + if (send_pending_) { + VLOG(1) << "Cannot send because of pending IO."; + return true; + } + + if (next_dscp_value_ != net::DSCP_NO_CHANGE) { + int result = udp_socket_->SetDiffServCodePoint(next_dscp_value_); + if (result != net::OK) { + LOG(ERROR) << "Unable to set DSCP: " << next_dscp_value_ + << " to socket; Error: " << result; + } + // Don't change DSCP in next send. + next_dscp_value_ = net::DSCP_NO_CHANGE; + } + + scoped_refptr<net::IOBuffer> buf = + new net::WrappedIOBuffer(reinterpret_cast<char*>(&packet->data.front())); + + int result; + base::Callback<void(int)> callback = base::Bind(&UdpTransport::OnSent, + weak_factory_.GetWeakPtr(), + buf, + packet, + cb); + if (client_connected_) { + // If we called Connect() before we must call Write() instead of + // SendTo(). Otherwise on some platforms we might get + // ERR_SOCKET_IS_CONNECTED. + result = udp_socket_->Write(buf, + static_cast<int>(packet->data.size()), + callback); + } else if (!IsEmpty(remote_addr_)) { + result = udp_socket_->SendTo(buf, + static_cast<int>(packet->data.size()), + remote_addr_, + callback); + } else { + return true; + } + + if (result == net::ERR_IO_PENDING) { + send_pending_ = true; + return false; + } else if (result < 0) { + LOG(ERROR) << "Failed to send packet: " << result << "."; + status_callback_.Run(TRANSPORT_SOCKET_ERROR); + return true; + } else { + // Successful send, re-start reading if needed. + ScheduleReceiveNextPacket(); + return true; + } +} + +void UdpTransport::OnSent(const scoped_refptr<net::IOBuffer>& buf, + PacketRef packet, + const base::Closure& cb, + int result) { + DCHECK(io_thread_proxy_->RunsTasksOnCurrentThread()); + + send_pending_ = false; + if (result < 0) { + LOG(ERROR) << "Failed to send packet: " << result << "."; + status_callback_.Run(TRANSPORT_SOCKET_ERROR); + } else { + // Successful send, re-start reading if needed. + ScheduleReceiveNextPacket(); + } + + if (!cb.is_null()) { + cb.Run(); + } +} + +} // namespace transport +} // namespace cast +} // namespace media diff --git a/chromium/media/cast/transport/transport/udp_transport.h b/chromium/media/cast/transport/transport/udp_transport.h new file mode 100644 index 00000000000..1a568501d5f --- /dev/null +++ b/chromium/media/cast/transport/transport/udp_transport.h @@ -0,0 +1,97 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_CAST_TRANSPORT_TRANSPORT_UDP_TRANSPORT_H_ +#define MEDIA_CAST_TRANSPORT_TRANSPORT_UDP_TRANSPORT_H_ + +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "media/cast/cast_environment.h" +#include "media/cast/transport/cast_transport_config.h" +#include "media/cast/transport/cast_transport_sender.h" +#include "net/base/ip_endpoint.h" +#include "net/base/net_util.h" +#include "net/udp/udp_socket.h" + +namespace net { +class IOBuffer; +class IPEndPoint; +class NetLog; +} // namespace net + +namespace media { +namespace cast { +namespace transport { + +// This class implements UDP transport mechanism for Cast. +class UdpTransport : public PacketSender { + public: + // Construct a UDP transport. + // All methods must be called on |io_thread_proxy|. + // |local_end_point| specifies the address and port to bind and listen + // to incoming packets. If the value is 0.0.0.0:0 then a bind is not + // performed. + // |remote_end_point| specifies the address and port to send packets + // to. If the value is 0.0.0.0:0 the the end point is set to the source + // address of the first packet received. + UdpTransport( + net::NetLog* net_log, + const scoped_refptr<base::SingleThreadTaskRunner>& io_thread_proxy, + const net::IPEndPoint& local_end_point, + const net::IPEndPoint& remote_end_point, + const CastTransportStatusCallback& status_callback); + virtual ~UdpTransport(); + + // Start receiving packets. Packets are submitted to |packet_receiver|. + void StartReceiving(const PacketReceiverCallback& packet_receiver); + + // Set a new DSCP value to the socket. The value will be set right before + // the next send. + void SetDscp(net::DiffServCodePoint dscp); + + // PacketSender implementations. + virtual bool SendPacket(PacketRef packet, + const base::Closure& cb) OVERRIDE; + + private: + // Requests and processes packets from |udp_socket_|. This method is called + // once with |length_or_status| set to net::ERR_IO_PENDING to start receiving + // packets. Thereafter, it is called with some other value as the callback + // response from UdpSocket::RecvFrom(). + void ReceiveNextPacket(int length_or_status); + + // Schedule packet receiving, if needed. + void ScheduleReceiveNextPacket(); + + void OnSent(const scoped_refptr<net::IOBuffer>& buf, + PacketRef packet, + const base::Closure& cb, + int result); + + const scoped_refptr<base::SingleThreadTaskRunner> io_thread_proxy_; + const net::IPEndPoint local_addr_; + net::IPEndPoint remote_addr_; + const scoped_ptr<net::UDPSocket> udp_socket_; + bool send_pending_; + bool receive_pending_; + bool client_connected_; + net::DiffServCodePoint next_dscp_value_; + scoped_ptr<Packet> next_packet_; + scoped_refptr<net::WrappedIOBuffer> recv_buf_; + net::IPEndPoint recv_addr_; + PacketReceiverCallback packet_receiver_; + const CastTransportStatusCallback status_callback_; + + // NOTE: Weak pointers must be invalidated before all other member variables. + base::WeakPtrFactory<UdpTransport> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(UdpTransport); +}; + +} // namespace transport +} // namespace cast +} // namespace media + +#endif // MEDIA_CAST_TRANSPORT_TRANSPORT_UDP_TRANSPORT_H_ diff --git a/chromium/media/cast/transport/transport/udp_transport_unittest.cc b/chromium/media/cast/transport/transport/udp_transport_unittest.cc new file mode 100644 index 00000000000..26879492f05 --- /dev/null +++ b/chromium/media/cast/transport/transport/udp_transport_unittest.cc @@ -0,0 +1,100 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/cast/transport/transport/udp_transport.h" + +#include <algorithm> +#include <string> +#include <vector> + +#include "base/bind.h" +#include "base/callback.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "media/cast/test/utility/net_utility.h" +#include "media/cast/transport/cast_transport_config.h" +#include "net/base/net_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace media { +namespace cast { +namespace transport { + +class MockPacketReceiver { + public: + MockPacketReceiver(const base::Closure& callback) + : packet_callback_(callback) {} + + void ReceivedPacket(scoped_ptr<Packet> packet) { + packet_ = std::string(packet->size(), '\0'); + std::copy(packet->begin(), packet->end(), packet_.begin()); + packet_callback_.Run(); + } + + std::string packet() const { return packet_; } + transport::PacketReceiverCallback packet_receiver() { + return base::Bind(&MockPacketReceiver::ReceivedPacket, + base::Unretained(this)); + } + + private: + std::string packet_; + base::Closure packet_callback_; + + DISALLOW_COPY_AND_ASSIGN(MockPacketReceiver); +}; + +void SendPacket(UdpTransport* transport, Packet packet) { + base::Closure cb; + transport->SendPacket(new base::RefCountedData<Packet>(packet), cb); +} + +static void UpdateCastTransportStatus(transport::CastTransportStatus status) { + NOTREACHED(); +} + +TEST(UdpTransport, SendAndReceive) { + base::MessageLoopForIO message_loop; + + net::IPEndPoint free_local_port1 = test::GetFreeLocalPort(); + net::IPEndPoint free_local_port2 = test::GetFreeLocalPort(); + net::IPAddressNumber empty_addr_number; + net::ParseIPLiteralToNumber("0.0.0.0", &empty_addr_number); + + UdpTransport send_transport(NULL, + message_loop.message_loop_proxy(), + free_local_port1, + free_local_port2, + base::Bind(&UpdateCastTransportStatus)); + UdpTransport recv_transport(NULL, + message_loop.message_loop_proxy(), + free_local_port2, + net::IPEndPoint(empty_addr_number, 0), + base::Bind(&UpdateCastTransportStatus)); + + Packet packet; + packet.push_back('t'); + packet.push_back('e'); + packet.push_back('s'); + packet.push_back('t'); + + base::RunLoop run_loop; + MockPacketReceiver receiver1(run_loop.QuitClosure()); + MockPacketReceiver receiver2( + base::Bind(&SendPacket, &recv_transport, packet)); + send_transport.StartReceiving(receiver1.packet_receiver()); + recv_transport.StartReceiving(receiver2.packet_receiver()); + + base::Closure cb; + send_transport.SendPacket(new base::RefCountedData<Packet>(packet), cb); + run_loop.Run(); + EXPECT_TRUE( + std::equal(packet.begin(), packet.end(), receiver1.packet().begin())); + EXPECT_TRUE( + std::equal(packet.begin(), packet.end(), receiver2.packet().begin())); +} + +} // namespace transport +} // namespace cast +} // namespace media diff --git a/chromium/media/cast/transport/utility/transport_encryption_handler.cc b/chromium/media/cast/transport/utility/transport_encryption_handler.cc new file mode 100644 index 00000000000..89db2cf95b3 --- /dev/null +++ b/chromium/media/cast/transport/utility/transport_encryption_handler.cc @@ -0,0 +1,76 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/cast/transport/utility/transport_encryption_handler.h" + +#include "base/logging.h" +#include "crypto/encryptor.h" +#include "crypto/symmetric_key.h" +#include "media/cast/transport/cast_transport_defines.h" + +namespace media { +namespace cast { +namespace transport { + +TransportEncryptionHandler::TransportEncryptionHandler() + : key_(), encryptor_(), iv_mask_(), initialized_(false) {} + +TransportEncryptionHandler::~TransportEncryptionHandler() {} + +bool TransportEncryptionHandler::Initialize(std::string aes_key, + std::string aes_iv_mask) { + initialized_ = false; + if (aes_iv_mask.size() == kAesKeySize && aes_key.size() == kAesKeySize) { + iv_mask_ = aes_iv_mask; + key_.reset( + crypto::SymmetricKey::Import(crypto::SymmetricKey::AES, aes_key)); + encryptor_.reset(new crypto::Encryptor()); + encryptor_->Init(key_.get(), crypto::Encryptor::CTR, std::string()); + initialized_ = true; + } else if (aes_iv_mask.size() != 0 || aes_key.size() != 0) { + DCHECK_EQ(aes_iv_mask.size(), 0u) + << "Invalid Crypto configuration: aes_iv_mask.size"; + DCHECK_EQ(aes_key.size(), 0u) + << "Invalid Crypto configuration: aes_key.size"; + return false; + } + return true; +} + +bool TransportEncryptionHandler::Encrypt(uint32 frame_id, + const base::StringPiece& data, + std::string* encrypted_data) { + if (!initialized_) + return false; + if (!encryptor_->SetCounter(GetAesNonce(frame_id, iv_mask_))) { + NOTREACHED() << "Failed to set counter"; + return false; + } + if (!encryptor_->Encrypt(data, encrypted_data)) { + NOTREACHED() << "Encrypt error"; + return false; + } + return true; +} + +bool TransportEncryptionHandler::Decrypt(uint32 frame_id, + const base::StringPiece& ciphertext, + std::string* plaintext) { + if (!initialized_) { + return false; + } + if (!encryptor_->SetCounter(transport::GetAesNonce(frame_id, iv_mask_))) { + NOTREACHED() << "Failed to set counter"; + return false; + } + if (!encryptor_->Decrypt(ciphertext, plaintext)) { + VLOG(1) << "Decryption error"; + return false; + } + return true; +} + +} // namespace transport +} // namespace cast +} // namespace media diff --git a/chromium/media/cast/transport/utility/transport_encryption_handler.h b/chromium/media/cast/transport/utility/transport_encryption_handler.h new file mode 100644 index 00000000000..06d0e3f34d6 --- /dev/null +++ b/chromium/media/cast/transport/utility/transport_encryption_handler.h @@ -0,0 +1,58 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_CAST_TRANSPORT_TRANSPORT_UTILITY_ENCRYPTION_HANDLER_H_ +#define MEDIA_CAST_TRANSPORT_TRANSPORT_UTILITY_ENCRYPTION_HANDLER_H_ + +// Helper class to handle encryption for the Cast Transport library. +#include <string> + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "base/strings/string_piece.h" +#include "base/threading/non_thread_safe.h" + +namespace crypto { +class Encryptor; +class SymmetricKey; +} + +namespace media { +namespace cast { +namespace transport { + +class TransportEncryptionHandler : public base::NonThreadSafe { + public: + TransportEncryptionHandler(); + ~TransportEncryptionHandler(); + + bool Initialize(std::string aes_key, std::string aes_iv_mask); + + bool Encrypt(uint32 frame_id, + const base::StringPiece& data, + std::string* encrypted_data); + + bool Decrypt(uint32 frame_id, + const base::StringPiece& ciphertext, + std::string* plaintext); + + // TODO(miu): This naming is very misleading. It should be called + // is_activated() since Initialize() without keys (i.e., cypto is disabled) + // may have succeeded. + bool initialized() const { return initialized_; } + + private: + scoped_ptr<crypto::SymmetricKey> key_; + scoped_ptr<crypto::Encryptor> encryptor_; + std::string iv_mask_; + bool initialized_; + + DISALLOW_COPY_AND_ASSIGN(TransportEncryptionHandler); +}; + +} // namespace transport +} // namespace cast +} // namespace media + +#endif // MEDIA_CAST_TRANSPORT_TRANSPORT_UTILITY_ENCRYPTION_HANDLER_H_ diff --git a/chromium/media/cast/video_receiver/codecs/vp8/vp8_decoder.cc b/chromium/media/cast/video_receiver/codecs/vp8/vp8_decoder.cc deleted file mode 100644 index 10fcb85d36e..00000000000 --- a/chromium/media/cast/video_receiver/codecs/vp8/vp8_decoder.cc +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "media/cast/video_receiver/codecs/vp8/vp8_decoder.h" - -#include "base/bind.h" -#include "base/logging.h" -#include "base/message_loop/message_loop.h" -#include "media/base/video_frame.h" -#include "media/base/video_util.h" -#include "third_party/libvpx/source/libvpx/vpx/vp8dx.h" -#include "ui/gfx/size.h" - -namespace media { -namespace cast { - -void LogFrameDecodedEvent(CastEnvironment* const cast_environment, - uint32 frame_id) { -// TODO(mikhal): Sort out passing of rtp_timestamp. -// cast_environment->Logging()->InsertFrameEvent(kVideoFrameDecoded, -// 0, frame_id); -} - -Vp8Decoder::Vp8Decoder(scoped_refptr<CastEnvironment> cast_environment) - : decoder_(new vpx_dec_ctx_t()), - cast_environment_(cast_environment) { - // Make sure that we initialize the decoder from the correct thread. - cast_environment_->PostTask(CastEnvironment::VIDEO_DECODER, FROM_HERE, - base::Bind(&Vp8Decoder::InitDecoder, base::Unretained(this))); -} - -Vp8Decoder::~Vp8Decoder() {} - -void Vp8Decoder::InitDecoder() { - vpx_codec_dec_cfg_t cfg; - // Initializing to use one core. - cfg.threads = 1; - vpx_codec_flags_t flags = VPX_CODEC_USE_POSTPROC; - - if (vpx_codec_dec_init(decoder_.get(), vpx_codec_vp8_dx(), &cfg, flags)) { - DCHECK(false) << "VP8 decode error"; - } -} - -bool Vp8Decoder::Decode(const EncodedVideoFrame* encoded_frame, - const base::TimeTicks render_time, - const VideoFrameDecodedCallback& frame_decoded_cb) { - DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::VIDEO_DECODER)); - const int frame_id_int = static_cast<int>(encoded_frame->frame_id); - VLOG(1) << "VP8 decode frame:" << frame_id_int - << " sized:" << encoded_frame->data.size(); - - if (encoded_frame->data.empty()) return false; - - vpx_codec_iter_t iter = NULL; - vpx_image_t* img; - if (vpx_codec_decode( - decoder_.get(), - reinterpret_cast<const uint8*>(encoded_frame->data.data()), - static_cast<unsigned int>(encoded_frame->data.size()), - 0, - 1 /* real time*/)) { - VLOG(1) << "Failed to decode VP8 frame."; - return false; - } - - img = vpx_codec_get_frame(decoder_.get(), &iter); - if (img == NULL) { - VLOG(1) << "Skip rendering VP8 frame:" << frame_id_int; - return false; - } - - gfx::Size visible_size(img->d_w, img->d_h); - gfx::Size full_size(img->stride[VPX_PLANE_Y], img->d_h); - DCHECK(VideoFrame::IsValidConfig(VideoFrame::I420, visible_size, - gfx::Rect(visible_size), full_size)); - // Temp timing setting - will sort out timing in a follow up cl. - scoped_refptr<VideoFrame> decoded_frame = - VideoFrame::CreateFrame(VideoFrame::I420, visible_size, - gfx::Rect(visible_size), full_size, base::TimeDelta()); - - // Copy each plane individually (need to account for stride). - // TODO(mikhal): Eliminate copy once http://crbug.com/321856 is resolved. - CopyPlane(VideoFrame::kYPlane, img->planes[VPX_PLANE_Y], - img->stride[VPX_PLANE_Y], img->d_h, decoded_frame.get()); - CopyPlane(VideoFrame::kUPlane, img->planes[VPX_PLANE_U], - img->stride[VPX_PLANE_U], (img->d_h + 1) / 2, decoded_frame.get()); - CopyPlane(VideoFrame::kVPlane, img->planes[VPX_PLANE_V], - img->stride[VPX_PLANE_V], (img->d_h + 1) / 2, decoded_frame.get()); - - // Log:: Decoding complete (should be called from the main thread). - cast_environment_->PostTask(CastEnvironment::MAIN, FROM_HERE, base::Bind( - LogFrameDecodedEvent, cast_environment_,encoded_frame->frame_id)); - - VLOG(1) << "Decoded frame " << frame_id_int; - // Frame decoded - return frame to the user via callback. - cast_environment_->PostTask(CastEnvironment::MAIN, FROM_HERE, - base::Bind(frame_decoded_cb, decoded_frame, render_time)); - - return true; -} - -} // namespace cast -} // namespace media - diff --git a/chromium/media/cast/video_receiver/codecs/vp8/vp8_decoder.gyp b/chromium/media/cast/video_receiver/codecs/vp8/vp8_decoder.gyp deleted file mode 100644 index 4bc9434d2d2..00000000000 --- a/chromium/media/cast/video_receiver/codecs/vp8/vp8_decoder.gyp +++ /dev/null @@ -1,25 +0,0 @@ -# Copyright 2013 The Chromium Authors. All rights reserved. -# Use of the source code is governed by a BSD-style license that can be -# found in the LICENSE file. - -{ - 'targets': [ - { - 'target_name': 'cast_vp8_decoder', - 'type': 'static_library', - 'include_dirs': [ - '<(DEPTH)/', - '<(DEPTH)/third_party/', - '<(DEPTH)/third_party/libvpx/', - ], - 'sources': [ - 'vp8_decoder.cc', - 'vp8_decoder.h', - ], # source - 'dependencies': [ - '<(DEPTH)/base/base.gyp:base', - '<(DEPTH)/third_party/libvpx/libvpx.gyp:libvpx', - ], - }, - ], -} diff --git a/chromium/media/cast/video_receiver/codecs/vp8/vp8_decoder.h b/chromium/media/cast/video_receiver/codecs/vp8/vp8_decoder.h deleted file mode 100644 index 6a93c41abc9..00000000000 --- a/chromium/media/cast/video_receiver/codecs/vp8/vp8_decoder.h +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef MEDIA_CAST_RTP_RECEVIER_CODECS_VP8_VP8_DECODER_H_ -#define MEDIA_CAST_RTP_RECEVIER_CODECS_VP8_VP8_DECODER_H_ - -#include "base/memory/scoped_ptr.h" -#include "base/threading/non_thread_safe.h" -#include "media/cast/cast_config.h" -#include "media/cast/cast_environment.h" -#include "media/cast/cast_receiver.h" -#include "third_party/libvpx/source/libvpx/vpx/vpx_decoder.h" - -typedef struct vpx_codec_ctx vpx_dec_ctx_t; - -// TODO(mikhal): Look into reusing VpxVideoDecoder. -namespace media { -namespace cast { - -// This class is not thread safe; it's only called from the cast video decoder -// thread. -class Vp8Decoder : public base::NonThreadSafe { - public: - explicit Vp8Decoder(scoped_refptr<CastEnvironment> cast_environment); - ~Vp8Decoder(); - - // Decode frame - The decoded frame will be passed via the callback. - // Will return false in case of error, and then it's up to the caller to - // release the memory. - // Ownership of the encoded_frame does not pass to the Vp8Decoder. - bool Decode(const EncodedVideoFrame* encoded_frame, - const base::TimeTicks render_time, - const VideoFrameDecodedCallback& frame_decoded_cb); - - private: - // Initialize the decoder. - void InitDecoder(); - scoped_ptr<vpx_dec_ctx_t> decoder_; - scoped_refptr<CastEnvironment> cast_environment_; -}; - -} // namespace cast -} // namespace media - -#endif // MEDIA_CAST_RTP_RECEVIER_CODECS_VP8_VP8_DECODER_H_ diff --git a/chromium/media/cast/video_receiver/video_decoder.cc b/chromium/media/cast/video_receiver/video_decoder.cc deleted file mode 100644 index 360cdaa36e9..00000000000 --- a/chromium/media/cast/video_receiver/video_decoder.cc +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "media/cast/video_receiver/video_decoder.h" - -#include "base/bind.h" -#include "base/logging.h" -#include "base/message_loop/message_loop.h" -#include "media/cast/video_receiver/codecs/vp8/vp8_decoder.h" - -namespace media { -namespace cast { - -VideoDecoder::VideoDecoder(const VideoReceiverConfig& video_config, - scoped_refptr<CastEnvironment> cast_environment) - : codec_(video_config.codec), - vp8_decoder_() { - switch (video_config.codec) { - case kVp8: - vp8_decoder_.reset(new Vp8Decoder(cast_environment)); - break; - case kH264: - NOTIMPLEMENTED(); - break; - case kExternalVideo: - DCHECK(false) << "Invalid codec"; - break; - } -} - -VideoDecoder::~VideoDecoder() {} - -bool VideoDecoder::DecodeVideoFrame(const EncodedVideoFrame* encoded_frame, - const base::TimeTicks render_time, - const VideoFrameDecodedCallback& - frame_decoded_cb) { - DCHECK(encoded_frame->codec == codec_) << "Invalid codec"; - DCHECK_GT(encoded_frame->data.size(), GG_UINT64_C(0)) << "Empty video frame"; - return vp8_decoder_->Decode(encoded_frame, render_time, frame_decoded_cb); -} - -} // namespace cast -} // namespace media diff --git a/chromium/media/cast/video_receiver/video_decoder.h b/chromium/media/cast/video_receiver/video_decoder.h deleted file mode 100644 index 97a8a62cc70..00000000000 --- a/chromium/media/cast/video_receiver/video_decoder.h +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef MEDIA_CAST_VIDEO_RECEIVER_VIDEO_DECODER_H_ -#define MEDIA_CAST_VIDEO_RECEIVER_VIDEO_DECODER_H_ - -#include "base/memory/scoped_ptr.h" -#include "base/threading/non_thread_safe.h" -#include "media/cast/cast_config.h" -#include "media/cast/cast_receiver.h" - -namespace media { -namespace cast { - -class Vp8Decoder; -class VideoFrame; - -// This class is not thread safe; it's only called from the cast video decoder -// thread. -class VideoDecoder : public base::NonThreadSafe { - public: - VideoDecoder(const VideoReceiverConfig& video_config, - scoped_refptr<CastEnvironment> cast_environment); - virtual ~VideoDecoder(); - - // Decode a video frame. Decoded (raw) frame will be returned via the - // provided callback - bool DecodeVideoFrame(const EncodedVideoFrame* encoded_frame, - const base::TimeTicks render_time, - const VideoFrameDecodedCallback& frame_decoded_cb); - - private: - VideoCodec codec_; - scoped_ptr<Vp8Decoder> vp8_decoder_; - - DISALLOW_COPY_AND_ASSIGN(VideoDecoder); -}; - -} // namespace cast -} // namespace media - -#endif // MEDIA_CAST_VIDEO_RECEIVER_VIDEO_DECODER_H_ diff --git a/chromium/media/cast/video_receiver/video_decoder_unittest.cc b/chromium/media/cast/video_receiver/video_decoder_unittest.cc deleted file mode 100644 index 6405d1d7bee..00000000000 --- a/chromium/media/cast/video_receiver/video_decoder_unittest.cc +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "base/bind.h" -#include "base/memory/scoped_ptr.h" -#include "base/test/simple_test_tick_clock.h" -#include "base/time/tick_clock.h" -#include "media/cast/cast_config.h" -#include "media/cast/cast_defines.h" -#include "media/cast/cast_environment.h" -#include "media/cast/cast_receiver.h" -#include "media/cast/test/fake_task_runner.h" -#include "media/cast/video_receiver/video_decoder.h" -#include "testing/gmock/include/gmock/gmock.h" - -namespace media { -namespace cast { - -using testing::_; - -// Random frame size for testing. -const int kFrameSize = 2345; -static const int64 kStartMillisecond = GG_INT64_C(1245); - -namespace { -class DecodeTestFrameCallback : - public base::RefCountedThreadSafe<DecodeTestFrameCallback> { - public: - DecodeTestFrameCallback() {} - - void DecodeComplete(const scoped_refptr<media::VideoFrame>& decoded_frame, - const base::TimeTicks& render_time) {} - protected: - virtual ~DecodeTestFrameCallback() {} - private: - friend class base::RefCountedThreadSafe<DecodeTestFrameCallback>; -}; -} // namespace - -class VideoDecoderTest : public ::testing::Test { - protected: - VideoDecoderTest() - : task_runner_(new test::FakeTaskRunner(&testing_clock_)), - cast_environment_(new CastEnvironment(&testing_clock_, task_runner_, - task_runner_, task_runner_, task_runner_, task_runner_, - GetDefaultCastLoggingConfig())), - test_callback_(new DecodeTestFrameCallback()) { - // Configure to vp8. - config_.codec = kVp8; - config_.use_external_decoder = false; - decoder_.reset(new VideoDecoder(config_, cast_environment_)); - testing_clock_.Advance( - base::TimeDelta::FromMilliseconds(kStartMillisecond)); - } - - virtual ~VideoDecoderTest() {} - - scoped_ptr<VideoDecoder> decoder_; - VideoReceiverConfig config_; - base::SimpleTestTickClock testing_clock_; - scoped_refptr<test::FakeTaskRunner> task_runner_; - scoped_refptr<CastEnvironment> cast_environment_; - scoped_refptr<DecodeTestFrameCallback> test_callback_; -}; - -// TODO(pwestin): EXPECT_DEATH tests can not pass valgrind. -TEST_F(VideoDecoderTest, DISABLED_SizeZero) { - EncodedVideoFrame encoded_frame; - base::TimeTicks render_time; - encoded_frame.codec = kVp8; - EXPECT_DEATH( - decoder_->DecodeVideoFrame( - &encoded_frame, render_time, - base::Bind(&DecodeTestFrameCallback::DecodeComplete, test_callback_)), - "Empty frame"); -} - -// TODO(pwestin): EXPECT_DEATH tests can not pass valgrind. -TEST_F(VideoDecoderTest, DISABLED_InvalidCodec) { - EncodedVideoFrame encoded_frame; - base::TimeTicks render_time; - encoded_frame.data.assign(kFrameSize, 0); - encoded_frame.codec = kExternalVideo; - EXPECT_DEATH( - decoder_->DecodeVideoFrame(&encoded_frame, render_time, base::Bind( - &DecodeTestFrameCallback::DecodeComplete, test_callback_)), - "Invalid codec"); -} - -// TODO(pwestin): Test decoding a real frame. - -} // namespace cast -} // namespace media diff --git a/chromium/media/cast/video_receiver/video_receiver.cc b/chromium/media/cast/video_receiver/video_receiver.cc deleted file mode 100644 index 98bed1fc699..00000000000 --- a/chromium/media/cast/video_receiver/video_receiver.cc +++ /dev/null @@ -1,465 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "media/cast/video_receiver/video_receiver.h" - -#include <algorithm> - -#include "base/bind.h" -#include "base/logging.h" -#include "base/message_loop/message_loop.h" -#include "crypto/encryptor.h" -#include "crypto/symmetric_key.h" -#include "media/cast/cast_defines.h" -#include "media/cast/framer/framer.h" -#include "media/cast/video_receiver/video_decoder.h" - -namespace media { -namespace cast { - -const int64 kMinSchedulingDelayMs = 1; - -static const int64 kMinTimeBetweenOffsetUpdatesMs = 2000; -static const int kTimeOffsetFilter = 8; -static const int64_t kMinProcessIntervalMs = 5; - -// Local implementation of RtpData (defined in rtp_rtcp_defines.h). -// Used to pass payload data into the video receiver. -class LocalRtpVideoData : public RtpData { - public: - explicit LocalRtpVideoData(VideoReceiver* video_receiver) - : video_receiver_(video_receiver) {} - - virtual ~LocalRtpVideoData() {} - - virtual void OnReceivedPayloadData(const uint8* payload_data, - size_t payload_size, - const RtpCastHeader* rtp_header) OVERRIDE { - video_receiver_->IncomingParsedRtpPacket(payload_data, payload_size, - *rtp_header); - } - - private: - VideoReceiver* video_receiver_; -}; - -// Local implementation of RtpPayloadFeedback (defined in rtp_defines.h) -// Used to convey cast-specific feedback from receiver to sender. -// Callback triggered by the Framer (cast message builder). -class LocalRtpVideoFeedback : public RtpPayloadFeedback { - public: - explicit LocalRtpVideoFeedback(VideoReceiver* video_receiver) - : video_receiver_(video_receiver) { - } - - virtual void CastFeedback(const RtcpCastMessage& cast_message) OVERRIDE { - video_receiver_->CastFeedback(cast_message); - } - - private: - VideoReceiver* video_receiver_; -}; - -// Local implementation of RtpReceiverStatistics (defined by rtcp.h). -// Used to pass statistics data from the RTP module to the RTCP module. -class LocalRtpReceiverStatistics : public RtpReceiverStatistics { - public: - explicit LocalRtpReceiverStatistics(RtpReceiver* rtp_receiver) - : rtp_receiver_(rtp_receiver) { - } - - virtual void GetStatistics(uint8* fraction_lost, - uint32* cumulative_lost, // 24 bits valid. - uint32* extended_high_sequence_number, - uint32* jitter) OVERRIDE { - rtp_receiver_->GetStatistics(fraction_lost, - cumulative_lost, - extended_high_sequence_number, - jitter); - } - - private: - RtpReceiver* rtp_receiver_; -}; - -VideoReceiver::VideoReceiver(scoped_refptr<CastEnvironment> cast_environment, - const VideoReceiverConfig& video_config, - PacedPacketSender* const packet_sender) - : cast_environment_(cast_environment), - codec_(video_config.codec), - target_delay_delta_( - base::TimeDelta::FromMilliseconds(video_config.rtp_max_delay_ms)), - frame_delay_(base::TimeDelta::FromMilliseconds( - 1000 / video_config.max_frame_rate)), - incoming_payload_callback_(new LocalRtpVideoData(this)), - incoming_payload_feedback_(new LocalRtpVideoFeedback(this)), - rtp_receiver_(cast_environment_->Clock(), NULL, &video_config, - incoming_payload_callback_.get()), - rtp_video_receiver_statistics_( - new LocalRtpReceiverStatistics(&rtp_receiver_)), - time_incoming_packet_updated_(false), - incoming_rtp_timestamp_(0), - weak_factory_(this) { - int max_unacked_frames = video_config.rtp_max_delay_ms * - video_config.max_frame_rate / 1000; - DCHECK(max_unacked_frames) << "Invalid argument"; - - if (video_config.aes_iv_mask.size() == kAesKeySize && - video_config.aes_key.size() == kAesKeySize) { - iv_mask_ = video_config.aes_iv_mask; - crypto::SymmetricKey* key = crypto::SymmetricKey::Import( - crypto::SymmetricKey::AES, video_config.aes_key); - decryptor_.reset(new crypto::Encryptor()); - decryptor_->Init(key, crypto::Encryptor::CTR, std::string()); - } else if (video_config.aes_iv_mask.size() != 0 || - video_config.aes_key.size() != 0) { - DCHECK(false) << "Invalid crypto configuration"; - } - - framer_.reset(new Framer(cast_environment->Clock(), - incoming_payload_feedback_.get(), - video_config.incoming_ssrc, - video_config.decoder_faster_than_max_frame_rate, - max_unacked_frames)); - if (!video_config.use_external_decoder) { - video_decoder_.reset(new VideoDecoder(video_config, cast_environment)); - } - - rtcp_.reset( - new Rtcp(cast_environment_, - NULL, - packet_sender, - NULL, - rtp_video_receiver_statistics_.get(), - video_config.rtcp_mode, - base::TimeDelta::FromMilliseconds(video_config.rtcp_interval), - video_config.feedback_ssrc, - video_config.incoming_ssrc, - video_config.rtcp_c_name)); -} - -VideoReceiver::~VideoReceiver() {} - -void VideoReceiver::InitializeTimers() { - DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); - ScheduleNextRtcpReport(); - ScheduleNextCastMessage(); -} - -void VideoReceiver::GetRawVideoFrame( - const VideoFrameDecodedCallback& callback) { - DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); - GetEncodedVideoFrame(base::Bind(&VideoReceiver::DecodeVideoFrame, - base::Unretained(this), callback)); -} - -// Called when we have a frame to decode. -void VideoReceiver::DecodeVideoFrame( - const VideoFrameDecodedCallback& callback, - scoped_ptr<EncodedVideoFrame> encoded_frame, - const base::TimeTicks& render_time) { - DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); - // Hand the ownership of the encoded frame to the decode thread. - cast_environment_->PostTask(CastEnvironment::VIDEO_DECODER, FROM_HERE, - base::Bind(&VideoReceiver::DecodeVideoFrameThread, base::Unretained(this), - base::Passed(&encoded_frame), render_time, callback)); -} - -// Utility function to run the decoder on a designated decoding thread. -void VideoReceiver::DecodeVideoFrameThread( - scoped_ptr<EncodedVideoFrame> encoded_frame, - const base::TimeTicks render_time, - const VideoFrameDecodedCallback& frame_decoded_callback) { - DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::VIDEO_DECODER)); - DCHECK(video_decoder_); - - if (!(video_decoder_->DecodeVideoFrame(encoded_frame.get(), render_time, - frame_decoded_callback))) { - // This will happen if we decide to decode but not show a frame. - cast_environment_->PostTask(CastEnvironment::MAIN, FROM_HERE, - base::Bind(&VideoReceiver::GetRawVideoFrame, base::Unretained(this), - frame_decoded_callback)); - } -} - -bool VideoReceiver::DecryptVideoFrame( - scoped_ptr<EncodedVideoFrame>* video_frame) { - DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); - DCHECK(decryptor_) << "Invalid state"; - - if (!decryptor_->SetCounter(GetAesNonce((*video_frame)->frame_id, - iv_mask_))) { - NOTREACHED() << "Failed to set counter"; - return false; - } - std::string decrypted_video_data; - if (!decryptor_->Decrypt((*video_frame)->data, &decrypted_video_data)) { - VLOG(1) << "Decryption error"; - // Give up on this frame, release it from jitter buffer. - framer_->ReleaseFrame((*video_frame)->frame_id); - return false; - } - (*video_frame)->data.swap(decrypted_video_data); - return true; -} - -// Called from the main cast thread. -void VideoReceiver::GetEncodedVideoFrame( - const VideoFrameEncodedCallback& callback) { - DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); - scoped_ptr<EncodedVideoFrame> encoded_frame(new EncodedVideoFrame()); - uint32 rtp_timestamp = 0; - bool next_frame = false; - - if (!framer_->GetEncodedVideoFrame(encoded_frame.get(), &rtp_timestamp, - &next_frame)) { - // We have no video frames. Wait for new packet(s). - queued_encoded_callbacks_.push_back(callback); - return; - } - - if (decryptor_ && !DecryptVideoFrame(&encoded_frame)) { - // Logging already done. - queued_encoded_callbacks_.push_back(callback); - return; - } - - base::TimeTicks render_time; - if (PullEncodedVideoFrame(rtp_timestamp, next_frame, &encoded_frame, - &render_time)) { - cast_environment_->PostTask(CastEnvironment::MAIN, FROM_HERE, - base::Bind(callback, base::Passed(&encoded_frame), render_time)); - } else { - // We have a video frame; however we are missing packets and we have time - // to wait for new packet(s). - queued_encoded_callbacks_.push_back(callback); - } -} - -// Should we pull the encoded video frame from the framer? decided by if this is -// the next frame or we are running out of time and have to pull the following -// frame. -// If the frame is too old to be rendered we set the don't show flag in the -// video bitstream where possible. -bool VideoReceiver::PullEncodedVideoFrame(uint32 rtp_timestamp, - bool next_frame, scoped_ptr<EncodedVideoFrame>* encoded_frame, - base::TimeTicks* render_time) { - DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); - base::TimeTicks now = cast_environment_->Clock()->NowTicks(); - *render_time = GetRenderTime(now, rtp_timestamp); - - // TODO(mikhal): Store actual render time and not diff. - cast_environment_->Logging()->InsertFrameEventWithDelay(kVideoRenderDelay, - rtp_timestamp, (*encoded_frame)->frame_id, now - *render_time); - - // Minimum time before a frame is due to be rendered before we pull it for - // decode. - base::TimeDelta min_wait_delta = frame_delay_; - base::TimeDelta time_until_render = *render_time - now; - if (!next_frame && (time_until_render > min_wait_delta)) { - // Example: - // We have decoded frame 1 and we have received the complete frame 3, but - // not frame 2. If we still have time before frame 3 should be rendered we - // will wait for 2 to arrive, however if 2 never show up this timer will hit - // and we will pull out frame 3 for decoding and rendering. - base::TimeDelta time_until_release = time_until_render - min_wait_delta; - cast_environment_->PostDelayedTask(CastEnvironment::MAIN, FROM_HERE, - base::Bind(&VideoReceiver::PlayoutTimeout, weak_factory_.GetWeakPtr()), - time_until_release); - VLOG(1) << "Wait before releasing frame " - << static_cast<int>((*encoded_frame)->frame_id) - << " time " << time_until_release.InMilliseconds(); - return false; - } - - base::TimeDelta dont_show_timeout_delta = - base::TimeDelta::FromMilliseconds(-kDontShowTimeoutMs); - if (codec_ == kVp8 && time_until_render < dont_show_timeout_delta) { - (*encoded_frame)->data[0] &= 0xef; - VLOG(1) << "Don't show frame " - << static_cast<int>((*encoded_frame)->frame_id) - << " time_until_render:" << time_until_render.InMilliseconds(); - } else { - VLOG(1) << "Show frame " - << static_cast<int>((*encoded_frame)->frame_id) - << " time_until_render:" << time_until_render.InMilliseconds(); - } - // We have a copy of the frame, release this one. - framer_->ReleaseFrame((*encoded_frame)->frame_id); - (*encoded_frame)->codec = codec_; - return true; -} - -void VideoReceiver::PlayoutTimeout() { - DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); - if (queued_encoded_callbacks_.empty()) return; - - uint32 rtp_timestamp = 0; - bool next_frame = false; - scoped_ptr<EncodedVideoFrame> encoded_frame(new EncodedVideoFrame()); - - if (!framer_->GetEncodedVideoFrame(encoded_frame.get(), &rtp_timestamp, - &next_frame)) { - // We have no video frames. Wait for new packet(s). - // Since the application can post multiple VideoFrameEncodedCallback and - // we only check the next frame to play out we might have multiple timeout - // events firing after each other; however this should be a rare event. - VLOG(1) << "Failed to retrieved a complete frame at this point in time"; - return; - } - VLOG(1) << "PlayoutTimeout retrieved frame " - << static_cast<int>(encoded_frame->frame_id); - - if (decryptor_ && !DecryptVideoFrame(&encoded_frame)) { - // Logging already done. - return; - } - - base::TimeTicks render_time; - if (PullEncodedVideoFrame(rtp_timestamp, next_frame, &encoded_frame, - &render_time)) { - if (!queued_encoded_callbacks_.empty()) { - VideoFrameEncodedCallback callback = queued_encoded_callbacks_.front(); - queued_encoded_callbacks_.pop_front(); - cast_environment_->PostTask(CastEnvironment::MAIN, FROM_HERE, - base::Bind(callback, base::Passed(&encoded_frame), render_time)); - } - } else { - // We have a video frame; however we are missing packets and we have time - // to wait for new packet(s). - } -} - -base::TimeTicks VideoReceiver::GetRenderTime(base::TimeTicks now, - uint32 rtp_timestamp) { - DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); - // Senders time in ms when this frame was captured. - // Note: the senders clock and our local clock might not be synced. - base::TimeTicks rtp_timestamp_in_ticks; - - if (time_offset_.InMilliseconds() == 0) { - if (!rtcp_->RtpTimestampInSenderTime(kVideoFrequency, - incoming_rtp_timestamp_, - &rtp_timestamp_in_ticks)) { - // We have not received any RTCP to sync the stream play it out as soon as - // possible. - return now; - } - time_offset_ = time_incoming_packet_ - rtp_timestamp_in_ticks; - } else if (time_incoming_packet_updated_) { - if (rtcp_->RtpTimestampInSenderTime(kVideoFrequency, - incoming_rtp_timestamp_, - &rtp_timestamp_in_ticks)) { - // Time to update the time_offset. - base::TimeDelta time_offset = - time_incoming_packet_ - rtp_timestamp_in_ticks; - time_offset_ = ((kTimeOffsetFilter - 1) * time_offset_ + time_offset) - / kTimeOffsetFilter; - } - } - // Reset |time_incoming_packet_updated_| to enable a future measurement. - time_incoming_packet_updated_ = false; - if (!rtcp_->RtpTimestampInSenderTime(kVideoFrequency, - rtp_timestamp, - &rtp_timestamp_in_ticks)) { - // This can fail if we have not received any RTCP packets in a long time. - return now; - } - return (rtp_timestamp_in_ticks + time_offset_ + target_delay_delta_); -} - -void VideoReceiver::IncomingPacket(const uint8* packet, size_t length, - const base::Closure callback) { - DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); - if (Rtcp::IsRtcpPacket(packet, length)) { - rtcp_->IncomingRtcpPacket(packet, length); - } else { - rtp_receiver_.ReceivedPacket(packet, length); - } - cast_environment_->PostTask(CastEnvironment::MAIN, FROM_HERE, callback); -} - -void VideoReceiver::IncomingParsedRtpPacket(const uint8* payload_data, - size_t payload_size, - const RtpCastHeader& rtp_header) { - DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); - - base::TimeTicks now = cast_environment_->Clock()->NowTicks(); - if (time_incoming_packet_.is_null() || now - time_incoming_packet_ > - base::TimeDelta::FromMilliseconds(kMinTimeBetweenOffsetUpdatesMs)) { - if (time_incoming_packet_.is_null()) InitializeTimers(); - incoming_rtp_timestamp_ = rtp_header.webrtc.header.timestamp; - time_incoming_packet_ = now; - time_incoming_packet_updated_ = true; - } - - cast_environment_->Logging()->InsertPacketEvent(kPacketReceived, - rtp_header.webrtc.header.timestamp, rtp_header.frame_id, - rtp_header.packet_id, rtp_header.max_packet_id, payload_size); - - bool complete = framer_->InsertPacket(payload_data, payload_size, rtp_header); - - if (!complete) return; // Video frame not complete; wait for more packets. - if (queued_encoded_callbacks_.empty()) return; // No pending callback. - - VideoFrameEncodedCallback callback = queued_encoded_callbacks_.front(); - queued_encoded_callbacks_.pop_front(); - cast_environment_->PostTask(CastEnvironment::MAIN, FROM_HERE, - base::Bind(&VideoReceiver::GetEncodedVideoFrame, - weak_factory_.GetWeakPtr(), callback)); -} - -// Send a cast feedback message. Actual message created in the framer (cast -// message builder). -void VideoReceiver::CastFeedback(const RtcpCastMessage& cast_message) { - DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); - // TODO(pwestin): wire up log messages. - rtcp_->SendRtcpFromRtpReceiver(&cast_message, NULL); - time_last_sent_cast_message_= cast_environment_->Clock()->NowTicks(); -} - -// Cast messages should be sent within a maximum interval. Schedule a call -// if not triggered elsewhere, e.g. by the cast message_builder. -void VideoReceiver::ScheduleNextCastMessage() { - DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); - base::TimeTicks send_time; - framer_->TimeToSendNextCastMessage(&send_time); - - base::TimeDelta time_to_send = send_time - - cast_environment_->Clock()->NowTicks(); - time_to_send = std::max(time_to_send, - base::TimeDelta::FromMilliseconds(kMinSchedulingDelayMs)); - cast_environment_->PostDelayedTask(CastEnvironment::MAIN, FROM_HERE, - base::Bind(&VideoReceiver::SendNextCastMessage, - weak_factory_.GetWeakPtr()), time_to_send); -} - -void VideoReceiver::SendNextCastMessage() { - DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); - framer_->SendCastMessage(); // Will only send a message if it is time. - ScheduleNextCastMessage(); -} - -// Schedule the next RTCP report to be sent back to the sender. -void VideoReceiver::ScheduleNextRtcpReport() { - DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); - base::TimeDelta time_to_next = rtcp_->TimeToSendNextRtcpReport() - - cast_environment_->Clock()->NowTicks(); - - time_to_next = std::max(time_to_next, - base::TimeDelta::FromMilliseconds(kMinSchedulingDelayMs)); - - cast_environment_->PostDelayedTask(CastEnvironment::MAIN, FROM_HERE, - base::Bind(&VideoReceiver::SendNextRtcpReport, - weak_factory_.GetWeakPtr()), time_to_next); -} - -void VideoReceiver::SendNextRtcpReport() { - DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); - rtcp_->SendRtcpFromRtpReceiver(NULL, NULL); - ScheduleNextRtcpReport(); -} - -} // namespace cast -} // namespace media diff --git a/chromium/media/cast/video_receiver/video_receiver.gypi b/chromium/media/cast/video_receiver/video_receiver.gypi deleted file mode 100644 index e1a9902872e..00000000000 --- a/chromium/media/cast/video_receiver/video_receiver.gypi +++ /dev/null @@ -1,31 +0,0 @@ -# Copyright 2013 The Chromium Authors. All rights reserved. -# Use of the source code is governed by a BSD-style license that can be -# found in the LICENSE file. - -{ - 'targets': [ - { - 'target_name': 'cast_video_receiver', - 'type': 'static_library', - 'include_dirs': [ - '<(DEPTH)/', - '<(DEPTH)/third_party/', - '<(DEPTH)/third_party/webrtc', - ], - 'sources': [ - 'video_decoder.h', - 'video_decoder.cc', - 'video_receiver.h', - 'video_receiver.cc', - ], # source - 'dependencies': [ - '<(DEPTH)/crypto/crypto.gyp:crypto', - 'framer/framer.gyp:cast_framer', - 'video_receiver/codecs/vp8/vp8_decoder.gyp:cast_vp8_decoder', - 'rtp_receiver/rtp_receiver.gyp:cast_rtp_receiver', - ], - }, - ], -} - - diff --git a/chromium/media/cast/video_receiver/video_receiver.h b/chromium/media/cast/video_receiver/video_receiver.h deleted file mode 100644 index fbc3653a514..00000000000 --- a/chromium/media/cast/video_receiver/video_receiver.h +++ /dev/null @@ -1,133 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef MEDIA_CAST_VIDEO_RECEIVER_VIDEO_RECEIVER_H_ -#define MEDIA_CAST_VIDEO_RECEIVER_VIDEO_RECEIVER_H_ - -#include "base/basictypes.h" -#include "base/callback.h" -#include "base/memory/ref_counted.h" -#include "base/memory/scoped_ptr.h" -#include "base/memory/weak_ptr.h" -#include "base/threading/non_thread_safe.h" -#include "base/time/tick_clock.h" -#include "base/time/time.h" -#include "media/cast/cast_config.h" -#include "media/cast/cast_environment.h" -#include "media/cast/cast_receiver.h" -#include "media/cast/rtcp/rtcp.h" -#include "media/cast/rtp_receiver/rtp_receiver.h" -#include "media/cast/rtp_receiver/rtp_receiver_defines.h" - -namespace crypto { - class Encryptor; -} - -namespace media { -namespace cast { - -class Framer; -class LocalRtpVideoData; -class LocalRtpVideoFeedback; -class PacedPacketSender; -class PeerVideoReceiver; -class Rtcp; -class RtpReceiverStatistics; -class VideoDecoder; - -// Should only be called from the Main cast thread. -class VideoReceiver : public base::NonThreadSafe, - public base::SupportsWeakPtr<VideoReceiver> { - public: - VideoReceiver(scoped_refptr<CastEnvironment> cast_environment, - const VideoReceiverConfig& video_config, - PacedPacketSender* const packet_sender); - - virtual ~VideoReceiver(); - - // Request a raw frame. Will return frame via callback when available. - void GetRawVideoFrame(const VideoFrameDecodedCallback& callback); - - // Request an encoded frame. Will return frame via callback when available. - void GetEncodedVideoFrame(const VideoFrameEncodedCallback& callback); - - // Insert a RTP packet to the video receiver. - void IncomingPacket(const uint8* packet, size_t length, - const base::Closure callback); - - protected: - void IncomingParsedRtpPacket(const uint8* payload_data, - size_t payload_size, - const RtpCastHeader& rtp_header); - - void DecodeVideoFrameThread( - scoped_ptr<EncodedVideoFrame> encoded_frame, - const base::TimeTicks render_time, - const VideoFrameDecodedCallback& frame_decoded_callback); - - private: - friend class LocalRtpVideoData; - friend class LocalRtpVideoFeedback; - - void CastFeedback(const RtcpCastMessage& cast_message); - - void DecodeVideoFrame(const VideoFrameDecodedCallback& callback, - scoped_ptr<EncodedVideoFrame> encoded_frame, - const base::TimeTicks& render_time); - - bool DecryptVideoFrame(scoped_ptr<EncodedVideoFrame>* video_frame); - - bool PullEncodedVideoFrame(uint32 rtp_timestamp, - bool next_frame, - scoped_ptr<EncodedVideoFrame>* encoded_frame, - base::TimeTicks* render_time); - - void PlayoutTimeout(); - - // Returns Render time based on current time and the rtp timestamp. - base::TimeTicks GetRenderTime(base::TimeTicks now, uint32 rtp_timestamp); - - void InitializeTimers(); - - // Schedule timing for the next cast message. - void ScheduleNextCastMessage(); - - // Schedule timing for the next RTCP report. - void ScheduleNextRtcpReport(); - - // Actually send the next cast message. - void SendNextCastMessage(); - - // Actually send the next RTCP report. - void SendNextRtcpReport(); - - scoped_ptr<VideoDecoder> video_decoder_; - scoped_refptr<CastEnvironment> cast_environment_; - scoped_ptr<Framer> framer_; - const VideoCodec codec_; - base::TimeDelta target_delay_delta_; - base::TimeDelta frame_delay_; - scoped_ptr<LocalRtpVideoData> incoming_payload_callback_; - scoped_ptr<LocalRtpVideoFeedback> incoming_payload_feedback_; - RtpReceiver rtp_receiver_; - scoped_ptr<Rtcp> rtcp_; - scoped_ptr<RtpReceiverStatistics> rtp_video_receiver_statistics_; - base::TimeTicks time_last_sent_cast_message_; - base::TimeDelta time_offset_; // Sender-receiver offset estimation. - scoped_ptr<crypto::Encryptor> decryptor_; - std::string iv_mask_; - std::list<VideoFrameEncodedCallback> queued_encoded_callbacks_; - bool time_incoming_packet_updated_; - base::TimeTicks time_incoming_packet_; - uint32 incoming_rtp_timestamp_; - - base::WeakPtrFactory<VideoReceiver> weak_factory_; - - DISALLOW_COPY_AND_ASSIGN(VideoReceiver); -}; - -} // namespace cast -} // namespace media - -#endif // MEDIA_CAST_VIDEO_RECEIVER_VIDEO_RECEIVER_H_ diff --git a/chromium/media/cast/video_receiver/video_receiver_unittest.cc b/chromium/media/cast/video_receiver/video_receiver_unittest.cc deleted file mode 100644 index 8001ac430d6..00000000000 --- a/chromium/media/cast/video_receiver/video_receiver_unittest.cc +++ /dev/null @@ -1,169 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "base/bind.h" -#include "base/memory/ref_counted.h" -#include "base/memory/scoped_ptr.h" -#include "base/test/simple_test_tick_clock.h" -#include "media/cast/cast_defines.h" -#include "media/cast/cast_environment.h" -#include "media/cast/net/pacing/mock_paced_packet_sender.h" -#include "media/cast/test/fake_task_runner.h" -#include "media/cast/video_receiver/video_receiver.h" -#include "testing/gmock/include/gmock/gmock.h" - -static const int kPacketSize = 1500; -static const int64 kStartMillisecond = GG_INT64_C(12345678900000); - -namespace media { -namespace cast { - -using testing::_; - -namespace { -// Was thread counted thread safe. -class TestVideoReceiverCallback : - public base::RefCountedThreadSafe<TestVideoReceiverCallback> { - public: - TestVideoReceiverCallback() - : num_called_(0) {} - - // TODO(mikhal): Set and check expectations. - void DecodeComplete(const scoped_refptr<media::VideoFrame>& video_frame, - const base::TimeTicks& render_time) { - ++num_called_; - } - - void FrameToDecode(scoped_ptr<EncodedVideoFrame> video_frame, - const base::TimeTicks& render_time) { - EXPECT_TRUE(video_frame->key_frame); - EXPECT_EQ(kVp8, video_frame->codec); - ++num_called_; - } - - int number_times_called() const { return num_called_;} - - protected: - virtual ~TestVideoReceiverCallback() {} - - private: - friend class base::RefCountedThreadSafe<TestVideoReceiverCallback>; - - int num_called_; -}; -} // namespace - -class PeerVideoReceiver : public VideoReceiver { - public: - PeerVideoReceiver(scoped_refptr<CastEnvironment> cast_environment, - const VideoReceiverConfig& video_config, - PacedPacketSender* const packet_sender) - : VideoReceiver(cast_environment, video_config, packet_sender) { - } - using VideoReceiver::IncomingParsedRtpPacket; -}; - - -class VideoReceiverTest : public ::testing::Test { - protected: - VideoReceiverTest() { - // Configure to use vp8 software implementation. - config_.codec = kVp8; - config_.use_external_decoder = false; - task_runner_ = new test::FakeTaskRunner(&testing_clock_); - cast_environment_ = new CastEnvironment(&testing_clock_, task_runner_, - task_runner_, task_runner_, task_runner_, task_runner_, - GetDefaultCastLoggingConfig()); - receiver_.reset(new - PeerVideoReceiver(cast_environment_, config_, &mock_transport_)); - testing_clock_.Advance( - base::TimeDelta::FromMilliseconds(kStartMillisecond)); - video_receiver_callback_ = new TestVideoReceiverCallback(); - } - - virtual ~VideoReceiverTest() {} - - virtual void SetUp() { - payload_.assign(kPacketSize, 0); - - // Always start with a key frame. - rtp_header_.is_key_frame = true; - rtp_header_.frame_id = 0; - rtp_header_.packet_id = 0; - rtp_header_.max_packet_id = 0; - rtp_header_.is_reference = false; - rtp_header_.reference_frame_id = 0; - } - - MockPacedPacketSender mock_transport_; - VideoReceiverConfig config_; - scoped_ptr<PeerVideoReceiver> receiver_; - std::vector<uint8> payload_; - RtpCastHeader rtp_header_; - base::SimpleTestTickClock testing_clock_; - - scoped_refptr<test::FakeTaskRunner> task_runner_; - scoped_refptr<CastEnvironment> cast_environment_; - scoped_refptr<TestVideoReceiverCallback> video_receiver_callback_; -}; - -TEST_F(VideoReceiverTest, GetOnePacketEncodedframe) { - EXPECT_CALL(mock_transport_, SendRtcpPacket(_)).WillRepeatedly( - testing::Return(true)); - receiver_->IncomingParsedRtpPacket(payload_.data(), payload_.size(), - rtp_header_); - - VideoFrameEncodedCallback frame_to_decode_callback = - base::Bind(&TestVideoReceiverCallback::FrameToDecode, - video_receiver_callback_); - - receiver_->GetEncodedVideoFrame(frame_to_decode_callback); - task_runner_->RunTasks(); - EXPECT_EQ(video_receiver_callback_->number_times_called(), 1); -} - -TEST_F(VideoReceiverTest, MultiplePackets) { - EXPECT_CALL(mock_transport_, SendRtcpPacket(_)).WillRepeatedly( - testing::Return(true)); - rtp_header_.max_packet_id = 2; - receiver_->IncomingParsedRtpPacket(payload_.data(), payload_.size(), - rtp_header_); - ++rtp_header_.packet_id; - ++rtp_header_.webrtc.header.sequenceNumber; - receiver_->IncomingParsedRtpPacket(payload_.data(), payload_.size(), - rtp_header_); - ++rtp_header_.packet_id; - receiver_->IncomingParsedRtpPacket(payload_.data(), payload_.size(), - rtp_header_); - - VideoFrameEncodedCallback frame_to_decode_callback = - base::Bind(&TestVideoReceiverCallback::FrameToDecode, - video_receiver_callback_); - - receiver_->GetEncodedVideoFrame(frame_to_decode_callback); - - task_runner_->RunTasks(); - EXPECT_EQ(video_receiver_callback_->number_times_called(), 1); -} - -TEST_F(VideoReceiverTest, GetOnePacketRawframe) { - EXPECT_CALL(mock_transport_, SendRtcpPacket(_)).WillRepeatedly( - testing::Return(true)); - receiver_->IncomingParsedRtpPacket(payload_.data(), payload_.size(), - rtp_header_); - // Decode error - requires legal input. - VideoFrameDecodedCallback frame_decoded_callback = - base::Bind(&TestVideoReceiverCallback::DecodeComplete, - video_receiver_callback_); - receiver_->GetRawVideoFrame(frame_decoded_callback); - task_runner_->RunTasks(); - EXPECT_EQ(video_receiver_callback_->number_times_called(), 0); -} - -// TODO(pwestin): add encoded frames. - -} // namespace cast -} // namespace media - - diff --git a/chromium/media/cast/video_sender/codecs/vp8/vp8_encoder.cc b/chromium/media/cast/video_sender/codecs/vp8/vp8_encoder.cc index 099be63a2c8..c7374babd19 100644 --- a/chromium/media/cast/video_sender/codecs/vp8/vp8_encoder.cc +++ b/chromium/media/cast/video_sender/codecs/vp8/vp8_encoder.cc @@ -11,6 +11,7 @@ #include "base/logging.h" #include "media/base/video_frame.h" #include "media/cast/cast_defines.h" +#include "media/cast/transport/cast_transport_config.h" #include "third_party/libvpx/source/libvpx/vpx/vp8cx.h" namespace media { @@ -18,31 +19,31 @@ namespace cast { static const uint32 kMinIntra = 300; +static int ComputeMaxNumOfRepeatedBuffes(int max_unacked_frames) { + if (max_unacked_frames > kNumberOfVp8VideoBuffers) + return (max_unacked_frames - 1) / kNumberOfVp8VideoBuffers; + + return 0; +} + Vp8Encoder::Vp8Encoder(const VideoSenderConfig& video_config, - uint8 max_unacked_frames) + int max_unacked_frames) : cast_config_(video_config), use_multiple_video_buffers_( cast_config_.max_number_of_video_buffers_used == kNumberOfVp8VideoBuffers), max_number_of_repeated_buffers_in_a_row_( - (max_unacked_frames > kNumberOfVp8VideoBuffers) ? - ((max_unacked_frames - 1) / kNumberOfVp8VideoBuffers) : 0), - config_(new vpx_codec_enc_cfg_t()), - encoder_(new vpx_codec_ctx_t()), - // Creating a wrapper to the image - setting image data to NULL. Actual - // pointer will be set during encode. Setting align to 1, as it is - // meaningless (actual memory is not allocated). - raw_image_(vpx_img_wrap(NULL, IMG_FMT_I420, video_config.width, - video_config.height, 1, NULL)), + ComputeMaxNumOfRepeatedBuffes(max_unacked_frames)), key_frame_requested_(true), - timestamp_(0), + first_frame_received_(false), last_encoded_frame_id_(kStartFrameId), number_of_repeated_buffers_(0) { // TODO(pwestin): we need to figure out how to synchronize the acking with the // internal state of the encoder, ideally the encoder will tell if we can // send another frame. DCHECK(!use_multiple_video_buffers_ || - max_number_of_repeated_buffers_in_a_row_ == 0) << "Invalid config"; + max_number_of_repeated_buffers_in_a_row_ == 0) + << "Invalid config"; // VP8 have 3 buffers available for prediction, with // max_number_of_video_buffers_used set to 1 we maximize the coding efficiency @@ -52,21 +53,37 @@ Vp8Encoder::Vp8Encoder(const VideoSenderConfig& video_config, // propagation. DCHECK(cast_config_.max_number_of_video_buffers_used == 1 || cast_config_.max_number_of_video_buffers_used == - kNumberOfVp8VideoBuffers) << "Invalid argument"; + kNumberOfVp8VideoBuffers) + << "Invalid argument"; - for (int i = 0; i < kNumberOfVp8VideoBuffers; ++i) { - acked_frame_buffers_[i] = true; - used_buffers_frame_id_[i] = kStartFrameId; - } - InitEncode(video_config.number_of_cores); + thread_checker_.DetachFromThread(); } Vp8Encoder::~Vp8Encoder() { - vpx_codec_destroy(encoder_); + vpx_codec_destroy(encoder_.get()); vpx_img_free(raw_image_); } -void Vp8Encoder::InitEncode(int number_of_cores) { +void Vp8Encoder::Initialize() { + DCHECK(thread_checker_.CalledOnValidThread()); + config_.reset(new vpx_codec_enc_cfg_t()); + encoder_.reset(new vpx_codec_ctx_t()); + + // Creating a wrapper to the image - setting image data to NULL. Actual + // pointer will be set during encode. Setting align to 1, as it is + // meaningless (actual memory is not allocated). + raw_image_ = vpx_img_wrap( + NULL, IMG_FMT_I420, cast_config_.width, cast_config_.height, 1, NULL); + + for (int i = 0; i < kNumberOfVp8VideoBuffers; ++i) { + acked_frame_buffers_[i] = true; + used_buffers_frame_id_[i] = kStartFrameId; + } + InitEncode(cast_config_.number_of_encode_threads); +} + +void Vp8Encoder::InitEncode(int number_of_encode_threads) { + DCHECK(thread_checker_.CalledOnValidThread()); // Populate encoder configuration with default values. if (vpx_codec_enc_config_default(vpx_codec_vp8_cx(), config_.get(), 0)) { DCHECK(false) << "Invalid return value"; @@ -85,17 +102,11 @@ void Vp8Encoder::InitEncode(int number_of_cores) { // codec requirements. config_->g_error_resilient = 1; } - - if (cast_config_.width * cast_config_.height > 640 * 480 - && number_of_cores >= 2) { - config_->g_threads = 2; // 2 threads for qHD/HD. - } else { - config_->g_threads = 1; // 1 thread for VGA or less. - } + config_->g_threads = number_of_encode_threads; // Rate control settings. - // TODO(pwestin): revisit these constants. Currently identical to webrtc. - config_->rc_dropframe_thresh = 30; + // Never allow the encoder to drop frame internally. + config_->rc_dropframe_thresh = 0; config_->rc_end_usage = VPX_CBR; config_->g_pass = VPX_RC_ONE_PASS; config_->rc_resize_allowed = 0; @@ -110,19 +121,22 @@ void Vp8Encoder::InitEncode(int number_of_cores) { // set the maximum target size of any key-frame. uint32 rc_max_intra_target = MaxIntraTarget(config_->rc_buf_optimal_sz); vpx_codec_flags_t flags = 0; - // TODO(mikhal): Tune settings. - if (vpx_codec_enc_init(encoder_, vpx_codec_vp8_cx(), config_.get(), flags)) { - DCHECK(false) << "Invalid return value"; + if (vpx_codec_enc_init( + encoder_.get(), vpx_codec_vp8_cx(), config_.get(), flags)) { + DCHECK(false) << "vpx_codec_enc_init() failed."; + encoder_.reset(); + return; } - vpx_codec_control(encoder_, VP8E_SET_STATIC_THRESHOLD, 1); - vpx_codec_control(encoder_, VP8E_SET_NOISE_SENSITIVITY, 0); - vpx_codec_control(encoder_, VP8E_SET_CPUUSED, -6); - vpx_codec_control(encoder_, VP8E_SET_MAX_INTRA_BITRATE_PCT, - rc_max_intra_target); + vpx_codec_control(encoder_.get(), VP8E_SET_STATIC_THRESHOLD, 1); + vpx_codec_control(encoder_.get(), VP8E_SET_NOISE_SENSITIVITY, 0); + vpx_codec_control(encoder_.get(), VP8E_SET_CPUUSED, -6); + vpx_codec_control( + encoder_.get(), VP8E_SET_MAX_INTRA_BITRATE_PCT, rc_max_intra_target); } bool Vp8Encoder::Encode(const scoped_refptr<media::VideoFrame>& video_frame, - EncodedVideoFrame* encoded_image) { + transport::EncodedFrame* encoded_image) { + DCHECK(thread_checker_.CalledOnValidThread()); // Image in vpx_image_t format. // Input image is const. VP8's raw image is not defined as const. raw_image_->planes[PLANE_Y] = @@ -142,8 +156,7 @@ bool Vp8Encoder::Encode(const scoped_refptr<media::VideoFrame>& video_frame, if (key_frame_requested_) { flags = VPX_EFLAG_FORCE_KF; // Self reference. - latest_frame_id_to_reference = - static_cast<uint8>(last_encoded_frame_id_ + 1); + latest_frame_id_to_reference = last_encoded_frame_id_ + 1; // We can pick any buffer as buffer_to_update since we update // them all. buffer_to_update = kLastBuffer; @@ -157,45 +170,63 @@ bool Vp8Encoder::Encode(const scoped_refptr<media::VideoFrame>& video_frame, // Note: The duration does not reflect the real time between frames. This is // done to keep the encoder happy. + // + // TODO(miu): This is a semi-hack. We should consider using + // |video_frame->timestamp()| instead. uint32 duration = kVideoFrequency / cast_config_.max_frame_rate; - if (vpx_codec_encode(encoder_, raw_image_, timestamp_, duration, flags, - VPX_DL_REALTIME)) { + + // Note: Timestamp here is used for bitrate calculation. The absolute value + // is not important. + if (!first_frame_received_) { + first_frame_received_ = true; + first_frame_timestamp_ = video_frame->timestamp(); + } + + vpx_codec_pts_t timestamp = + (video_frame->timestamp() - first_frame_timestamp_).InMicroseconds() * + kVideoFrequency / base::Time::kMicrosecondsPerSecond; + + if (vpx_codec_encode(encoder_.get(), + raw_image_, + timestamp, + duration, + flags, + VPX_DL_REALTIME) != VPX_CODEC_OK) { + LOG(ERROR) << "Failed to encode for once."; return false; } - timestamp_ += duration; // Get encoded frame. - const vpx_codec_cx_pkt_t *pkt = NULL; + const vpx_codec_cx_pkt_t* pkt = NULL; vpx_codec_iter_t iter = NULL; - size_t total_size = 0; - while ((pkt = vpx_codec_get_cx_data(encoder_, &iter)) != NULL) { - if (pkt->kind == VPX_CODEC_CX_FRAME_PKT) { - total_size += pkt->data.frame.sz; - encoded_image->data.reserve(total_size); - encoded_image->data.insert( - encoded_image->data.end(), - static_cast<const uint8*>(pkt->data.frame.buf), - static_cast<const uint8*>(pkt->data.frame.buf) + - pkt->data.frame.sz); - if (pkt->data.frame.flags & VPX_FRAME_IS_KEY) { - encoded_image->key_frame = true; - } else { - encoded_image->key_frame = false; - } - } + bool is_key_frame = false; + while ((pkt = vpx_codec_get_cx_data(encoder_.get(), &iter)) != NULL) { + if (pkt->kind != VPX_CODEC_CX_FRAME_PKT) + continue; + encoded_image->data.assign( + static_cast<const uint8*>(pkt->data.frame.buf), + static_cast<const uint8*>(pkt->data.frame.buf) + pkt->data.frame.sz); + is_key_frame = !!(pkt->data.frame.flags & VPX_FRAME_IS_KEY); + break; // Done, since all data is provided in one CX_FRAME_PKT packet. } // Don't update frame_id for zero size frames. - if (total_size == 0) return true; + if (encoded_image->data.empty()) + return true; // Populate the encoded frame. - encoded_image->codec = kVp8; - encoded_image->last_referenced_frame_id = latest_frame_id_to_reference; encoded_image->frame_id = ++last_encoded_frame_id_; + if (is_key_frame) { + encoded_image->dependency = transport::EncodedFrame::KEY; + encoded_image->referenced_frame_id = encoded_image->frame_id; + } else { + encoded_image->dependency = transport::EncodedFrame::DEPENDENT; + encoded_image->referenced_frame_id = latest_frame_id_to_reference; + } - VLOG(1) << "VP8 encoded frame:" << static_cast<int>(encoded_image->frame_id) - << " sized:" << total_size; + DVLOG(1) << "VP8 encoded frame_id " << encoded_image->frame_id + << ", sized:" << encoded_image->data.size(); - if (encoded_image->key_frame) { + if (is_key_frame) { key_frame_requested_ = false; for (int i = 0; i < kNumberOfVp8VideoBuffers; ++i) { @@ -215,12 +246,14 @@ bool Vp8Encoder::Encode(const scoped_refptr<media::VideoFrame>& video_frame, } void Vp8Encoder::GetCodecReferenceFlags(vpx_codec_flags_t* flags) { - if (!use_multiple_video_buffers_) return; + if (!use_multiple_video_buffers_) + return; // We need to reference something. DCHECK(acked_frame_buffers_[kAltRefBuffer] || acked_frame_buffers_[kGoldenBuffer] || - acked_frame_buffers_[kLastBuffer]) << "Invalid state"; + acked_frame_buffers_[kLastBuffer]) + << "Invalid state"; if (!acked_frame_buffers_[kAltRefBuffer]) { *flags |= VP8_EFLAG_NO_REF_ARF; @@ -234,7 +267,8 @@ void Vp8Encoder::GetCodecReferenceFlags(vpx_codec_flags_t* flags) { } uint32 Vp8Encoder::GetLatestFrameIdToReference() { - if (!use_multiple_video_buffers_) return last_encoded_frame_id_; + if (!use_multiple_video_buffers_) + return last_encoded_frame_id_; int latest_frame_id_to_reference = -1; if (acked_frame_buffers_[kAltRefBuffer]) { @@ -265,9 +299,12 @@ uint32 Vp8Encoder::GetLatestFrameIdToReference() { } Vp8Encoder::Vp8Buffers Vp8Encoder::GetNextBufferToUpdate() { + if (!use_multiple_video_buffers_) + return kNoBuffer; + // Update at most one buffer, except for key-frames. - Vp8Buffers buffer_to_update; + Vp8Buffers buffer_to_update = kNoBuffer; if (number_of_repeated_buffers_ < max_number_of_repeated_buffers_in_a_row_) { // TODO(pwestin): experiment with this. The issue with only this change is // that we can end up with only 4 frames in flight when we expect 6. @@ -299,7 +336,8 @@ Vp8Encoder::Vp8Buffers Vp8Encoder::GetNextBufferToUpdate() { void Vp8Encoder::GetCodecUpdateFlags(Vp8Buffers buffer_to_update, vpx_codec_flags_t* flags) { - if (!use_multiple_video_buffers_) return; + if (!use_multiple_video_buffers_) + return; // Update at most one buffer, except for key-frames. switch (buffer_to_update) { @@ -325,19 +363,23 @@ void Vp8Encoder::GetCodecUpdateFlags(Vp8Buffers buffer_to_update, } void Vp8Encoder::UpdateRates(uint32 new_bitrate) { + DCHECK(thread_checker_.CalledOnValidThread()); uint32 new_bitrate_kbit = new_bitrate / 1000; - if (config_->rc_target_bitrate == new_bitrate_kbit) return; + if (config_->rc_target_bitrate == new_bitrate_kbit) + return; config_->rc_target_bitrate = new_bitrate_kbit; // Update encoder context. - if (vpx_codec_enc_config_set(encoder_, config_.get())) { + if (vpx_codec_enc_config_set(encoder_.get(), config_.get())) { DCHECK(false) << "Invalid return value"; } } void Vp8Encoder::LatestFrameIdToReference(uint32 frame_id) { - if (!use_multiple_video_buffers_) return; + DCHECK(thread_checker_.CalledOnValidThread()); + if (!use_multiple_video_buffers_) + return; VLOG(1) << "VP8 ok to reference frame:" << static_cast<int>(frame_id); for (int i = 0; i < kNumberOfVp8VideoBuffers; ++i) { @@ -348,6 +390,7 @@ void Vp8Encoder::LatestFrameIdToReference(uint32 frame_id) { } void Vp8Encoder::GenerateKeyFrame() { + DCHECK(thread_checker_.CalledOnValidThread()); key_frame_requested_ = true; } @@ -362,7 +405,7 @@ uint32 Vp8Encoder::MaxIntraTarget(uint32 optimal_buffer_size_ms) const { float scale_parameter = 0.5; uint32 target_pct = optimal_buffer_size_ms * scale_parameter * - cast_config_.max_frame_rate / 10; + cast_config_.max_frame_rate / 10; // Don't go below 3 times the per frame bandwidth. return std::max(target_pct, kMinIntra); diff --git a/chromium/media/cast/video_sender/codecs/vp8/vp8_encoder.gypi b/chromium/media/cast/video_sender/codecs/vp8/vp8_encoder.gypi deleted file mode 100644 index fa9c2944a15..00000000000 --- a/chromium/media/cast/video_sender/codecs/vp8/vp8_encoder.gypi +++ /dev/null @@ -1,20 +0,0 @@ -{ - 'targets': [ - { - 'target_name': 'cast_vp8_encoder', - 'type': 'static_library', - 'include_dirs': [ - '<(DEPTH)/', - '<(DEPTH)/third_party/', - ], - 'sources': [ - 'vp8_encoder.cc', - 'vp8_encoder.h', - ], # source - 'dependencies': [ - '<(DEPTH)/ui/gfx/gfx.gyp:gfx', - '<(DEPTH)/third_party/libvpx/libvpx.gyp:libvpx', - ], - }, - ], -} diff --git a/chromium/media/cast/video_sender/codecs/vp8/vp8_encoder.h b/chromium/media/cast/video_sender/codecs/vp8/vp8_encoder.h index d09cc27dabc..2421cf15114 100644 --- a/chromium/media/cast/video_sender/codecs/vp8/vp8_encoder.h +++ b/chromium/media/cast/video_sender/codecs/vp8/vp8_encoder.h @@ -7,7 +7,10 @@ #include "base/basictypes.h" #include "base/memory/scoped_ptr.h" +#include "base/threading/thread_checker.h" +#include "base/time/time.h" #include "media/cast/cast_config.h" +#include "media/cast/video_sender/software_video_encoder.h" #include "third_party/libvpx/source/libvpx/vpx/vpx_encoder.h" namespace media { @@ -22,24 +25,27 @@ namespace cast { const int kNumberOfVp8VideoBuffers = 3; -class Vp8Encoder { +class Vp8Encoder : public SoftwareVideoEncoder { public: - Vp8Encoder(const VideoSenderConfig& video_config, - uint8 max_unacked_frames); + Vp8Encoder(const VideoSenderConfig& video_config, int max_unacked_frames); - ~Vp8Encoder(); + virtual ~Vp8Encoder(); + + // Initialize the encoder before Encode() can be called. This method + // must be called on the thread that Encode() is called. + virtual void Initialize() OVERRIDE; // Encode a raw image (as a part of a video stream). - bool Encode(const scoped_refptr<media::VideoFrame>& video_frame, - EncodedVideoFrame* encoded_image); + virtual bool Encode(const scoped_refptr<media::VideoFrame>& video_frame, + transport::EncodedFrame* encoded_image) OVERRIDE; // Update the encoder with a new target bit rate. - void UpdateRates(uint32 new_bitrate); + virtual void UpdateRates(uint32 new_bitrate) OVERRIDE; // Set the next frame to be a key frame. - void GenerateKeyFrame(); + virtual void GenerateKeyFrame() OVERRIDE; - void LatestFrameIdToReference(uint32 frame_id); + virtual void LatestFrameIdToReference(uint32 frame_id) OVERRIDE; private: enum Vp8Buffers { @@ -73,16 +79,22 @@ class Vp8Encoder { // VP8 internal objects. scoped_ptr<vpx_codec_enc_cfg_t> config_; - vpx_enc_ctx_t* encoder_; + scoped_ptr<vpx_enc_ctx_t> encoder_; vpx_image_t* raw_image_; bool key_frame_requested_; - int64 timestamp_; + bool first_frame_received_; + base::TimeDelta first_frame_timestamp_; uint32 last_encoded_frame_id_; uint32 used_buffers_frame_id_[kNumberOfVp8VideoBuffers]; bool acked_frame_buffers_[kNumberOfVp8VideoBuffers]; Vp8Buffers last_used_vp8_buffer_; int number_of_repeated_buffers_; + + // This is bound to the thread where Initialize() is called. + base::ThreadChecker thread_checker_; + + DISALLOW_COPY_AND_ASSIGN(Vp8Encoder); }; } // namespace cast diff --git a/chromium/media/cast/video_sender/external_video_encoder.cc b/chromium/media/cast/video_sender/external_video_encoder.cc new file mode 100644 index 00000000000..ca30bcd47af --- /dev/null +++ b/chromium/media/cast/video_sender/external_video_encoder.cc @@ -0,0 +1,436 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/cast/video_sender/external_video_encoder.h" + +#include "base/bind.h" +#include "base/logging.h" +#include "base/memory/scoped_vector.h" +#include "base/memory/shared_memory.h" +#include "base/message_loop/message_loop.h" +#include "media/base/video_frame.h" +#include "media/base/video_util.h" +#include "media/cast/cast_defines.h" +#include "media/cast/logging/logging_defines.h" +#include "media/cast/transport/cast_transport_config.h" +#include "media/video/video_encode_accelerator.h" + +namespace media { +namespace cast { +class LocalVideoEncodeAcceleratorClient; +} // namespace cast +} // namespace media + +namespace { +static const size_t kOutputBufferCount = 3; + +void LogFrameEncodedEvent( + const scoped_refptr<media::cast::CastEnvironment>& cast_environment, + base::TimeTicks event_time, + media::cast::RtpTimestamp rtp_timestamp, + uint32 frame_id) { + cast_environment->Logging()->InsertFrameEvent( + event_time, media::cast::FRAME_ENCODED, media::cast::VIDEO_EVENT, + rtp_timestamp, frame_id); +} + +// Proxy this call to ExternalVideoEncoder on the cast main thread. +void ProxyCreateVideoEncodeAccelerator( + const scoped_refptr<media::cast::CastEnvironment>& cast_environment, + const base::WeakPtr<media::cast::ExternalVideoEncoder>& weak_ptr, + const media::cast::CreateVideoEncodeMemoryCallback& + create_video_encode_mem_cb, + scoped_refptr<base::SingleThreadTaskRunner> encoder_task_runner, + scoped_ptr<media::VideoEncodeAccelerator> vea) { + cast_environment->PostTask( + media::cast::CastEnvironment::MAIN, + FROM_HERE, + base::Bind( + &media::cast::ExternalVideoEncoder::OnCreateVideoEncodeAccelerator, + weak_ptr, + create_video_encode_mem_cb, + encoder_task_runner, + base::Passed(&vea))); +} +} // namespace + +namespace media { +namespace cast { + +// Container for the associated data of a video frame being processed. +struct EncodedFrameReturnData { + EncodedFrameReturnData(base::TimeTicks c_time, + VideoEncoder::FrameEncodedCallback callback) { + capture_time = c_time; + frame_encoded_callback = callback; + } + base::TimeTicks capture_time; + VideoEncoder::FrameEncodedCallback frame_encoded_callback; +}; + +// The ExternalVideoEncoder class can be deleted directly by cast, while +// LocalVideoEncodeAcceleratorClient stays around long enough to properly shut +// down the VideoEncodeAccelerator. +class LocalVideoEncodeAcceleratorClient + : public VideoEncodeAccelerator::Client, + public base::RefCountedThreadSafe<LocalVideoEncodeAcceleratorClient> { + public: + LocalVideoEncodeAcceleratorClient( + scoped_refptr<CastEnvironment> cast_environment, + scoped_refptr<base::SingleThreadTaskRunner> encoder_task_runner, + scoped_ptr<media::VideoEncodeAccelerator> vea, + const CreateVideoEncodeMemoryCallback& create_video_encode_mem_cb, + const base::WeakPtr<ExternalVideoEncoder>& weak_owner) + : cast_environment_(cast_environment), + encoder_task_runner_(encoder_task_runner), + video_encode_accelerator_(vea.Pass()), + create_video_encode_memory_cb_(create_video_encode_mem_cb), + weak_owner_(weak_owner), + last_encoded_frame_id_(kStartFrameId), + key_frame_encountered_(false) { + DCHECK(encoder_task_runner_); + } + + // Initialize the real HW encoder. + void Initialize(const VideoSenderConfig& video_config) { + DCHECK(encoder_task_runner_); + DCHECK(encoder_task_runner_->RunsTasksOnCurrentThread()); + + VideoCodecProfile output_profile = media::VIDEO_CODEC_PROFILE_UNKNOWN; + switch (video_config.codec) { + case transport::kVp8: + output_profile = media::VP8PROFILE_MAIN; + break; + case transport::kH264: + output_profile = media::H264PROFILE_MAIN; + break; + case transport::kFakeSoftwareVideo: + NOTREACHED() << "Fake software video encoder cannot be external"; + break; + case transport::kUnknownVideoCodec: + NOTREACHED() << "Video codec not specified"; + break; + } + codec_ = video_config.codec; + max_frame_rate_ = video_config.max_frame_rate; + + if (!video_encode_accelerator_->Initialize( + media::VideoFrame::I420, + gfx::Size(video_config.width, video_config.height), + output_profile, + video_config.start_bitrate, + this)) { + NotifyError(VideoEncodeAccelerator::kInvalidArgumentError); + return; + } + + // Wait until shared memory is allocated to indicate that encoder is + // initialized. + } + + // Free the HW. + void Destroy() { + DCHECK(encoder_task_runner_); + DCHECK(encoder_task_runner_->RunsTasksOnCurrentThread()); + + video_encode_accelerator_.reset(); + } + + void SetBitRate(uint32 bit_rate) { + DCHECK(encoder_task_runner_); + DCHECK(encoder_task_runner_->RunsTasksOnCurrentThread()); + + video_encode_accelerator_->RequestEncodingParametersChange(bit_rate, + max_frame_rate_); + } + + void EncodeVideoFrame( + const scoped_refptr<media::VideoFrame>& video_frame, + const base::TimeTicks& capture_time, + bool key_frame_requested, + const VideoEncoder::FrameEncodedCallback& frame_encoded_callback) { + DCHECK(encoder_task_runner_); + DCHECK(encoder_task_runner_->RunsTasksOnCurrentThread()); + + encoded_frame_data_storage_.push_back( + EncodedFrameReturnData(capture_time, frame_encoded_callback)); + + // BitstreamBufferReady will be called once the encoder is done. + video_encode_accelerator_->Encode(video_frame, key_frame_requested); + } + + protected: + virtual void NotifyError(VideoEncodeAccelerator::Error error) OVERRIDE { + DCHECK(encoder_task_runner_); + DCHECK(encoder_task_runner_->RunsTasksOnCurrentThread()); + VLOG(1) << "ExternalVideoEncoder NotifyError: " << error; + + video_encode_accelerator_.reset(); + cast_environment_->PostTask( + CastEnvironment::MAIN, + FROM_HERE, + base::Bind(&ExternalVideoEncoder::EncoderError, weak_owner_)); + } + + // Called to allocate the input and output buffers. + virtual void RequireBitstreamBuffers(unsigned int input_count, + const gfx::Size& input_coded_size, + size_t output_buffer_size) OVERRIDE { + DCHECK(encoder_task_runner_); + DCHECK(encoder_task_runner_->RunsTasksOnCurrentThread()); + DCHECK(video_encode_accelerator_); + + for (size_t j = 0; j < kOutputBufferCount; ++j) { + create_video_encode_memory_cb_.Run( + output_buffer_size, + base::Bind(&LocalVideoEncodeAcceleratorClient::OnCreateSharedMemory, + this)); + } + } + + // Encoder has encoded a frame and it's available in one of out output + // buffers. + virtual void BitstreamBufferReady(int32 bitstream_buffer_id, + size_t payload_size, + bool key_frame) OVERRIDE { + DCHECK(encoder_task_runner_); + DCHECK(encoder_task_runner_->RunsTasksOnCurrentThread()); + if (bitstream_buffer_id < 0 || + bitstream_buffer_id >= static_cast<int32>(output_buffers_.size())) { + NOTREACHED(); + VLOG(1) << "BitstreamBufferReady(): invalid bitstream_buffer_id=" + << bitstream_buffer_id; + NotifyError(media::VideoEncodeAccelerator::kPlatformFailureError); + return; + } + base::SharedMemory* output_buffer = output_buffers_[bitstream_buffer_id]; + if (payload_size > output_buffer->mapped_size()) { + NOTREACHED(); + VLOG(1) << "BitstreamBufferReady(): invalid payload_size = " + << payload_size; + NotifyError(media::VideoEncodeAccelerator::kPlatformFailureError); + return; + } + if (key_frame) + key_frame_encountered_ = true; + if (!key_frame_encountered_) { + // Do not send video until we have encountered the first key frame. + // Save the bitstream buffer in |stream_header_| to be sent later along + // with the first key frame. + stream_header_.append(static_cast<const char*>(output_buffer->memory()), + payload_size); + } else if (!encoded_frame_data_storage_.empty()) { + scoped_ptr<transport::EncodedFrame> encoded_frame( + new transport::EncodedFrame()); + encoded_frame->dependency = key_frame ? transport::EncodedFrame::KEY : + transport::EncodedFrame::DEPENDENT; + encoded_frame->frame_id = ++last_encoded_frame_id_; + if (key_frame) + encoded_frame->referenced_frame_id = encoded_frame->frame_id; + else + encoded_frame->referenced_frame_id = encoded_frame->frame_id - 1; + encoded_frame->reference_time = + encoded_frame_data_storage_.front().capture_time; + encoded_frame->rtp_timestamp = + GetVideoRtpTimestamp(encoded_frame->reference_time); + if (!stream_header_.empty()) { + encoded_frame->data = stream_header_; + stream_header_.clear(); + } + encoded_frame->data.append( + static_cast<const char*>(output_buffer->memory()), payload_size); + + cast_environment_->PostTask( + CastEnvironment::MAIN, + FROM_HERE, + base::Bind(&LogFrameEncodedEvent, + cast_environment_, + cast_environment_->Clock()->NowTicks(), + encoded_frame->rtp_timestamp, + encoded_frame->frame_id)); + + cast_environment_->PostTask( + CastEnvironment::MAIN, + FROM_HERE, + base::Bind(encoded_frame_data_storage_.front().frame_encoded_callback, + base::Passed(&encoded_frame))); + + encoded_frame_data_storage_.pop_front(); + } else { + VLOG(1) << "BitstreamBufferReady(): no encoded frame data available"; + } + + // We need to re-add the output buffer to the encoder after we are done + // with it. + video_encode_accelerator_->UseOutputBitstreamBuffer(media::BitstreamBuffer( + bitstream_buffer_id, + output_buffers_[bitstream_buffer_id]->handle(), + output_buffers_[bitstream_buffer_id]->mapped_size())); + } + + private: + // Note: This method can be called on any thread. + void OnCreateSharedMemory(scoped_ptr<base::SharedMemory> memory) { + encoder_task_runner_->PostTask( + FROM_HERE, + base::Bind(&LocalVideoEncodeAcceleratorClient::ReceivedSharedMemory, + this, + base::Passed(&memory))); + } + + void ReceivedSharedMemory(scoped_ptr<base::SharedMemory> memory) { + DCHECK(encoder_task_runner_); + DCHECK(encoder_task_runner_->RunsTasksOnCurrentThread()); + + output_buffers_.push_back(memory.release()); + + // Wait until all requested buffers are received. + if (output_buffers_.size() < kOutputBufferCount) + return; + + // Immediately provide all output buffers to the VEA. + for (size_t i = 0; i < output_buffers_.size(); ++i) { + video_encode_accelerator_->UseOutputBitstreamBuffer( + media::BitstreamBuffer(static_cast<int32>(i), + output_buffers_[i]->handle(), + output_buffers_[i]->mapped_size())); + } + + cast_environment_->PostTask( + CastEnvironment::MAIN, + FROM_HERE, + base::Bind(&ExternalVideoEncoder::EncoderInitialized, weak_owner_)); + } + + friend class base::RefCountedThreadSafe<LocalVideoEncodeAcceleratorClient>; + + virtual ~LocalVideoEncodeAcceleratorClient() {} + + const scoped_refptr<CastEnvironment> cast_environment_; + scoped_refptr<base::SingleThreadTaskRunner> encoder_task_runner_; + scoped_ptr<media::VideoEncodeAccelerator> video_encode_accelerator_; + const CreateVideoEncodeMemoryCallback create_video_encode_memory_cb_; + const base::WeakPtr<ExternalVideoEncoder> weak_owner_; + int max_frame_rate_; + transport::VideoCodec codec_; + uint32 last_encoded_frame_id_; + bool key_frame_encountered_; + std::string stream_header_; + + // Shared memory buffers for output with the VideoAccelerator. + ScopedVector<base::SharedMemory> output_buffers_; + + // FIFO list. + std::list<EncodedFrameReturnData> encoded_frame_data_storage_; + + DISALLOW_COPY_AND_ASSIGN(LocalVideoEncodeAcceleratorClient); +}; + +ExternalVideoEncoder::ExternalVideoEncoder( + scoped_refptr<CastEnvironment> cast_environment, + const VideoSenderConfig& video_config, + const CreateVideoEncodeAcceleratorCallback& create_vea_cb, + const CreateVideoEncodeMemoryCallback& create_video_encode_mem_cb) + : video_config_(video_config), + cast_environment_(cast_environment), + encoder_active_(false), + key_frame_requested_(false), + weak_factory_(this) { + DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); + + create_vea_cb.Run(base::Bind(&ProxyCreateVideoEncodeAccelerator, + cast_environment, + weak_factory_.GetWeakPtr(), + create_video_encode_mem_cb)); +} + +ExternalVideoEncoder::~ExternalVideoEncoder() { + encoder_task_runner_->PostTask( + FROM_HERE, + base::Bind(&LocalVideoEncodeAcceleratorClient::Destroy, + video_accelerator_client_)); +} + +void ExternalVideoEncoder::EncoderInitialized() { + DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); + encoder_active_ = true; +} + +void ExternalVideoEncoder::EncoderError() { + DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); + encoder_active_ = false; +} + +void ExternalVideoEncoder::OnCreateVideoEncodeAccelerator( + const CreateVideoEncodeMemoryCallback& create_video_encode_mem_cb, + scoped_refptr<base::SingleThreadTaskRunner> encoder_task_runner, + scoped_ptr<media::VideoEncodeAccelerator> vea) { + DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); + encoder_task_runner_ = encoder_task_runner; + + video_accelerator_client_ = + new LocalVideoEncodeAcceleratorClient(cast_environment_, + encoder_task_runner, + vea.Pass(), + create_video_encode_mem_cb, + weak_factory_.GetWeakPtr()); + encoder_task_runner_->PostTask( + FROM_HERE, + base::Bind(&LocalVideoEncodeAcceleratorClient::Initialize, + video_accelerator_client_, + video_config_)); +} + +bool ExternalVideoEncoder::EncodeVideoFrame( + const scoped_refptr<media::VideoFrame>& video_frame, + const base::TimeTicks& capture_time, + const FrameEncodedCallback& frame_encoded_callback) { + DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); + + if (!encoder_active_) + return false; + + encoder_task_runner_->PostTask( + FROM_HERE, + base::Bind(&LocalVideoEncodeAcceleratorClient::EncodeVideoFrame, + video_accelerator_client_, + video_frame, + capture_time, + key_frame_requested_, + frame_encoded_callback)); + + key_frame_requested_ = false; + return true; +} + +// Inform the encoder about the new target bit rate. +void ExternalVideoEncoder::SetBitRate(int new_bit_rate) { + if (!encoder_active_) { + // If we receive SetBitRate() before VEA creation callback is invoked, + // cache the new bit rate in the encoder config and use the new settings + // to initialize VEA. + video_config_.start_bitrate = new_bit_rate; + return; + } + + encoder_task_runner_->PostTask( + FROM_HERE, + base::Bind(&LocalVideoEncodeAcceleratorClient::SetBitRate, + video_accelerator_client_, + new_bit_rate)); +} + +// Inform the encoder to encode the next frame as a key frame. +void ExternalVideoEncoder::GenerateKeyFrame() { + DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); + key_frame_requested_ = true; +} + +// Inform the encoder to only reference frames older or equal to frame_id; +void ExternalVideoEncoder::LatestFrameIdToReference(uint32 /*frame_id*/) { + // Do nothing not supported. +} + +} // namespace cast +} // namespace media diff --git a/chromium/media/cast/video_sender/external_video_encoder.h b/chromium/media/cast/video_sender/external_video_encoder.h new file mode 100644 index 00000000000..29fe0c5fcdb --- /dev/null +++ b/chromium/media/cast/video_sender/external_video_encoder.h @@ -0,0 +1,86 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_CAST_VIDEO_SENDER_EXTERNAL_VIDEO_ENCODER_H_ +#define MEDIA_CAST_VIDEO_SENDER_EXTERNAL_VIDEO_ENCODER_H_ + +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "media/cast/cast_config.h" +#include "media/cast/cast_environment.h" +#include "media/cast/video_sender/video_encoder.h" +#include "media/video/video_encode_accelerator.h" + +namespace media { +class VideoFrame; +} + +namespace media { +namespace cast { + +class LocalVideoEncodeAcceleratorClient; + +// This object is called external from the main cast thread and internally from +// the video encoder thread. +class ExternalVideoEncoder : public VideoEncoder { + public: + ExternalVideoEncoder( + scoped_refptr<CastEnvironment> cast_environment, + const VideoSenderConfig& video_config, + const CreateVideoEncodeAcceleratorCallback& create_vea_cb, + const CreateVideoEncodeMemoryCallback& create_video_encode_mem_cb); + + virtual ~ExternalVideoEncoder(); + + // Called from the main cast thread. This function post the encode task to the + // video encoder thread; + // The video_frame must be valid until the closure callback is called. + // The closure callback is called from the video encoder thread as soon as + // the encoder is done with the frame; it does not mean that the encoded frame + // has been sent out. + // Once the encoded frame is ready the frame_encoded_callback is called. + virtual bool EncodeVideoFrame( + const scoped_refptr<media::VideoFrame>& video_frame, + const base::TimeTicks& capture_time, + const FrameEncodedCallback& frame_encoded_callback) OVERRIDE; + + // The following functions are called from the main cast thread. + virtual void SetBitRate(int new_bit_rate) OVERRIDE; + virtual void GenerateKeyFrame() OVERRIDE; + virtual void LatestFrameIdToReference(uint32 frame_id) OVERRIDE; + + // Called when a VEA is created. + void OnCreateVideoEncodeAccelerator( + const CreateVideoEncodeMemoryCallback& create_video_encode_mem_cb, + scoped_refptr<base::SingleThreadTaskRunner> encoder_task_runner, + scoped_ptr<media::VideoEncodeAccelerator> vea); + + protected: + void EncoderInitialized(); + void EncoderError(); + + private: + friend class LocalVideoEncodeAcceleratorClient; + + VideoSenderConfig video_config_; + scoped_refptr<CastEnvironment> cast_environment_; + + bool encoder_active_; + bool key_frame_requested_; + + scoped_refptr<LocalVideoEncodeAcceleratorClient> video_accelerator_client_; + scoped_refptr<base::SingleThreadTaskRunner> encoder_task_runner_; + + // Weak pointer factory for posting back LocalVideoEncodeAcceleratorClient + // notifications to ExternalVideoEncoder. + // NOTE: Weak pointers must be invalidated before all other member variables. + base::WeakPtrFactory<ExternalVideoEncoder> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(ExternalVideoEncoder); +}; + +} // namespace cast +} // namespace media + +#endif // MEDIA_CAST_VIDEO_SENDER_EXTERNAL_VIDEO_ENCODER_H_ diff --git a/chromium/media/cast/video_sender/external_video_encoder_unittest.cc b/chromium/media/cast/video_sender/external_video_encoder_unittest.cc new file mode 100644 index 00000000000..853258ce30a --- /dev/null +++ b/chromium/media/cast/video_sender/external_video_encoder_unittest.cc @@ -0,0 +1,191 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <vector> + +#include "base/bind.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "media/base/video_frame.h" +#include "media/cast/cast_defines.h" +#include "media/cast/cast_environment.h" +#include "media/cast/test/fake_single_thread_task_runner.h" +#include "media/cast/test/fake_video_encode_accelerator.h" +#include "media/cast/test/utility/video_utility.h" +#include "media/cast/video_sender/external_video_encoder.h" +#include "testing/gmock/include/gmock/gmock.h" + +namespace media { +namespace cast { + +using testing::_; + +namespace { + +void CreateVideoEncodeAccelerator( + const scoped_refptr<base::SingleThreadTaskRunner>& task_runner, + scoped_ptr<VideoEncodeAccelerator> fake_vea, + const ReceiveVideoEncodeAcceleratorCallback& callback) { + callback.Run(task_runner, fake_vea.Pass()); +} + +void CreateSharedMemory( + size_t size, const ReceiveVideoEncodeMemoryCallback& callback) { + scoped_ptr<base::SharedMemory> shm(new base::SharedMemory()); + if (!shm->CreateAndMapAnonymous(size)) { + NOTREACHED(); + return; + } + callback.Run(shm.Pass()); +} + +class TestVideoEncoderCallback + : public base::RefCountedThreadSafe<TestVideoEncoderCallback> { + public: + TestVideoEncoderCallback() {} + + void SetExpectedResult(uint32 expected_frame_id, + uint32 expected_last_referenced_frame_id, + const base::TimeTicks& expected_capture_time) { + expected_frame_id_ = expected_frame_id; + expected_last_referenced_frame_id_ = expected_last_referenced_frame_id; + expected_capture_time_ = expected_capture_time; + } + + void DeliverEncodedVideoFrame( + scoped_ptr<transport::EncodedFrame> encoded_frame) { + if (expected_frame_id_ == expected_last_referenced_frame_id_) { + EXPECT_EQ(transport::EncodedFrame::KEY, encoded_frame->dependency); + } else { + EXPECT_EQ(transport::EncodedFrame::DEPENDENT, + encoded_frame->dependency); + } + EXPECT_EQ(expected_frame_id_, encoded_frame->frame_id); + EXPECT_EQ(expected_last_referenced_frame_id_, + encoded_frame->referenced_frame_id); + EXPECT_EQ(expected_capture_time_, encoded_frame->reference_time); + } + + protected: + virtual ~TestVideoEncoderCallback() {} + + private: + friend class base::RefCountedThreadSafe<TestVideoEncoderCallback>; + + bool expected_key_frame_; + uint32 expected_frame_id_; + uint32 expected_last_referenced_frame_id_; + base::TimeTicks expected_capture_time_; + + DISALLOW_COPY_AND_ASSIGN(TestVideoEncoderCallback); +}; +} // namespace + +class ExternalVideoEncoderTest : public ::testing::Test { + protected: + ExternalVideoEncoderTest() + : test_video_encoder_callback_(new TestVideoEncoderCallback()) { + video_config_.rtp_config.ssrc = 1; + video_config_.incoming_feedback_ssrc = 2; + video_config_.rtp_config.payload_type = 127; + video_config_.use_external_encoder = true; + video_config_.width = 320; + video_config_.height = 240; + video_config_.max_bitrate = 5000000; + video_config_.min_bitrate = 1000000; + video_config_.start_bitrate = 2000000; + video_config_.max_qp = 56; + video_config_.min_qp = 0; + video_config_.max_frame_rate = 30; + video_config_.max_number_of_video_buffers_used = 3; + video_config_.codec = transport::kVp8; + gfx::Size size(video_config_.width, video_config_.height); + video_frame_ = media::VideoFrame::CreateFrame( + VideoFrame::I420, size, gfx::Rect(size), size, base::TimeDelta()); + PopulateVideoFrame(video_frame_, 123); + + testing_clock_ = new base::SimpleTestTickClock(); + task_runner_ = new test::FakeSingleThreadTaskRunner(testing_clock_); + cast_environment_ = + new CastEnvironment(scoped_ptr<base::TickClock>(testing_clock_).Pass(), + task_runner_, + task_runner_, + task_runner_); + + fake_vea_ = new test::FakeVideoEncodeAccelerator(task_runner_); + scoped_ptr<VideoEncodeAccelerator> fake_vea(fake_vea_); + video_encoder_.reset( + new ExternalVideoEncoder(cast_environment_, + video_config_, + base::Bind(&CreateVideoEncodeAccelerator, + task_runner_, + base::Passed(&fake_vea)), + base::Bind(&CreateSharedMemory))); + } + + virtual ~ExternalVideoEncoderTest() {} + + base::SimpleTestTickClock* testing_clock_; // Owned by CastEnvironment. + test::FakeVideoEncodeAccelerator* fake_vea_; // Owned by video_encoder_. + scoped_refptr<TestVideoEncoderCallback> test_video_encoder_callback_; + VideoSenderConfig video_config_; + scoped_refptr<test::FakeSingleThreadTaskRunner> task_runner_; + scoped_ptr<VideoEncoder> video_encoder_; + scoped_refptr<media::VideoFrame> video_frame_; + scoped_refptr<CastEnvironment> cast_environment_; + + DISALLOW_COPY_AND_ASSIGN(ExternalVideoEncoderTest); +}; + +TEST_F(ExternalVideoEncoderTest, EncodePattern30fpsRunningOutOfAck) { + task_runner_->RunTasks(); // Run the initializer on the correct thread. + + VideoEncoder::FrameEncodedCallback frame_encoded_callback = + base::Bind(&TestVideoEncoderCallback::DeliverEncodedVideoFrame, + test_video_encoder_callback_.get()); + + base::TimeTicks capture_time; + capture_time += base::TimeDelta::FromMilliseconds(33); + test_video_encoder_callback_->SetExpectedResult(0, 0, capture_time); + EXPECT_TRUE(video_encoder_->EncodeVideoFrame( + video_frame_, capture_time, frame_encoded_callback)); + task_runner_->RunTasks(); + + for (int i = 0; i < 6; ++i) { + capture_time += base::TimeDelta::FromMilliseconds(33); + test_video_encoder_callback_->SetExpectedResult(i + 1, i, capture_time); + EXPECT_TRUE(video_encoder_->EncodeVideoFrame( + video_frame_, capture_time, frame_encoded_callback)); + task_runner_->RunTasks(); + } + // We need to run the task to cleanup the GPU instance. + video_encoder_.reset(NULL); + task_runner_->RunTasks(); +} + +TEST_F(ExternalVideoEncoderTest, StreamHeader) { + task_runner_->RunTasks(); // Run the initializer on the correct thread. + + VideoEncoder::FrameEncodedCallback frame_encoded_callback = + base::Bind(&TestVideoEncoderCallback::DeliverEncodedVideoFrame, + test_video_encoder_callback_.get()); + + // Force the FakeVideoEncodeAccelerator to return a dummy non-key frame first. + fake_vea_->SendDummyFrameForTesting(false); + + // Verify the first returned bitstream buffer is still a key frame. + base::TimeTicks capture_time; + capture_time += base::TimeDelta::FromMilliseconds(33); + test_video_encoder_callback_->SetExpectedResult(0, 0, capture_time); + EXPECT_TRUE(video_encoder_->EncodeVideoFrame( + video_frame_, capture_time, frame_encoded_callback)); + task_runner_->RunTasks(); + + // We need to run the task to cleanup the GPU instance. + video_encoder_.reset(NULL); + task_runner_->RunTasks(); +} + +} // namespace cast +} // namespace media diff --git a/chromium/media/cast/video_sender/fake_software_video_encoder.cc b/chromium/media/cast/video_sender/fake_software_video_encoder.cc new file mode 100644 index 00000000000..7c5c9526419 --- /dev/null +++ b/chromium/media/cast/video_sender/fake_software_video_encoder.cc @@ -0,0 +1,69 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/cast/video_sender/fake_software_video_encoder.h" + +#include "base/json/json_writer.h" +#include "base/values.h" +#include "media/cast/transport/cast_transport_config.h" + +#ifndef OFFICIAL_BUILD + +namespace media { +namespace cast { + +FakeSoftwareVideoEncoder::FakeSoftwareVideoEncoder( + const VideoSenderConfig& video_config) + : video_config_(video_config), + next_frame_is_key_(true), + frame_id_(0), + frame_id_to_reference_(0), + frame_size_(0) { +} + +FakeSoftwareVideoEncoder::~FakeSoftwareVideoEncoder() {} + +void FakeSoftwareVideoEncoder::Initialize() {} + +bool FakeSoftwareVideoEncoder::Encode( + const scoped_refptr<media::VideoFrame>& video_frame, + transport::EncodedFrame* encoded_image) { + encoded_image->frame_id = frame_id_++; + if (next_frame_is_key_) { + encoded_image->dependency = transport::EncodedFrame::KEY; + encoded_image->referenced_frame_id = encoded_image->frame_id; + next_frame_is_key_ = false; + } else { + encoded_image->dependency = transport::EncodedFrame::DEPENDENT; + encoded_image->referenced_frame_id = encoded_image->frame_id - 1; + } + + base::DictionaryValue values; + values.SetBoolean("key", + encoded_image->dependency == transport::EncodedFrame::KEY); + values.SetInteger("ref", encoded_image->referenced_frame_id); + values.SetInteger("id", encoded_image->frame_id); + values.SetInteger("size", frame_size_); + base::JSONWriter::Write(&values, &encoded_image->data); + encoded_image->data.resize( + std::max<size_t>(encoded_image->data.size(), frame_size_)); + return true; +} + +void FakeSoftwareVideoEncoder::UpdateRates(uint32 new_bitrate) { + frame_size_ = new_bitrate / video_config_.max_frame_rate / 8; +} + +void FakeSoftwareVideoEncoder::GenerateKeyFrame() { + next_frame_is_key_ = true; +} + +void FakeSoftwareVideoEncoder::LatestFrameIdToReference(uint32 frame_id) { + frame_id_to_reference_ = frame_id; +} + +} // namespace cast +} // namespace media + +#endif diff --git a/chromium/media/cast/video_sender/fake_software_video_encoder.h b/chromium/media/cast/video_sender/fake_software_video_encoder.h new file mode 100644 index 00000000000..0eb88ddfe17 --- /dev/null +++ b/chromium/media/cast/video_sender/fake_software_video_encoder.h @@ -0,0 +1,38 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_CAST_VIDEO_SENDER_FAKE_SOFTWARE_VIDEO_ENCODER_H_ +#define MEDIA_CAST_VIDEO_SENDER_FAKE_SOFTWARE_VIDEO_ENCODER_H_ + +#include "media/cast/cast_config.h" +#include "media/cast/video_sender/software_video_encoder.h" + +namespace media { +namespace cast { + +class FakeSoftwareVideoEncoder : public SoftwareVideoEncoder { + public: + FakeSoftwareVideoEncoder(const VideoSenderConfig& video_config); + virtual ~FakeSoftwareVideoEncoder(); + + // SoftwareVideoEncoder implementations. + virtual void Initialize() OVERRIDE; + virtual bool Encode(const scoped_refptr<media::VideoFrame>& video_frame, + transport::EncodedFrame* encoded_image) OVERRIDE; + virtual void UpdateRates(uint32 new_bitrate) OVERRIDE; + virtual void GenerateKeyFrame() OVERRIDE; + virtual void LatestFrameIdToReference(uint32 frame_id) OVERRIDE; + + private: + VideoSenderConfig video_config_; + bool next_frame_is_key_; + uint32 frame_id_; + uint32 frame_id_to_reference_; + int frame_size_; +}; + +} // namespace cast +} // namespace media + +#endif // MEDIA_CAST_VIDEO_SENDER_FAKE_SOFTWARE_VIDEO_ENCODER_H_ diff --git a/chromium/media/cast/video_sender/mock_video_encoder_controller.cc b/chromium/media/cast/video_sender/mock_video_encoder_controller.cc deleted file mode 100644 index 4f649aa44fe..00000000000 --- a/chromium/media/cast/video_sender/mock_video_encoder_controller.cc +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "media/cast/video_sender/mock_video_encoder_controller.h" - -namespace media { -namespace cast { - -MockVideoEncoderController::MockVideoEncoderController() { -} - -MockVideoEncoderController::~MockVideoEncoderController() { -} - -} // namespace cast -} // namespace media diff --git a/chromium/media/cast/video_sender/mock_video_encoder_controller.h b/chromium/media/cast/video_sender/mock_video_encoder_controller.h deleted file mode 100644 index cfc58a9eb8f..00000000000 --- a/chromium/media/cast/video_sender/mock_video_encoder_controller.h +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef MEDIA_CAST_VIDEO_SENDER_MOCK_VIDEO_ENCODER_CONTROLLER_H_ -#define MEDIA_CAST_VIDEO_SENDER_MOCK_VIDEO_ENCODER_CONTROLLER_H_ - -#include "media/cast/cast_config.h" -#include "testing/gmock/include/gmock/gmock.h" - -namespace media { -namespace cast { - -class MockVideoEncoderController : public VideoEncoderController { - public: - MockVideoEncoderController(); - virtual ~MockVideoEncoderController(); - - MOCK_METHOD1(SetBitRate, void(int new_bit_rate)); - - MOCK_METHOD1(SkipNextFrame, void(bool skip_next_frame)); - - MOCK_METHOD0(GenerateKeyFrame, void()); - - MOCK_METHOD1(LatestFrameIdToReference, void(uint32 frame_id)); - - MOCK_CONST_METHOD0(NumberOfSkippedFrames, int()); -}; - -} // namespace cast -} // namespace media - -#endif // MEDIA_CAST_VIDEO_SENDER_MOCK_VIDEO_ENCODER_CONTROLLER_H_ - diff --git a/chromium/media/cast/video_sender/software_video_encoder.h b/chromium/media/cast/video_sender/software_video_encoder.h new file mode 100644 index 00000000000..f1bf6f63316 --- /dev/null +++ b/chromium/media/cast/video_sender/software_video_encoder.h @@ -0,0 +1,46 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_CAST_VIDEO_SENDER_SOFTWARE_VIDEO_ENCODER_H_ +#define MEDIA_CAST_VIDEO_SENDER_SOFTWARE_VIDEO_ENCODER_H_ + +#include "base/basictypes.h" +#include "base/memory/ref_counted.h" + +namespace media { +class VideoFrame; +} + +namespace media { +namespace cast { +namespace transport { +struct EncodedFrame; +} // namespace transport + +class SoftwareVideoEncoder { + public: + virtual ~SoftwareVideoEncoder() {} + + // Initialize the encoder before Encode() can be called. This method + // must be called on the thread that Encode() is called. + virtual void Initialize() = 0; + + // Encode a raw image (as a part of a video stream). + virtual bool Encode(const scoped_refptr<media::VideoFrame>& video_frame, + transport::EncodedFrame* encoded_image) = 0; + + // Update the encoder with a new target bit rate. + virtual void UpdateRates(uint32 new_bitrate) = 0; + + // Set the next frame to be a key frame. + virtual void GenerateKeyFrame() = 0; + + // Set the last frame to reference. + virtual void LatestFrameIdToReference(uint32 frame_id) = 0; +}; + +} // namespace cast +} // namespace media + +#endif // MEDIA_CAST_VIDEO_SENDER_SOFTWARE_VIDEO_ENCODER_H_ diff --git a/chromium/media/cast/video_sender/video_encoder.cc b/chromium/media/cast/video_sender/video_encoder.cc deleted file mode 100644 index faa78d3a3e7..00000000000 --- a/chromium/media/cast/video_sender/video_encoder.cc +++ /dev/null @@ -1,123 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "media/cast/video_sender/video_encoder.h" - -#include "base/bind.h" -#include "base/logging.h" -#include "base/message_loop/message_loop.h" -#include "media/base/video_frame.h" -#include "media/cast/cast_defines.h" - -namespace media { -namespace cast { - -void LogFrameEncodedEvent(CastEnvironment* const cast_environment, - const base::TimeTicks& capture_time) { - cast_environment->Logging()->InsertFrameEvent(kVideoFrameEncoded, - GetVideoRtpTimestamp(capture_time), kFrameIdUnknown); -} - -VideoEncoder::VideoEncoder(scoped_refptr<CastEnvironment> cast_environment, - const VideoSenderConfig& video_config, - uint8 max_unacked_frames) - : video_config_(video_config), - cast_environment_(cast_environment), - skip_next_frame_(false), - skip_count_(0) { - if (video_config.codec == kVp8) { - vp8_encoder_.reset(new Vp8Encoder(video_config, max_unacked_frames)); - } else { - DCHECK(false) << "Invalid config"; // Codec not supported. - } - - dynamic_config_.key_frame_requested = false; - dynamic_config_.latest_frame_id_to_reference = kStartFrameId; - dynamic_config_.bit_rate = video_config.start_bitrate; -} - -VideoEncoder::~VideoEncoder() {} - -bool VideoEncoder::EncodeVideoFrame( - const scoped_refptr<media::VideoFrame>& video_frame, - const base::TimeTicks& capture_time, - const FrameEncodedCallback& frame_encoded_callback) { - DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); - if (video_config_.codec != kVp8) return false; - - if (skip_next_frame_) { - ++skip_count_; - VLOG(1) << "Skip encoding frame"; - return false; - } - - cast_environment_->Logging()->InsertFrameEvent(kVideoFrameSentToEncoder, - GetVideoRtpTimestamp(capture_time), kFrameIdUnknown); - cast_environment_->PostTask(CastEnvironment::VIDEO_ENCODER, FROM_HERE, - base::Bind(&VideoEncoder::EncodeVideoFrameEncoderThread, - base::Unretained(this), video_frame, capture_time, - dynamic_config_, frame_encoded_callback)); - - dynamic_config_.key_frame_requested = false; - return true; -} - -void VideoEncoder::EncodeVideoFrameEncoderThread( - const scoped_refptr<media::VideoFrame>& video_frame, - const base::TimeTicks& capture_time, - const CodecDynamicConfig& dynamic_config, - const FrameEncodedCallback& frame_encoded_callback) { - DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::VIDEO_ENCODER)); - if (dynamic_config.key_frame_requested) { - vp8_encoder_->GenerateKeyFrame(); - } - vp8_encoder_->LatestFrameIdToReference( - dynamic_config.latest_frame_id_to_reference); - vp8_encoder_->UpdateRates(dynamic_config.bit_rate); - - scoped_ptr<EncodedVideoFrame> encoded_frame(new EncodedVideoFrame()); - bool retval = vp8_encoder_->Encode(video_frame, encoded_frame.get()); - - cast_environment_->PostTask(CastEnvironment::MAIN, FROM_HERE, - base::Bind(LogFrameEncodedEvent, cast_environment_, capture_time)); - - if (!retval) { - VLOG(1) << "Encoding failed"; - return; - } - if (encoded_frame->data.size() <= 0) { - VLOG(1) << "Encoding resulted in an empty frame"; - return; - } - cast_environment_->PostTask(CastEnvironment::MAIN, FROM_HERE, - base::Bind(frame_encoded_callback, - base::Passed(&encoded_frame), capture_time)); -} - -// Inform the encoder about the new target bit rate. -void VideoEncoder::SetBitRate(int new_bit_rate) { - dynamic_config_.bit_rate = new_bit_rate; -} - -// Inform the encoder to not encode the next frame. -void VideoEncoder::SkipNextFrame(bool skip_next_frame) { - skip_next_frame_ = skip_next_frame; -} - -// Inform the encoder to encode the next frame as a key frame. -void VideoEncoder::GenerateKeyFrame() { - dynamic_config_.key_frame_requested = true; -} - -// Inform the encoder to only reference frames older or equal to frame_id; -void VideoEncoder::LatestFrameIdToReference(uint32 frame_id) { - dynamic_config_.latest_frame_id_to_reference = frame_id; -} - -int VideoEncoder::NumberOfSkippedFrames() const { - return skip_count_; -} - -} // namespace cast -} // namespace media diff --git a/chromium/media/cast/video_sender/video_encoder.h b/chromium/media/cast/video_sender/video_encoder.h index 559dff16734..c7b1049ce67 100644 --- a/chromium/media/cast/video_sender/video_encoder.h +++ b/chromium/media/cast/video_sender/video_encoder.h @@ -5,76 +5,44 @@ #ifndef MEDIA_CAST_VIDEO_SENDER_VIDEO_ENCODER_H_ #define MEDIA_CAST_VIDEO_SENDER_VIDEO_ENCODER_H_ +#include "base/callback.h" +#include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" #include "base/memory/weak_ptr.h" -#include "base/message_loop/message_loop.h" +#include "base/time/time.h" +#include "media/base/video_frame.h" #include "media/cast/cast_config.h" #include "media/cast/cast_environment.h" -#include "media/cast/video_sender/codecs/vp8/vp8_encoder.h" - -namespace media { -class VideoFrame; -} namespace media { namespace cast { -// This object is called external from the main cast thread and internally from -// the video encoder thread. -class VideoEncoder : public VideoEncoderController { +// All these functions are called from the main cast thread. +class VideoEncoder { public: - typedef base::Callback<void(scoped_ptr<EncodedVideoFrame>, - const base::TimeTicks&)> FrameEncodedCallback; - - VideoEncoder(scoped_refptr<CastEnvironment> cast_environment, - const VideoSenderConfig& video_config, - uint8 max_unacked_frames); + typedef base::Callback<void(scoped_ptr<transport::EncodedFrame>)> + FrameEncodedCallback; - virtual ~VideoEncoder(); + virtual ~VideoEncoder() {} - // Called from the main cast thread. This function post the encode task to the - // video encoder thread; // The video_frame must be valid until the closure callback is called. // The closure callback is called from the video encoder thread as soon as // the encoder is done with the frame; it does not mean that the encoded frame // has been sent out. // Once the encoded frame is ready the frame_encoded_callback is called. - bool EncodeVideoFrame(const scoped_refptr<media::VideoFrame>& video_frame, - const base::TimeTicks& capture_time, - const FrameEncodedCallback& frame_encoded_callback); - - protected: - struct CodecDynamicConfig { - bool key_frame_requested; - uint32 latest_frame_id_to_reference; - int bit_rate; - }; - - // The actual encode, called from the video encoder thread. - void EncodeVideoFrameEncoderThread( + virtual bool EncodeVideoFrame( const scoped_refptr<media::VideoFrame>& video_frame, const base::TimeTicks& capture_time, - const CodecDynamicConfig& dynamic_config, - const FrameEncodedCallback& frame_encoded_callback); - - // The following functions are called from the main cast thread. - virtual void SetBitRate(int new_bit_rate) OVERRIDE; - virtual void SkipNextFrame(bool skip_next_frame) OVERRIDE; - virtual void GenerateKeyFrame() OVERRIDE; - virtual void LatestFrameIdToReference(uint32 frame_id) OVERRIDE; - virtual int NumberOfSkippedFrames() const OVERRIDE; + const FrameEncodedCallback& frame_encoded_callback) = 0; - private: - friend class base::RefCountedThreadSafe<VideoEncoder>; + // Inform the encoder about the new target bit rate. + virtual void SetBitRate(int new_bit_rate) = 0; - const VideoSenderConfig video_config_; - scoped_refptr<CastEnvironment> cast_environment_; - scoped_ptr<Vp8Encoder> vp8_encoder_; - CodecDynamicConfig dynamic_config_; - bool skip_next_frame_; - int skip_count_; + // Inform the encoder to encode the next frame as a key frame. + virtual void GenerateKeyFrame() = 0; - DISALLOW_COPY_AND_ASSIGN(VideoEncoder); + // Inform the encoder to only reference frames older or equal to frame_id; + virtual void LatestFrameIdToReference(uint32 frame_id) = 0; }; } // namespace cast diff --git a/chromium/media/cast/video_sender/video_encoder_impl.cc b/chromium/media/cast/video_sender/video_encoder_impl.cc new file mode 100644 index 00000000000..b90ef0f07e3 --- /dev/null +++ b/chromium/media/cast/video_sender/video_encoder_impl.cc @@ -0,0 +1,139 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/cast/video_sender/video_encoder_impl.h" + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/callback.h" +#include "base/logging.h" +#include "base/message_loop/message_loop.h" +#include "media/base/video_frame.h" +#include "media/cast/cast_defines.h" +#include "media/cast/video_sender/codecs/vp8/vp8_encoder.h" +#include "media/cast/video_sender/fake_software_video_encoder.h" + +namespace media { +namespace cast { + +namespace { + +typedef base::Callback<void(Vp8Encoder*)> PassEncoderCallback; + +void InitializeEncoderOnEncoderThread( + const scoped_refptr<CastEnvironment>& environment, + SoftwareVideoEncoder* encoder) { + DCHECK(environment->CurrentlyOn(CastEnvironment::VIDEO)); + encoder->Initialize(); +} + +void EncodeVideoFrameOnEncoderThread( + scoped_refptr<CastEnvironment> environment, + SoftwareVideoEncoder* encoder, + const scoped_refptr<media::VideoFrame>& video_frame, + const base::TimeTicks& capture_time, + const VideoEncoderImpl::CodecDynamicConfig& dynamic_config, + const VideoEncoderImpl::FrameEncodedCallback& frame_encoded_callback) { + DCHECK(environment->CurrentlyOn(CastEnvironment::VIDEO)); + if (dynamic_config.key_frame_requested) { + encoder->GenerateKeyFrame(); + } + encoder->LatestFrameIdToReference( + dynamic_config.latest_frame_id_to_reference); + encoder->UpdateRates(dynamic_config.bit_rate); + + scoped_ptr<transport::EncodedFrame> encoded_frame( + new transport::EncodedFrame()); + if (!encoder->Encode(video_frame, encoded_frame.get())) { + VLOG(1) << "Encoding failed"; + return; + } + if (encoded_frame->data.empty()) { + VLOG(1) << "Encoding resulted in an empty frame"; + return; + } + encoded_frame->rtp_timestamp = transport::GetVideoRtpTimestamp(capture_time); + encoded_frame->reference_time = capture_time; + + environment->PostTask( + CastEnvironment::MAIN, + FROM_HERE, + base::Bind( + frame_encoded_callback, base::Passed(&encoded_frame))); +} +} // namespace + +VideoEncoderImpl::VideoEncoderImpl( + scoped_refptr<CastEnvironment> cast_environment, + const VideoSenderConfig& video_config, + int max_unacked_frames) + : video_config_(video_config), + cast_environment_(cast_environment) { + if (video_config.codec == transport::kVp8) { + encoder_.reset(new Vp8Encoder(video_config, max_unacked_frames)); + cast_environment_->PostTask(CastEnvironment::VIDEO, + FROM_HERE, + base::Bind(&InitializeEncoderOnEncoderThread, + cast_environment, + encoder_.get())); +#ifndef OFFICIAL_BUILD + } else if (video_config.codec == transport::kFakeSoftwareVideo) { + encoder_.reset(new FakeSoftwareVideoEncoder(video_config)); +#endif + } else { + DCHECK(false) << "Invalid config"; // Codec not supported. + } + + dynamic_config_.key_frame_requested = false; + dynamic_config_.latest_frame_id_to_reference = kStartFrameId; + dynamic_config_.bit_rate = video_config.start_bitrate; +} + +VideoEncoderImpl::~VideoEncoderImpl() { + DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); + if (encoder_) { + cast_environment_->PostTask( + CastEnvironment::VIDEO, + FROM_HERE, + base::Bind(&base::DeletePointer<SoftwareVideoEncoder>, + encoder_.release())); + } +} + +bool VideoEncoderImpl::EncodeVideoFrame( + const scoped_refptr<media::VideoFrame>& video_frame, + const base::TimeTicks& capture_time, + const FrameEncodedCallback& frame_encoded_callback) { + DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); + cast_environment_->PostTask(CastEnvironment::VIDEO, + FROM_HERE, + base::Bind(&EncodeVideoFrameOnEncoderThread, + cast_environment_, + encoder_.get(), + video_frame, + capture_time, + dynamic_config_, + frame_encoded_callback)); + + dynamic_config_.key_frame_requested = false; + return true; +} + +// Inform the encoder about the new target bit rate. +void VideoEncoderImpl::SetBitRate(int new_bit_rate) { + dynamic_config_.bit_rate = new_bit_rate; +} + +// Inform the encoder to encode the next frame as a key frame. +void VideoEncoderImpl::GenerateKeyFrame() { + dynamic_config_.key_frame_requested = true; +} + +// Inform the encoder to only reference frames older or equal to frame_id; +void VideoEncoderImpl::LatestFrameIdToReference(uint32 frame_id) { + dynamic_config_.latest_frame_id_to_reference = frame_id; +} + +} // namespace cast +} // namespace media diff --git a/chromium/media/cast/video_sender/video_encoder_impl.h b/chromium/media/cast/video_sender/video_encoder_impl.h new file mode 100644 index 00000000000..b34b440c935 --- /dev/null +++ b/chromium/media/cast/video_sender/video_encoder_impl.h @@ -0,0 +1,72 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_CAST_VIDEO_SENDER_VIDEO_ENCODER_IMPL_H_ +#define MEDIA_CAST_VIDEO_SENDER_VIDEO_ENCODER_IMPL_H_ + +#include "base/memory/scoped_ptr.h" +#include "media/cast/cast_config.h" +#include "media/cast/cast_environment.h" +#include "media/cast/video_sender/software_video_encoder.h" +#include "media/cast/video_sender/video_encoder.h" + +namespace media { +class VideoFrame; + +namespace cast { + +// This object is called external from the main cast thread and internally from +// the video encoder thread. +class VideoEncoderImpl : public VideoEncoder { + public: + struct CodecDynamicConfig { + bool key_frame_requested; + uint32 latest_frame_id_to_reference; + int bit_rate; + }; + + typedef base::Callback<void(scoped_ptr<transport::EncodedFrame>)> + FrameEncodedCallback; + + VideoEncoderImpl(scoped_refptr<CastEnvironment> cast_environment, + const VideoSenderConfig& video_config, + int max_unacked_frames); + + virtual ~VideoEncoderImpl(); + + // Called from the main cast thread. This function post the encode task to the + // video encoder thread; + // The video_frame must be valid until the closure callback is called. + // The closure callback is called from the video encoder thread as soon as + // the encoder is done with the frame; it does not mean that the encoded frame + // has been sent out. + // Once the encoded frame is ready the frame_encoded_callback is called. + virtual bool EncodeVideoFrame( + const scoped_refptr<media::VideoFrame>& video_frame, + const base::TimeTicks& capture_time, + const FrameEncodedCallback& frame_encoded_callback) OVERRIDE; + + // The following functions are called from the main cast thread. + virtual void SetBitRate(int new_bit_rate) OVERRIDE; + virtual void GenerateKeyFrame() OVERRIDE; + virtual void LatestFrameIdToReference(uint32 frame_id) OVERRIDE; + + private: + const VideoSenderConfig video_config_; + scoped_refptr<CastEnvironment> cast_environment_; + CodecDynamicConfig dynamic_config_; + + // This member belongs to the video encoder thread. It must not be + // dereferenced on the main thread. We manage the lifetime of this member + // manually because it needs to be initialize, used and destroyed on the + // video encoder thread and video encoder thread can out-live the main thread. + scoped_ptr<SoftwareVideoEncoder> encoder_; + + DISALLOW_COPY_AND_ASSIGN(VideoEncoderImpl); +}; + +} // namespace cast +} // namespace media + +#endif // MEDIA_CAST_VIDEO_SENDER_VIDEO_ENCODER_IMPL_H_ diff --git a/chromium/media/cast/video_sender/video_encoder_impl_unittest.cc b/chromium/media/cast/video_sender/video_encoder_impl_unittest.cc new file mode 100644 index 00000000000..a60812304f2 --- /dev/null +++ b/chromium/media/cast/video_sender/video_encoder_impl_unittest.cc @@ -0,0 +1,260 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <vector> + +#include "base/bind.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "media/base/video_frame.h" +#include "media/cast/cast_defines.h" +#include "media/cast/cast_environment.h" +#include "media/cast/test/fake_single_thread_task_runner.h" +#include "media/cast/test/utility/video_utility.h" +#include "media/cast/video_sender/video_encoder_impl.h" +#include "testing/gmock/include/gmock/gmock.h" + +namespace media { +namespace cast { + +using testing::_; + +namespace { +class TestVideoEncoderCallback + : public base::RefCountedThreadSafe<TestVideoEncoderCallback> { + public: + TestVideoEncoderCallback() {} + + void SetExpectedResult(uint32 expected_frame_id, + uint32 expected_last_referenced_frame_id, + const base::TimeTicks& expected_capture_time) { + expected_frame_id_ = expected_frame_id; + expected_last_referenced_frame_id_ = expected_last_referenced_frame_id; + expected_capture_time_ = expected_capture_time; + } + + void DeliverEncodedVideoFrame( + scoped_ptr<transport::EncodedFrame> encoded_frame) { + if (expected_frame_id_ == expected_last_referenced_frame_id_) { + EXPECT_EQ(transport::EncodedFrame::KEY, encoded_frame->dependency); + } else { + EXPECT_EQ(transport::EncodedFrame::DEPENDENT, + encoded_frame->dependency); + } + EXPECT_EQ(expected_frame_id_, encoded_frame->frame_id); + EXPECT_EQ(expected_last_referenced_frame_id_, + encoded_frame->referenced_frame_id); + EXPECT_EQ(expected_capture_time_, encoded_frame->reference_time); + } + + protected: + virtual ~TestVideoEncoderCallback() {} + + private: + friend class base::RefCountedThreadSafe<TestVideoEncoderCallback>; + + uint32 expected_frame_id_; + uint32 expected_last_referenced_frame_id_; + base::TimeTicks expected_capture_time_; + + DISALLOW_COPY_AND_ASSIGN(TestVideoEncoderCallback); +}; +} // namespace + +class VideoEncoderImplTest : public ::testing::Test { + protected: + VideoEncoderImplTest() + : test_video_encoder_callback_(new TestVideoEncoderCallback()) { + video_config_.rtp_config.ssrc = 1; + video_config_.incoming_feedback_ssrc = 2; + video_config_.rtp_config.payload_type = 127; + video_config_.use_external_encoder = false; + video_config_.width = 320; + video_config_.height = 240; + video_config_.max_bitrate = 5000000; + video_config_.min_bitrate = 1000000; + video_config_.start_bitrate = 2000000; + video_config_.max_qp = 56; + video_config_.min_qp = 0; + video_config_.max_frame_rate = 30; + video_config_.max_number_of_video_buffers_used = 3; + video_config_.codec = transport::kVp8; + gfx::Size size(video_config_.width, video_config_.height); + video_frame_ = media::VideoFrame::CreateFrame( + VideoFrame::I420, size, gfx::Rect(size), size, base::TimeDelta()); + PopulateVideoFrame(video_frame_, 123); + } + + virtual ~VideoEncoderImplTest() {} + + virtual void SetUp() OVERRIDE { + testing_clock_ = new base::SimpleTestTickClock(); + task_runner_ = new test::FakeSingleThreadTaskRunner(testing_clock_); + cast_environment_ = + new CastEnvironment(scoped_ptr<base::TickClock>(testing_clock_).Pass(), + task_runner_, + task_runner_, + task_runner_); + } + + virtual void TearDown() OVERRIDE { + video_encoder_.reset(); + task_runner_->RunTasks(); + } + + void Configure(int max_unacked_frames) { + video_encoder_.reset(new VideoEncoderImpl( + cast_environment_, video_config_, max_unacked_frames)); + } + + base::SimpleTestTickClock* testing_clock_; // Owned by CastEnvironment. + scoped_refptr<TestVideoEncoderCallback> test_video_encoder_callback_; + VideoSenderConfig video_config_; + scoped_refptr<test::FakeSingleThreadTaskRunner> task_runner_; + scoped_ptr<VideoEncoder> video_encoder_; + scoped_refptr<media::VideoFrame> video_frame_; + + scoped_refptr<CastEnvironment> cast_environment_; + + DISALLOW_COPY_AND_ASSIGN(VideoEncoderImplTest); +}; + +TEST_F(VideoEncoderImplTest, EncodePattern30fpsRunningOutOfAck) { + Configure(3); + + VideoEncoder::FrameEncodedCallback frame_encoded_callback = + base::Bind(&TestVideoEncoderCallback::DeliverEncodedVideoFrame, + test_video_encoder_callback_.get()); + + base::TimeTicks capture_time; + capture_time += base::TimeDelta::FromMilliseconds(33); + test_video_encoder_callback_->SetExpectedResult(0, 0, capture_time); + EXPECT_TRUE(video_encoder_->EncodeVideoFrame( + video_frame_, capture_time, frame_encoded_callback)); + task_runner_->RunTasks(); + + capture_time += base::TimeDelta::FromMilliseconds(33); + video_encoder_->LatestFrameIdToReference(0); + test_video_encoder_callback_->SetExpectedResult(1, 0, capture_time); + EXPECT_TRUE(video_encoder_->EncodeVideoFrame( + video_frame_, capture_time, frame_encoded_callback)); + task_runner_->RunTasks(); + + capture_time += base::TimeDelta::FromMilliseconds(33); + video_encoder_->LatestFrameIdToReference(1); + test_video_encoder_callback_->SetExpectedResult(2, 1, capture_time); + EXPECT_TRUE(video_encoder_->EncodeVideoFrame( + video_frame_, capture_time, frame_encoded_callback)); + task_runner_->RunTasks(); + + video_encoder_->LatestFrameIdToReference(2); + + for (int i = 3; i < 6; ++i) { + capture_time += base::TimeDelta::FromMilliseconds(33); + test_video_encoder_callback_->SetExpectedResult(i, 2, capture_time); + EXPECT_TRUE(video_encoder_->EncodeVideoFrame( + video_frame_, capture_time, frame_encoded_callback)); + task_runner_->RunTasks(); + } +} + +// TODO(pwestin): Re-enabled after redesign the encoder to control number of +// frames in flight. +TEST_F(VideoEncoderImplTest, DISABLED_EncodePattern60fpsRunningOutOfAck) { + video_config_.max_number_of_video_buffers_used = 1; + Configure(6); + + base::TimeTicks capture_time; + VideoEncoder::FrameEncodedCallback frame_encoded_callback = + base::Bind(&TestVideoEncoderCallback::DeliverEncodedVideoFrame, + test_video_encoder_callback_.get()); + + capture_time += base::TimeDelta::FromMilliseconds(33); + test_video_encoder_callback_->SetExpectedResult(0, 0, capture_time); + EXPECT_TRUE(video_encoder_->EncodeVideoFrame( + video_frame_, capture_time, frame_encoded_callback)); + task_runner_->RunTasks(); + + video_encoder_->LatestFrameIdToReference(0); + capture_time += base::TimeDelta::FromMilliseconds(33); + test_video_encoder_callback_->SetExpectedResult(1, 0, capture_time); + EXPECT_TRUE(video_encoder_->EncodeVideoFrame( + video_frame_, capture_time, frame_encoded_callback)); + task_runner_->RunTasks(); + + video_encoder_->LatestFrameIdToReference(1); + capture_time += base::TimeDelta::FromMilliseconds(33); + test_video_encoder_callback_->SetExpectedResult(2, 0, capture_time); + EXPECT_TRUE(video_encoder_->EncodeVideoFrame( + video_frame_, capture_time, frame_encoded_callback)); + task_runner_->RunTasks(); + + video_encoder_->LatestFrameIdToReference(2); + + for (int i = 3; i < 9; ++i) { + capture_time += base::TimeDelta::FromMilliseconds(33); + test_video_encoder_callback_->SetExpectedResult(i, 2, capture_time); + EXPECT_TRUE(video_encoder_->EncodeVideoFrame( + video_frame_, capture_time, frame_encoded_callback)); + task_runner_->RunTasks(); + } +} + +// TODO(pwestin): Re-enabled after redesign the encoder to control number of +// frames in flight. +TEST_F(VideoEncoderImplTest, + DISABLED_EncodePattern60fps200msDelayRunningOutOfAck) { + Configure(12); + + base::TimeTicks capture_time; + VideoEncoder::FrameEncodedCallback frame_encoded_callback = + base::Bind(&TestVideoEncoderCallback::DeliverEncodedVideoFrame, + test_video_encoder_callback_.get()); + + capture_time += base::TimeDelta::FromMilliseconds(33); + test_video_encoder_callback_->SetExpectedResult(0, 0, capture_time); + EXPECT_TRUE(video_encoder_->EncodeVideoFrame( + video_frame_, capture_time, frame_encoded_callback)); + task_runner_->RunTasks(); + + video_encoder_->LatestFrameIdToReference(0); + capture_time += base::TimeDelta::FromMilliseconds(33); + test_video_encoder_callback_->SetExpectedResult(1, 0, capture_time); + EXPECT_TRUE(video_encoder_->EncodeVideoFrame( + video_frame_, capture_time, frame_encoded_callback)); + task_runner_->RunTasks(); + + video_encoder_->LatestFrameIdToReference(1); + capture_time += base::TimeDelta::FromMilliseconds(33); + test_video_encoder_callback_->SetExpectedResult(2, 0, capture_time); + EXPECT_TRUE(video_encoder_->EncodeVideoFrame( + video_frame_, capture_time, frame_encoded_callback)); + task_runner_->RunTasks(); + + video_encoder_->LatestFrameIdToReference(2); + capture_time += base::TimeDelta::FromMilliseconds(33); + test_video_encoder_callback_->SetExpectedResult(3, 0, capture_time); + EXPECT_TRUE(video_encoder_->EncodeVideoFrame( + video_frame_, capture_time, frame_encoded_callback)); + task_runner_->RunTasks(); + + video_encoder_->LatestFrameIdToReference(3); + capture_time += base::TimeDelta::FromMilliseconds(33); + test_video_encoder_callback_->SetExpectedResult(4, 0, capture_time); + EXPECT_TRUE(video_encoder_->EncodeVideoFrame( + video_frame_, capture_time, frame_encoded_callback)); + task_runner_->RunTasks(); + + video_encoder_->LatestFrameIdToReference(4); + + for (int i = 5; i < 17; ++i) { + test_video_encoder_callback_->SetExpectedResult(i, 4, capture_time); + EXPECT_TRUE(video_encoder_->EncodeVideoFrame( + video_frame_, capture_time, frame_encoded_callback)); + task_runner_->RunTasks(); + } +} + +} // namespace cast +} // namespace media diff --git a/chromium/media/cast/video_sender/video_encoder_unittest.cc b/chromium/media/cast/video_sender/video_encoder_unittest.cc deleted file mode 100644 index b68b8364c43..00000000000 --- a/chromium/media/cast/video_sender/video_encoder_unittest.cc +++ /dev/null @@ -1,247 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include <vector> - -#include "base/bind.h" -#include "base/memory/ref_counted.h" -#include "base/memory/scoped_ptr.h" -#include "media/base/video_frame.h" -#include "media/cast/cast_defines.h" -#include "media/cast/cast_environment.h" -#include "media/cast/test/fake_task_runner.h" -#include "media/cast/test/video_utility.h" -#include "media/cast/video_sender/video_encoder.h" -#include "testing/gmock/include/gmock/gmock.h" - -namespace media { -namespace cast { - -using testing::_; - -namespace { -class TestVideoEncoderCallback : - public base::RefCountedThreadSafe<TestVideoEncoderCallback> { - public: - TestVideoEncoderCallback() {} - - void SetExpectedResult(bool expected_key_frame, - uint8 expected_frame_id, - uint8 expected_last_referenced_frame_id, - const base::TimeTicks& expected_capture_time) { - expected_key_frame_ = expected_key_frame; - expected_frame_id_ = expected_frame_id; - expected_last_referenced_frame_id_ = expected_last_referenced_frame_id; - expected_capture_time_ = expected_capture_time; - } - - void DeliverEncodedVideoFrame(scoped_ptr<EncodedVideoFrame> encoded_frame, - const base::TimeTicks& capture_time) { - EXPECT_EQ(expected_key_frame_, encoded_frame->key_frame); - EXPECT_EQ(expected_frame_id_, encoded_frame->frame_id); - EXPECT_EQ(expected_last_referenced_frame_id_, - encoded_frame->last_referenced_frame_id); - EXPECT_EQ(expected_capture_time_, capture_time); - } - - protected: - virtual ~TestVideoEncoderCallback() {} - - private: - friend class base::RefCountedThreadSafe<TestVideoEncoderCallback>; - - bool expected_key_frame_; - uint8 expected_frame_id_; - uint8 expected_last_referenced_frame_id_; - base::TimeTicks expected_capture_time_; -}; -} // namespace - -class VideoEncoderTest : public ::testing::Test { - protected: - VideoEncoderTest() - : test_video_encoder_callback_(new TestVideoEncoderCallback()) { - video_config_.sender_ssrc = 1; - video_config_.incoming_feedback_ssrc = 2; - video_config_.rtp_payload_type = 127; - video_config_.use_external_encoder = false; - video_config_.width = 320; - video_config_.height = 240; - video_config_.max_bitrate = 5000000; - video_config_.min_bitrate = 1000000; - video_config_.start_bitrate = 2000000; - video_config_.max_qp = 56; - video_config_.min_qp = 0; - video_config_.max_frame_rate = 30; - video_config_.max_number_of_video_buffers_used = 3; - video_config_.codec = kVp8; - gfx::Size size(video_config_.width, video_config_.height); - video_frame_ = media::VideoFrame::CreateFrame(VideoFrame::I420, - size, gfx::Rect(size), size, base::TimeDelta()); - PopulateVideoFrame(video_frame_, 123); - } - - virtual ~VideoEncoderTest() {} - - virtual void SetUp() { - task_runner_ = new test::FakeTaskRunner(&testing_clock_); - cast_environment_ = new CastEnvironment(&testing_clock_, task_runner_, - task_runner_, task_runner_, task_runner_, task_runner_, - GetDefaultCastLoggingConfig()); - } - - void Configure(uint8 max_unacked_frames) { - video_encoder_.reset(new VideoEncoder(cast_environment_, video_config_, - max_unacked_frames)); - video_encoder_controller_ = video_encoder_.get(); - } - - base::SimpleTestTickClock testing_clock_; - scoped_refptr<TestVideoEncoderCallback> test_video_encoder_callback_; - VideoSenderConfig video_config_; - scoped_refptr<test::FakeTaskRunner> task_runner_; - scoped_ptr<VideoEncoder> video_encoder_; - VideoEncoderController* video_encoder_controller_; - scoped_refptr<media::VideoFrame> video_frame_; - - scoped_refptr<CastEnvironment> cast_environment_; -}; - -TEST_F(VideoEncoderTest, EncodePattern30fpsRunningOutOfAck) { - Configure(3); - - VideoEncoder::FrameEncodedCallback frame_encoded_callback = - base::Bind(&TestVideoEncoderCallback::DeliverEncodedVideoFrame, - test_video_encoder_callback_.get()); - - base::TimeTicks capture_time; - capture_time += base::TimeDelta::FromMilliseconds(33); - test_video_encoder_callback_->SetExpectedResult(true, 0, 0, capture_time); - EXPECT_TRUE(video_encoder_->EncodeVideoFrame(video_frame_, capture_time, - frame_encoded_callback)); - task_runner_->RunTasks(); - - capture_time += base::TimeDelta::FromMilliseconds(33); - video_encoder_controller_->LatestFrameIdToReference(0); - test_video_encoder_callback_->SetExpectedResult(false, 1, 0, capture_time); - EXPECT_TRUE(video_encoder_->EncodeVideoFrame(video_frame_, capture_time, - frame_encoded_callback)); - task_runner_->RunTasks(); - - capture_time += base::TimeDelta::FromMilliseconds(33); - video_encoder_controller_->LatestFrameIdToReference(1); - test_video_encoder_callback_->SetExpectedResult(false, 2, 1, capture_time); - EXPECT_TRUE(video_encoder_->EncodeVideoFrame(video_frame_, capture_time, - frame_encoded_callback)); - task_runner_->RunTasks(); - - video_encoder_controller_->LatestFrameIdToReference(2); - - for (int i = 3; i < 6; ++i) { - capture_time += base::TimeDelta::FromMilliseconds(33); - test_video_encoder_callback_->SetExpectedResult(false, i, 2, capture_time); - EXPECT_TRUE(video_encoder_->EncodeVideoFrame(video_frame_, capture_time, - frame_encoded_callback)); - task_runner_->RunTasks(); - } -} - -// TODO(pwestin): Re-enabled after redesign the encoder to control number of -// frames in flight. -TEST_F(VideoEncoderTest,DISABLED_EncodePattern60fpsRunningOutOfAck) { - video_config_.max_number_of_video_buffers_used = 1; - Configure(6); - - base::TimeTicks capture_time; - VideoEncoder::FrameEncodedCallback frame_encoded_callback = - base::Bind(&TestVideoEncoderCallback::DeliverEncodedVideoFrame, - test_video_encoder_callback_.get()); - - capture_time += base::TimeDelta::FromMilliseconds(33); - test_video_encoder_callback_->SetExpectedResult(true, 0, 0, capture_time); - EXPECT_TRUE(video_encoder_->EncodeVideoFrame(video_frame_, capture_time, - frame_encoded_callback)); - task_runner_->RunTasks(); - - video_encoder_controller_->LatestFrameIdToReference(0); - capture_time += base::TimeDelta::FromMilliseconds(33); - test_video_encoder_callback_->SetExpectedResult(false, 1, 0, capture_time); - EXPECT_TRUE(video_encoder_->EncodeVideoFrame(video_frame_, capture_time, - frame_encoded_callback)); - task_runner_->RunTasks(); - - video_encoder_controller_->LatestFrameIdToReference(1); - capture_time += base::TimeDelta::FromMilliseconds(33); - test_video_encoder_callback_->SetExpectedResult(false, 2, 0, capture_time); - EXPECT_TRUE(video_encoder_->EncodeVideoFrame(video_frame_, capture_time, - frame_encoded_callback)); - task_runner_->RunTasks(); - - video_encoder_controller_->LatestFrameIdToReference(2); - - for (int i = 3; i < 9; ++i) { - capture_time += base::TimeDelta::FromMilliseconds(33); - test_video_encoder_callback_->SetExpectedResult(false, i, 2, capture_time); - EXPECT_TRUE(video_encoder_->EncodeVideoFrame(video_frame_, capture_time, - frame_encoded_callback)); - task_runner_->RunTasks(); - } -} - -// TODO(pwestin): Re-enabled after redesign the encoder to control number of -// frames in flight. -TEST_F(VideoEncoderTest, DISABLED_EncodePattern60fps200msDelayRunningOutOfAck) { - Configure(12); - - base::TimeTicks capture_time; - VideoEncoder::FrameEncodedCallback frame_encoded_callback = - base::Bind(&TestVideoEncoderCallback::DeliverEncodedVideoFrame, - test_video_encoder_callback_.get()); - - capture_time += base::TimeDelta::FromMilliseconds(33); - test_video_encoder_callback_->SetExpectedResult(true, 0, 0, capture_time); - EXPECT_TRUE(video_encoder_->EncodeVideoFrame(video_frame_, capture_time, - frame_encoded_callback)); - task_runner_->RunTasks(); - - video_encoder_controller_->LatestFrameIdToReference(0); - capture_time += base::TimeDelta::FromMilliseconds(33); - test_video_encoder_callback_->SetExpectedResult(false, 1, 0, capture_time); - EXPECT_TRUE(video_encoder_->EncodeVideoFrame(video_frame_, capture_time, - frame_encoded_callback)); - task_runner_->RunTasks(); - - video_encoder_controller_->LatestFrameIdToReference(1); - capture_time += base::TimeDelta::FromMilliseconds(33); - test_video_encoder_callback_->SetExpectedResult(false, 2, 0, capture_time); - EXPECT_TRUE(video_encoder_->EncodeVideoFrame(video_frame_, capture_time, - frame_encoded_callback)); - task_runner_->RunTasks(); - - video_encoder_controller_->LatestFrameIdToReference(2); - capture_time += base::TimeDelta::FromMilliseconds(33); - test_video_encoder_callback_->SetExpectedResult(false, 3, 0, capture_time); - EXPECT_TRUE(video_encoder_->EncodeVideoFrame(video_frame_, capture_time, - frame_encoded_callback)); - task_runner_->RunTasks(); - - video_encoder_controller_->LatestFrameIdToReference(3); - capture_time += base::TimeDelta::FromMilliseconds(33); - test_video_encoder_callback_->SetExpectedResult(false, 4, 0, capture_time); - EXPECT_TRUE(video_encoder_->EncodeVideoFrame(video_frame_, capture_time, - frame_encoded_callback)); - task_runner_->RunTasks(); - - video_encoder_controller_->LatestFrameIdToReference(4); - - for (int i = 5; i < 17; ++i) { - test_video_encoder_callback_->SetExpectedResult(false, i, 4, capture_time); - EXPECT_TRUE(video_encoder_->EncodeVideoFrame(video_frame_, capture_time, - frame_encoded_callback)); - task_runner_->RunTasks(); - } -} - -} // namespace cast -} // namespace media diff --git a/chromium/media/cast/video_sender/video_sender.cc b/chromium/media/cast/video_sender/video_sender.cc index 7391fe8e645..cf050b7f10c 100644 --- a/chromium/media/cast/video_sender/video_sender.cc +++ b/chromium/media/cast/video_sender/video_sender.cc @@ -4,451 +4,395 @@ #include "media/cast/video_sender/video_sender.h" -#include <list> +#include <algorithm> +#include <cstring> #include "base/bind.h" +#include "base/debug/trace_event.h" #include "base/logging.h" #include "base/message_loop/message_loop.h" -#include "crypto/encryptor.h" -#include "crypto/symmetric_key.h" #include "media/cast/cast_defines.h" -#include "media/cast/net/pacing/paced_sender.h" -#include "media/cast/video_sender/video_encoder.h" +#include "media/cast/rtcp/rtcp_defines.h" +#include "media/cast/transport/cast_transport_config.h" +#include "media/cast/video_sender/external_video_encoder.h" +#include "media/cast/video_sender/video_encoder_impl.h" namespace media { namespace cast { -const int64 kMinSchedulingDelayMs = 1; - -class LocalRtcpVideoSenderFeedback : public RtcpSenderFeedback { - public: - explicit LocalRtcpVideoSenderFeedback(VideoSender* video_sender) - : video_sender_(video_sender) { - } - - virtual void OnReceivedCastFeedback( - const RtcpCastMessage& cast_feedback) OVERRIDE { - video_sender_->OnReceivedCastFeedback(cast_feedback); - } - - private: - VideoSender* video_sender_; -}; - -class LocalRtpVideoSenderStatistics : public RtpSenderStatistics { - public: - explicit LocalRtpVideoSenderStatistics(RtpSender* rtp_sender) - : rtp_sender_(rtp_sender) { - } - - virtual void GetStatistics(const base::TimeTicks& now, - RtcpSenderInfo* sender_info) OVERRIDE { - rtp_sender_->RtpStatistics(now, sender_info); - } - - private: - RtpSender* rtp_sender_; -}; +const int kNumAggressiveReportsSentAtStart = 100; +const int kMinSchedulingDelayMs = 1; VideoSender::VideoSender( scoped_refptr<CastEnvironment> cast_environment, const VideoSenderConfig& video_config, - VideoEncoderController* const video_encoder_controller, - PacedPacketSender* const paced_packet_sender) - : rtp_max_delay_( - base::TimeDelta::FromMilliseconds(video_config.rtp_max_delay_ms)), - max_frame_rate_(video_config.max_frame_rate), - cast_environment_(cast_environment), - rtcp_feedback_(new LocalRtcpVideoSenderFeedback(this)), - rtp_sender_(new RtpSender(cast_environment, NULL, &video_config, - paced_packet_sender)), - last_acked_frame_id_(-1), - last_sent_frame_id_(-1), - duplicate_ack_(0), - last_skip_count_(0), + const CreateVideoEncodeAcceleratorCallback& create_vea_cb, + const CreateVideoEncodeMemoryCallback& create_video_encode_mem_cb, + transport::CastTransportSender* const transport_sender) + : cast_environment_(cast_environment), + target_playout_delay_(base::TimeDelta::FromMilliseconds( + video_config.rtp_config.max_delay_ms)), + transport_sender_(transport_sender), + max_unacked_frames_( + std::min(kMaxUnackedFrames, + 1 + static_cast<int>(target_playout_delay_ * + video_config.max_frame_rate / + base::TimeDelta::FromSeconds(1)))), + rtcp_(cast_environment_, + this, + transport_sender_, + NULL, // paced sender. + NULL, + video_config.rtcp_mode, + base::TimeDelta::FromMilliseconds(video_config.rtcp_interval), + video_config.rtp_config.ssrc, + video_config.incoming_feedback_ssrc, + video_config.rtcp_c_name, + VIDEO_EVENT), + rtp_timestamp_helper_(kVideoFrequency), + num_aggressive_rtcp_reports_sent_(0), + frames_in_encoder_(0), + last_sent_frame_id_(0), + latest_acked_frame_id_(0), + duplicate_ack_counter_(0), congestion_control_(cast_environment->Clock(), - video_config.congestion_control_back_off, video_config.max_bitrate, video_config.min_bitrate, - video_config.start_bitrate), - initialized_(false), + max_unacked_frames_), + cast_initialization_status_(STATUS_VIDEO_UNINITIALIZED), weak_factory_(this) { - max_unacked_frames_ = static_cast<uint8>(video_config.rtp_max_delay_ms * - video_config.max_frame_rate / 1000) + 1; - VLOG(1) << "max_unacked_frames " << static_cast<int>(max_unacked_frames_); - DCHECK_GT(max_unacked_frames_, 0) << "Invalid argument"; - - rtp_video_sender_statistics_.reset( - new LocalRtpVideoSenderStatistics(rtp_sender_.get())); + VLOG(1) << "max_unacked_frames " << max_unacked_frames_; + DCHECK_GT(max_unacked_frames_, 0); if (video_config.use_external_encoder) { - DCHECK(video_encoder_controller) << "Invalid argument"; - video_encoder_controller_ = video_encoder_controller; + video_encoder_.reset(new ExternalVideoEncoder(cast_environment, + video_config, + create_vea_cb, + create_video_encode_mem_cb)); } else { - video_encoder_.reset(new VideoEncoder(cast_environment, video_config, - max_unacked_frames_)); - video_encoder_controller_ = video_encoder_.get(); + video_encoder_.reset(new VideoEncoderImpl( + cast_environment, video_config, max_unacked_frames_)); } + cast_initialization_status_ = STATUS_VIDEO_INITIALIZED; - if (video_config.aes_iv_mask.size() == kAesKeySize && - video_config.aes_key.size() == kAesKeySize) { - iv_mask_ = video_config.aes_iv_mask; - crypto::SymmetricKey* key = crypto::SymmetricKey::Import( - crypto::SymmetricKey::AES, video_config.aes_key); - encryptor_.reset(new crypto::Encryptor()); - encryptor_->Init(key, crypto::Encryptor::CTR, std::string()); - } else if (video_config.aes_iv_mask.size() != 0 || - video_config.aes_key.size() != 0) { - DCHECK(false) << "Invalid crypto configuration"; - } + media::cast::transport::CastTransportVideoConfig transport_config; + transport_config.codec = video_config.codec; + transport_config.rtp.config = video_config.rtp_config; + transport_config.rtp.max_outstanding_frames = max_unacked_frames_; + transport_sender_->InitializeVideo(transport_config); - rtcp_.reset(new Rtcp( - cast_environment_, - rtcp_feedback_.get(), - paced_packet_sender, - rtp_video_sender_statistics_.get(), - NULL, - video_config.rtcp_mode, - base::TimeDelta::FromMilliseconds(video_config.rtcp_interval), - video_config.sender_ssrc, - video_config.incoming_feedback_ssrc, - video_config.rtcp_c_name)); -} + rtcp_.SetCastReceiverEventHistorySize(kReceiverRtcpEventHistorySize); -VideoSender::~VideoSender() {} + memset(frame_id_to_rtp_timestamp_, 0, sizeof(frame_id_to_rtp_timestamp_)); +} -void VideoSender::InitializeTimers() { - DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); - if (!initialized_) { - initialized_ = true; - ScheduleNextRtcpReport(); - ScheduleNextResendCheck(); - ScheduleNextSkippedFramesCheck(); - } +VideoSender::~VideoSender() { } void VideoSender::InsertRawVideoFrame( const scoped_refptr<media::VideoFrame>& video_frame, const base::TimeTicks& capture_time) { DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); + if (cast_initialization_status_ != STATUS_VIDEO_INITIALIZED) { + NOTREACHED(); + return; + } DCHECK(video_encoder_.get()) << "Invalid state"; - cast_environment_->Logging()->InsertFrameEvent(kVideoFrameReceived, - GetVideoRtpTimestamp(capture_time), kFrameIdUnknown); - if (!video_encoder_->EncodeVideoFrame(video_frame, capture_time, - base::Bind(&VideoSender::SendEncodedVideoFrameMainThread, - weak_factory_.GetWeakPtr()))) { + RtpTimestamp rtp_timestamp = GetVideoRtpTimestamp(capture_time); + cast_environment_->Logging()->InsertFrameEvent( + capture_time, FRAME_CAPTURE_BEGIN, VIDEO_EVENT, + rtp_timestamp, kFrameIdUnknown); + cast_environment_->Logging()->InsertFrameEvent( + cast_environment_->Clock()->NowTicks(), + FRAME_CAPTURE_END, VIDEO_EVENT, + rtp_timestamp, + kFrameIdUnknown); + + // Used by chrome/browser/extension/api/cast_streaming/performance_test.cc + TRACE_EVENT_INSTANT2( + "cast_perf_test", "InsertRawVideoFrame", + TRACE_EVENT_SCOPE_THREAD, + "timestamp", capture_time.ToInternalValue(), + "rtp_timestamp", rtp_timestamp); + + if (AreTooManyFramesInFlight()) { + VLOG(1) << "Dropping frame due to too many frames currently in-flight."; + return; } -} - -void VideoSender::InsertCodedVideoFrame(const EncodedVideoFrame* encoded_frame, - const base::TimeTicks& capture_time, - const base::Closure callback) { - DCHECK(!video_encoder_.get()) << "Invalid state"; - DCHECK(encoded_frame) << "Invalid argument"; - DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); - - SendEncodedVideoFrame(encoded_frame, capture_time); - cast_environment_->PostTask(CastEnvironment::MAIN, FROM_HERE, callback); -} -void VideoSender::SendEncodedVideoFrameMainThread( - scoped_ptr<EncodedVideoFrame> video_frame, - const base::TimeTicks& capture_time) { - SendEncodedVideoFrame(video_frame.get(), capture_time); -} + uint32 bitrate = congestion_control_.GetBitrate( + capture_time + target_playout_delay_, target_playout_delay_); -bool VideoSender::EncryptVideoFrame(const EncodedVideoFrame& video_frame, - EncodedVideoFrame* encrypted_frame) { - DCHECK(encryptor_) << "Invalid state"; + video_encoder_->SetBitRate(bitrate); - if (!encryptor_->SetCounter(GetAesNonce(video_frame.frame_id, iv_mask_))) { - NOTREACHED() << "Failed to set counter"; - return false; - } - - if (!encryptor_->Encrypt(video_frame.data, &encrypted_frame->data)) { - NOTREACHED() << "Encrypt error"; - return false; + if (video_encoder_->EncodeVideoFrame( + video_frame, + capture_time, + base::Bind(&VideoSender::SendEncodedVideoFrame, + weak_factory_.GetWeakPtr(), + bitrate))) { + frames_in_encoder_++; + } else { + VLOG(1) << "Encoder rejected a frame. Skipping..."; } - encrypted_frame->codec = video_frame.codec; - encrypted_frame->key_frame = video_frame.key_frame; - encrypted_frame->frame_id = video_frame.frame_id; - encrypted_frame->last_referenced_frame_id = - video_frame.last_referenced_frame_id; - return true; } -void VideoSender::SendEncodedVideoFrame(const EncodedVideoFrame* encoded_frame, - const base::TimeTicks& capture_time) { +void VideoSender::SendEncodedVideoFrame( + int requested_bitrate_before_encode, + scoped_ptr<transport::EncodedFrame> encoded_frame) { DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); - last_send_time_ = cast_environment_->Clock()->NowTicks(); - if (encryptor_) { - EncodedVideoFrame encrypted_video_frame; + DCHECK_GT(frames_in_encoder_, 0); + frames_in_encoder_--; - if (!EncryptVideoFrame(*encoded_frame, &encrypted_video_frame)) { - // Logging already done. - return; - } - rtp_sender_->IncomingEncodedVideoFrame(&encrypted_video_frame, - capture_time); - } else { - rtp_sender_->IncomingEncodedVideoFrame(encoded_frame, capture_time); + const uint32 frame_id = encoded_frame->frame_id; + + const bool is_first_frame_to_be_sent = last_send_time_.is_null(); + last_send_time_ = cast_environment_->Clock()->NowTicks(); + last_sent_frame_id_ = frame_id; + // If this is the first frame about to be sent, fake the value of + // |latest_acked_frame_id_| to indicate the receiver starts out all caught up. + // Also, schedule the periodic frame re-send checks. + if (is_first_frame_to_be_sent) { + latest_acked_frame_id_ = frame_id - 1; + ScheduleNextResendCheck(); } - if (encoded_frame->key_frame) { - VLOG(1) << "Send encoded key frame; frame_id:" - << static_cast<int>(encoded_frame->frame_id); + + VLOG_IF(1, encoded_frame->dependency == transport::EncodedFrame::KEY) + << "Send encoded key frame; frame_id: " << frame_id; + + cast_environment_->Logging()->InsertEncodedFrameEvent( + last_send_time_, FRAME_ENCODED, VIDEO_EVENT, encoded_frame->rtp_timestamp, + frame_id, static_cast<int>(encoded_frame->data.size()), + encoded_frame->dependency == transport::EncodedFrame::KEY, + requested_bitrate_before_encode); + // Only use lowest 8 bits as key. + frame_id_to_rtp_timestamp_[frame_id & 0xff] = encoded_frame->rtp_timestamp; + + // Used by chrome/browser/extension/api/cast_streaming/performance_test.cc + TRACE_EVENT_INSTANT1( + "cast_perf_test", "VideoFrameEncoded", + TRACE_EVENT_SCOPE_THREAD, + "rtp_timestamp", encoded_frame->rtp_timestamp); + + DCHECK(!encoded_frame->reference_time.is_null()); + rtp_timestamp_helper_.StoreLatestTime(encoded_frame->reference_time, + encoded_frame->rtp_timestamp); + + // At the start of the session, it's important to send reports before each + // frame so that the receiver can properly compute playout times. The reason + // more than one report is sent is because transmission is not guaranteed, + // only best effort, so send enough that one should almost certainly get + // through. + if (num_aggressive_rtcp_reports_sent_ < kNumAggressiveReportsSentAtStart) { + // SendRtcpReport() will schedule future reports to be made if this is the + // last "aggressive report." + ++num_aggressive_rtcp_reports_sent_; + const bool is_last_aggressive_report = + (num_aggressive_rtcp_reports_sent_ == kNumAggressiveReportsSentAtStart); + VLOG_IF(1, is_last_aggressive_report) << "Sending last aggressive report."; + SendRtcpReport(is_last_aggressive_report); } - last_sent_frame_id_ = static_cast<int>(encoded_frame->frame_id); - UpdateFramesInFlight(); - InitializeTimers(); + + congestion_control_.SendFrameToTransport( + frame_id, encoded_frame->data.size() * 8, last_send_time_); + + transport_sender_->InsertCodedVideoFrame(*encoded_frame); } -void VideoSender::IncomingRtcpPacket(const uint8* packet, size_t length, - const base::Closure callback) { +void VideoSender::IncomingRtcpPacket(scoped_ptr<Packet> packet) { DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); - rtcp_->IncomingRtcpPacket(packet, length); - cast_environment_->PostTask(CastEnvironment::MAIN, FROM_HERE, callback); + rtcp_.IncomingRtcpPacket(&packet->front(), packet->size()); } void VideoSender::ScheduleNextRtcpReport() { DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); - base::TimeDelta time_to_next = rtcp_->TimeToSendNextRtcpReport() - - cast_environment_->Clock()->NowTicks(); - - time_to_next = std::max(time_to_next, - base::TimeDelta::FromMilliseconds(kMinSchedulingDelayMs)); - - cast_environment_->PostDelayedTask(CastEnvironment::MAIN, FROM_HERE, - base::Bind(&VideoSender::SendRtcpReport, weak_factory_.GetWeakPtr()), - time_to_next); + base::TimeDelta time_to_next = rtcp_.TimeToSendNextRtcpReport() - + cast_environment_->Clock()->NowTicks(); + + time_to_next = std::max( + time_to_next, base::TimeDelta::FromMilliseconds(kMinSchedulingDelayMs)); + + cast_environment_->PostDelayedTask( + CastEnvironment::MAIN, + FROM_HERE, + base::Bind(&VideoSender::SendRtcpReport, + weak_factory_.GetWeakPtr(), + true), + time_to_next); } -void VideoSender::SendRtcpReport() { +void VideoSender::SendRtcpReport(bool schedule_future_reports) { DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); - - RtcpSenderLogMessage sender_log_message; - const FrameRawMap& frame_raw_map = - cast_environment_->Logging()->GetFrameRawData(); - - FrameRawMap::const_iterator it = frame_raw_map.begin(); - while (it != frame_raw_map.end()) { - RtcpSenderFrameLogMessage frame_message; - frame_message.rtp_timestamp = it->first; - frame_message.frame_status = kRtcpSenderFrameStatusUnknown; - if (it->second.type.empty()) { - ++it; - continue; - } - CastLoggingEvent last_event = it->second.type.back(); - switch (last_event) { - case kVideoFrameCaptured: - frame_message.frame_status = kRtcpSenderFrameStatusDroppedByFlowControl; - break; - case kVideoFrameSentToEncoder: - frame_message.frame_status = kRtcpSenderFrameStatusDroppedByEncoder; - break; - case kVideoFrameEncoded: - frame_message.frame_status = kRtcpSenderFrameStatusSentToNetwork; - break; - default: - ++it; - continue; - } - ++it; - if (it == frame_raw_map.end()) { - // Last message on our map; only send if it is kVideoFrameEncoded. - if (last_event != kVideoFrameEncoded) { - // For other events we will wait for it to finish and report the result - // in the next report. - break; - } - } - sender_log_message.push_back(frame_message); - } - rtcp_->SendRtcpFromRtpSender(&sender_log_message); - if (!sender_log_message.empty()) { - VLOG(1) << "Failed to send all log messages"; + const base::TimeTicks now = cast_environment_->Clock()->NowTicks(); + uint32 now_as_rtp_timestamp = 0; + if (rtp_timestamp_helper_.GetCurrentTimeAsRtpTimestamp( + now, &now_as_rtp_timestamp)) { + rtcp_.SendRtcpFromRtpSender(now, now_as_rtp_timestamp); + } else { + // |rtp_timestamp_helper_| should have stored a mapping by this point. + NOTREACHED(); } - - // TODO(pwestin): When we start pulling out the logging by other means we need - // to synchronize this. - cast_environment_->Logging()->Reset(); - ScheduleNextRtcpReport(); + if (schedule_future_reports) + ScheduleNextRtcpReport(); } void VideoSender::ScheduleNextResendCheck() { DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); - base::TimeDelta time_to_next; - if (last_send_time_.is_null()) { - time_to_next = rtp_max_delay_; - } else { - time_to_next = last_send_time_ - cast_environment_->Clock()->NowTicks() + - rtp_max_delay_; - } - time_to_next = std::max(time_to_next, - base::TimeDelta::FromMilliseconds(kMinSchedulingDelayMs)); - - cast_environment_->PostDelayedTask(CastEnvironment::MAIN, FROM_HERE, + DCHECK(!last_send_time_.is_null()); + base::TimeDelta time_to_next = + last_send_time_ - cast_environment_->Clock()->NowTicks() + + target_playout_delay_; + time_to_next = std::max( + time_to_next, base::TimeDelta::FromMilliseconds(kMinSchedulingDelayMs)); + cast_environment_->PostDelayedTask( + CastEnvironment::MAIN, + FROM_HERE, base::Bind(&VideoSender::ResendCheck, weak_factory_.GetWeakPtr()), - time_to_next); + time_to_next); } void VideoSender::ResendCheck() { DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); - if (!last_send_time_.is_null() && last_sent_frame_id_ != -1) { - base::TimeDelta time_since_last_send = - cast_environment_->Clock()->NowTicks() - last_send_time_; - if (time_since_last_send > rtp_max_delay_) { - if (last_acked_frame_id_ == -1) { - // We have not received any ack, send a key frame. - video_encoder_controller_->GenerateKeyFrame(); - last_acked_frame_id_ = -1; - last_sent_frame_id_ = -1; - UpdateFramesInFlight(); - } else { - DCHECK_LE(0, last_acked_frame_id_); - - uint32 frame_id = static_cast<uint32>(last_acked_frame_id_ + 1); - VLOG(1) << "ACK timeout resend frame:" << static_cast<int>(frame_id); - ResendFrame(frame_id); - } + DCHECK(!last_send_time_.is_null()); + const base::TimeDelta time_since_last_send = + cast_environment_->Clock()->NowTicks() - last_send_time_; + if (time_since_last_send > target_playout_delay_) { + if (latest_acked_frame_id_ == last_sent_frame_id_) { + // Last frame acked, no point in doing anything + } else { + VLOG(1) << "ACK timeout; last acked frame: " << latest_acked_frame_id_; + ResendForKickstart(); } } ScheduleNextResendCheck(); } -void VideoSender::ScheduleNextSkippedFramesCheck() { - DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); - base::TimeDelta time_to_next; - if (last_checked_skip_count_time_.is_null()) { - time_to_next = - base::TimeDelta::FromMilliseconds(kSkippedFramesCheckPeriodkMs); - } else { - time_to_next = last_checked_skip_count_time_ - - cast_environment_->Clock()->NowTicks() + - base::TimeDelta::FromMilliseconds(kSkippedFramesCheckPeriodkMs); - } - time_to_next = std::max(time_to_next, - base::TimeDelta::FromMilliseconds(kMinSchedulingDelayMs)); - - cast_environment_->PostDelayedTask(CastEnvironment::MAIN, FROM_HERE, - base::Bind(&VideoSender::SkippedFramesCheck, weak_factory_.GetWeakPtr()), - time_to_next); -} - -void VideoSender::SkippedFramesCheck() { - DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); - int skip_count = video_encoder_controller_->NumberOfSkippedFrames(); - if (skip_count - last_skip_count_ > - kSkippedFramesThreshold * max_frame_rate_) { - // TODO(pwestin): Propagate this up to the application. - } - last_skip_count_ = skip_count; - last_checked_skip_count_time_ = cast_environment_->Clock()->NowTicks(); - ScheduleNextSkippedFramesCheck(); -} - void VideoSender::OnReceivedCastFeedback(const RtcpCastMessage& cast_feedback) { DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); + base::TimeDelta rtt; base::TimeDelta avg_rtt; base::TimeDelta min_rtt; base::TimeDelta max_rtt; + if (rtcp_.Rtt(&rtt, &avg_rtt, &min_rtt, &max_rtt)) { + congestion_control_.UpdateRtt(rtt); - if (rtcp_->Rtt(&rtt, &avg_rtt, &min_rtt, &max_rtt)) { - cast_environment_->Logging()->InsertGenericEvent(kRttMs, - rtt.InMilliseconds()); // Don't use a RTT lower than our average. rtt = std::max(rtt, avg_rtt); + + // Having the RTT values implies the receiver sent back a receiver report + // based on it having received a report from here. Therefore, ensure this + // sender stops aggressively sending reports. + if (num_aggressive_rtcp_reports_sent_ < kNumAggressiveReportsSentAtStart) { + VLOG(1) << "No longer a need to send reports aggressively (sent " + << num_aggressive_rtcp_reports_sent_ << ")."; + num_aggressive_rtcp_reports_sent_ = kNumAggressiveReportsSentAtStart; + ScheduleNextRtcpReport(); + } } else { // We have no measured value use default. rtt = base::TimeDelta::FromMilliseconds(kStartRttMs); } + + if (last_send_time_.is_null()) + return; // Cannot get an ACK without having first sent a frame. + if (cast_feedback.missing_frames_and_packets_.empty()) { - // No lost packets. - int resend_frame = -1; - if (last_sent_frame_id_ == -1) return; - - video_encoder_controller_->LatestFrameIdToReference( - cast_feedback.ack_frame_id_); - - if (static_cast<uint32>(last_acked_frame_id_ + 1) == - cast_feedback.ack_frame_id_) { - uint32 new_bitrate = 0; - if (congestion_control_.OnAck(rtt, &new_bitrate)) { - video_encoder_controller_->SetBitRate(new_bitrate); - } - } - if (static_cast<uint32>(last_acked_frame_id_) == cast_feedback.ack_frame_id_ - // We only count duplicate ACKs when we have sent newer frames. - && IsNewerFrameId(last_sent_frame_id_, last_acked_frame_id_)) { - duplicate_ack_++; + video_encoder_->LatestFrameIdToReference(cast_feedback.ack_frame_id_); + + // We only count duplicate ACKs when we have sent newer frames. + if (latest_acked_frame_id_ == cast_feedback.ack_frame_id_ && + latest_acked_frame_id_ != last_sent_frame_id_) { + duplicate_ack_counter_++; } else { - duplicate_ack_ = 0; + duplicate_ack_counter_ = 0; } - if (duplicate_ack_ >= 2 && duplicate_ack_ % 3 == 2) { - // Resend last ACK + 1 frame. - resend_frame = static_cast<uint32>(last_acked_frame_id_ + 1); - } - if (resend_frame != -1) { - DCHECK_LE(0, resend_frame); - VLOG(1) << "Received duplicate ACK for frame:" - << static_cast<int>(resend_frame); - ResendFrame(static_cast<uint32>(resend_frame)); + // TODO(miu): The values "2" and "3" should be derived from configuration. + if (duplicate_ack_counter_ >= 2 && duplicate_ack_counter_ % 3 == 2) { + VLOG(1) << "Received duplicate ACK for frame " << latest_acked_frame_id_; + ResendForKickstart(); } } else { - rtp_sender_->ResendPackets(cast_feedback.missing_frames_and_packets_); - last_send_time_ = cast_environment_->Clock()->NowTicks(); + // Only count duplicated ACKs if there is no NACK request in between. + // This is to avoid aggresive resend. + duplicate_ack_counter_ = 0; - uint32 new_bitrate = 0; - if (congestion_control_.OnNack(rtt, &new_bitrate)) { - video_encoder_controller_->SetBitRate(new_bitrate); - } + // A NACK is also used to cancel pending re-transmissions. + transport_sender_->ResendPackets( + false, cast_feedback.missing_frames_and_packets_, true, rtt); } - ReceivedAck(cast_feedback.ack_frame_id_); -} -void VideoSender::ReceivedAck(uint32 acked_frame_id) { - DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); - last_acked_frame_id_ = static_cast<int>(acked_frame_id); - cast_environment_->Logging()->InsertGenericEvent(kAckReceived, - acked_frame_id); - VLOG(1) << "ReceivedAck:" << static_cast<int>(acked_frame_id); - last_acked_frame_id_ = acked_frame_id; - UpdateFramesInFlight(); + base::TimeTicks now = cast_environment_->Clock()->NowTicks(); + congestion_control_.AckFrame(cast_feedback.ack_frame_id_, now); + + RtpTimestamp rtp_timestamp = + frame_id_to_rtp_timestamp_[cast_feedback.ack_frame_id_ & 0xff]; + cast_environment_->Logging()->InsertFrameEvent(now, + FRAME_ACK_RECEIVED, + VIDEO_EVENT, + rtp_timestamp, + cast_feedback.ack_frame_id_); + + const bool is_acked_out_of_order = + static_cast<int32>(cast_feedback.ack_frame_id_ - + latest_acked_frame_id_) < 0; + VLOG(2) << "Received ACK" << (is_acked_out_of_order ? " out-of-order" : "") + << " for frame " << cast_feedback.ack_frame_id_; + if (!is_acked_out_of_order) { + // Cancel resends of acked frames. + MissingFramesAndPacketsMap missing_frames_and_packets; + PacketIdSet missing; + while (latest_acked_frame_id_ != cast_feedback.ack_frame_id_) { + latest_acked_frame_id_++; + missing_frames_and_packets[latest_acked_frame_id_] = missing; + } + transport_sender_->ResendPackets( + false, missing_frames_and_packets, true, rtt); + latest_acked_frame_id_ = cast_feedback.ack_frame_id_; + } } -void VideoSender::UpdateFramesInFlight() { +bool VideoSender::AreTooManyFramesInFlight() const { DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); - if (last_sent_frame_id_ != -1) { - DCHECK_LE(0, last_sent_frame_id_); - uint32 frames_in_flight; - if (last_acked_frame_id_ != -1) { - DCHECK_LE(0, last_acked_frame_id_); - frames_in_flight = static_cast<uint32>(last_sent_frame_id_) - - static_cast<uint32>(last_acked_frame_id_); - } else { - frames_in_flight = static_cast<uint32>(last_sent_frame_id_) + 1; - } - VLOG(1) << "Frames in flight; last sent: " << last_sent_frame_id_ - << " last acked:" << last_acked_frame_id_; - if (frames_in_flight >= max_unacked_frames_) { - video_encoder_controller_->SkipNextFrame(true); - return; - } + int frames_in_flight = frames_in_encoder_; + if (!last_send_time_.is_null()) { + frames_in_flight += + static_cast<int32>(last_sent_frame_id_ - latest_acked_frame_id_); } - video_encoder_controller_->SkipNextFrame(false); + VLOG(2) << frames_in_flight + << " frames in flight; last sent: " << last_sent_frame_id_ + << " latest acked: " << latest_acked_frame_id_ + << " frames in encoder: " << frames_in_encoder_; + return frames_in_flight >= max_unacked_frames_; } -void VideoSender::ResendFrame(uint32 resend_frame_id) { +void VideoSender::ResendForKickstart() { DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); + DCHECK(!last_send_time_.is_null()); + VLOG(1) << "Resending last packet of frame " << last_sent_frame_id_ + << " to kick-start."; + // Send the first packet of the last encoded frame to kick start + // retransmission. This gives enough information to the receiver what + // packets and frames are missing. MissingFramesAndPacketsMap missing_frames_and_packets; PacketIdSet missing; - missing_frames_and_packets.insert(std::make_pair(resend_frame_id, missing)); - rtp_sender_->ResendPackets(missing_frames_and_packets); + missing.insert(kRtcpCastLastPacket); + missing_frames_and_packets.insert( + std::make_pair(last_sent_frame_id_, missing)); last_send_time_ = cast_environment_->Clock()->NowTicks(); + + base::TimeDelta rtt; + base::TimeDelta avg_rtt; + base::TimeDelta min_rtt; + base::TimeDelta max_rtt; + rtcp_.Rtt(&rtt, &avg_rtt, &min_rtt, &max_rtt); + + // Sending this extra packet is to kick-start the session. There is + // no need to optimize re-transmission for this case. + transport_sender_->ResendPackets(false, missing_frames_and_packets, + false, rtt); } } // namespace cast diff --git a/chromium/media/cast/video_sender/video_sender.gypi b/chromium/media/cast/video_sender/video_sender.gypi deleted file mode 100644 index e91a8c97efe..00000000000 --- a/chromium/media/cast/video_sender/video_sender.gypi +++ /dev/null @@ -1,34 +0,0 @@ -# Copyright 2013 The Chromium Authors. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - -{ - 'includes': [ - 'codecs/vp8/vp8_encoder.gypi', - ], - 'targets': [ - { - 'target_name': 'video_sender', - 'type': 'static_library', - 'include_dirs': [ - '<(DEPTH)/', - '<(DEPTH)/third_party/', - ], - 'sources': [ - 'video_encoder.h', - 'video_encoder.cc', - 'video_sender.h', - 'video_sender.cc', - ], # source - 'dependencies': [ - '<(DEPTH)/crypto/crypto.gyp:crypto', - '<(DEPTH)/media/cast/rtcp/rtcp.gyp:*', - '<(DEPTH)/media/cast/net/rtp_sender/rtp_sender.gyp:*', - '<(DEPTH)/media/media.gyp:media', - '<(DEPTH)/media/media.gyp:shared_memory_support', - 'congestion_control', - 'cast_vp8_encoder', - ], - }, - ], -} diff --git a/chromium/media/cast/video_sender/video_sender.h b/chromium/media/cast/video_sender/video_sender.h index eb7b5ea4f21..cf8d27511c2 100644 --- a/chromium/media/cast/video_sender/video_sender.h +++ b/chromium/media/cast/video_sender/video_sender.h @@ -15,25 +15,22 @@ #include "media/cast/cast_config.h" #include "media/cast/cast_environment.h" #include "media/cast/congestion_control/congestion_control.h" -#include "media/cast/net/rtp_sender/rtp_sender.h" +#include "media/cast/logging/logging_defines.h" #include "media/cast/rtcp/rtcp.h" - -namespace crypto { - class Encryptor; -} +#include "media/cast/rtp_timestamp_helper.h" namespace media { + class VideoFrame; -} -namespace media { namespace cast { -class VideoEncoder; -class LocalRtcpVideoSenderFeedback; -class LocalRtpVideoSenderStatistics; class LocalVideoEncoderCallback; -class PacedPacketSender; +class VideoEncoder; + +namespace transport { +class CastTransportSender; +} // Not thread safe. Only called from the main cast thread. // This class owns all objects related to sending video, objects that create RTP @@ -41,101 +38,139 @@ class PacedPacketSender; // RTCP packets. // Additionally it posts a bunch of delayed tasks to the main thread for various // timeouts. -class VideoSender : public base::NonThreadSafe, +class VideoSender : public RtcpSenderFeedback, + public base::NonThreadSafe, public base::SupportsWeakPtr<VideoSender> { public: VideoSender(scoped_refptr<CastEnvironment> cast_environment, const VideoSenderConfig& video_config, - VideoEncoderController* const video_encoder_controller, - PacedPacketSender* const paced_packet_sender); + const CreateVideoEncodeAcceleratorCallback& create_vea_cb, + const CreateVideoEncodeMemoryCallback& create_video_encode_mem_cb, + transport::CastTransportSender* const transport_sender); virtual ~VideoSender(); - // The video_frame must be valid until the closure callback is called. - // The closure callback is called from the video encoder thread as soon as - // the encoder is done with the frame; it does not mean that the encoded frame - // has been sent out. - void InsertRawVideoFrame( - const scoped_refptr<media::VideoFrame>& video_frame, - const base::TimeTicks& capture_time); - - // The video_frame must be valid until the closure callback is called. - // The closure callback is called from the main thread as soon as - // the cast sender is done with the frame; it does not mean that the encoded - // frame has been sent out. - void InsertCodedVideoFrame(const EncodedVideoFrame* video_frame, - const base::TimeTicks& capture_time, - const base::Closure callback); + CastInitializationStatus InitializationResult() const { + return cast_initialization_status_; + } + + // Note: It is not guaranteed that |video_frame| will actually be encoded and + // sent, if VideoSender detects too many frames in flight. Therefore, clients + // should be careful about the rate at which this method is called. + // + // Note: It is invalid to call this method if InitializationResult() returns + // anything but STATUS_VIDEO_INITIALIZED. + void InsertRawVideoFrame(const scoped_refptr<media::VideoFrame>& video_frame, + const base::TimeTicks& capture_time); // Only called from the main cast thread. - void IncomingRtcpPacket(const uint8* packet, size_t length, - const base::Closure callback); + void IncomingRtcpPacket(scoped_ptr<Packet> packet); protected: // Protected for testability. - void OnReceivedCastFeedback(const RtcpCastMessage& cast_feedback); + virtual void OnReceivedCastFeedback(const RtcpCastMessage& cast_feedback) + OVERRIDE; private: - friend class LocalRtcpVideoSenderFeedback; - - // Schedule when we should send the next RTPC report, - // via a PostDelayedTask to the main cast thread. + // Schedule and execute periodic sending of RTCP report. void ScheduleNextRtcpReport(); - void SendRtcpReport(); - - // Schedule when we should check that we have received an acknowledgment, or a - // loss report from our remote peer. If we have not heard back from our remote - // peer we speculatively resend our oldest unacknowledged frame (the whole - // frame). Note for this to happen we need to lose all pending packets (in - // normal operation 3 full frames), hence this is the last resort to prevent - // us getting stuck after a long outage. + void SendRtcpReport(bool schedule_future_reports); + + // Schedule and execute periodic checks for re-sending packets. If no + // acknowledgements have been received for "too long," VideoSender will + // speculatively re-send certain packets of an unacked frame to kick-start + // re-transmission. This is a last resort tactic to prevent the session from + // getting stuck after a long outage. void ScheduleNextResendCheck(); void ResendCheck(); + void ResendForKickstart(); + + // Returns true if there are too many frames in flight, as defined by the + // configured target playout delay plus simple logic. When this is true, + // InsertRawVideoFrame() will silenty drop frames instead of sending them to + // the video encoder. + bool AreTooManyFramesInFlight() const; + + // Called by the |video_encoder_| with the next EncodeFrame to send. + void SendEncodedVideoFrame(int requested_bitrate_before_encode, + scoped_ptr<transport::EncodedFrame> encoded_frame); + + const scoped_refptr<CastEnvironment> cast_environment_; + + // The total amount of time between a frame's capture/recording on the sender + // and its playback on the receiver (i.e., shown to a user). This is fixed as + // a value large enough to give the system sufficient time to encode, + // transmit/retransmit, receive, decode, and render; given its run-time + // environment (sender/receiver hardware performance, network conditions, + // etc.). + const base::TimeDelta target_playout_delay_; + + // Sends encoded frames over the configured transport (e.g., UDP). In + // Chromium, this could be a proxy that first sends the frames from a renderer + // process to the browser process over IPC, with the browser process being + // responsible for "packetizing" the frames and pushing packets into the + // network layer. + transport::CastTransportSender* const transport_sender_; + + // Maximum number of outstanding frames before the encoding and sending of + // new frames shall halt. + const int max_unacked_frames_; + + // Encodes media::VideoFrame images into EncodedFrames. Per configuration, + // this will point to either the internal software-based encoder or a proxy to + // a hardware-based encoder. + scoped_ptr<VideoEncoder> video_encoder_; - // Monitor how many frames that are silently dropped by the video sender - // per time unit. - void ScheduleNextSkippedFramesCheck(); - void SkippedFramesCheck(); - - void SendEncodedVideoFrame(const EncodedVideoFrame* video_frame, - const base::TimeTicks& capture_time); - void ResendFrame(uint32 resend_frame_id); - void ReceivedAck(uint32 acked_frame_id); - void UpdateFramesInFlight(); - - void SendEncodedVideoFrameMainThread( - scoped_ptr<EncodedVideoFrame> video_frame, - const base::TimeTicks& capture_time); + // Manages sending/receiving of RTCP packets, including sender/receiver + // reports. + Rtcp rtcp_; - void InitializeTimers(); + // Records lip-sync (i.e., mapping of RTP <--> NTP timestamps), and + // extrapolates this mapping to any other point in time. + RtpTimestampHelper rtp_timestamp_helper_; - // Caller must allocate the destination |encrypted_video_frame| the data - // member will be resized to hold the encrypted size. - bool EncryptVideoFrame(const EncodedVideoFrame& encoded_frame, - EncodedVideoFrame* encrypted_video_frame); + // Counts how many RTCP reports are being "aggressively" sent (i.e., one per + // frame) at the start of the session. Once a threshold is reached, RTCP + // reports are instead sent at the configured interval + random drift. + int num_aggressive_rtcp_reports_sent_; - const base::TimeDelta rtp_max_delay_; - const int max_frame_rate_; + // The number of frames currently being processed in |video_encoder_|. + int frames_in_encoder_; - scoped_refptr<CastEnvironment> cast_environment_; - scoped_ptr<LocalRtcpVideoSenderFeedback> rtcp_feedback_; - scoped_ptr<LocalRtpVideoSenderStatistics> rtp_video_sender_statistics_; - scoped_ptr<VideoEncoder> video_encoder_; - scoped_ptr<Rtcp> rtcp_; - scoped_ptr<RtpSender> rtp_sender_; - VideoEncoderController* video_encoder_controller_; - uint8 max_unacked_frames_; - scoped_ptr<crypto::Encryptor> encryptor_; - std::string iv_mask_; - int last_acked_frame_id_; - int last_sent_frame_id_; - int duplicate_ack_; + // This is "null" until the first frame is sent. Thereafter, this tracks the + // last time any frame was sent or re-sent. base::TimeTicks last_send_time_; - base::TimeTicks last_checked_skip_count_time_; - int last_skip_count_; + + // The ID of the last frame sent. Logic throughout VideoSender assumes this + // can safely wrap-around. This member is invalid until + // |!last_send_time_.is_null()|. + uint32 last_sent_frame_id_; + + // The ID of the latest (not necessarily the last) frame that has been + // acknowledged. Logic throughout VideoSender assumes this can safely + // wrap-around. This member is invalid until |!last_send_time_.is_null()|. + uint32 latest_acked_frame_id_; + + // Counts the number of duplicate ACK that are being received. When this + // number reaches a threshold, the sender will take this as a sign that the + // receiver hasn't yet received the first packet of the next frame. In this + // case, VideoSender will trigger a re-send of the next frame. + int duplicate_ack_counter_; + + // When we get close to the max number of un-acked frames, we set lower + // the bitrate drastically to ensure that we catch up. Without this we + // risk getting stuck in a catch-up state forever. CongestionControl congestion_control_; - bool initialized_; + // If this sender is ready for use, this is STATUS_VIDEO_INITIALIZED. + CastInitializationStatus cast_initialization_status_; + + // This is a "good enough" mapping for finding the RTP timestamp associated + // with a video frame. The key is the lowest 8 bits of frame id (which is + // what is sent via RTCP). This map is used for logging purposes. + RtpTimestamp frame_id_to_rtp_timestamp_[256]; + + // NOTE: Weak pointers must be invalidated before all other member variables. base::WeakPtrFactory<VideoSender> weak_factory_; DISALLOW_COPY_AND_ASSIGN(VideoSender); @@ -145,4 +180,3 @@ class VideoSender : public base::NonThreadSafe, } // namespace media #endif // MEDIA_CAST_VIDEO_SENDER_VIDEO_SENDER_H_ - diff --git a/chromium/media/cast/video_sender/video_sender_unittest.cc b/chromium/media/cast/video_sender/video_sender_unittest.cc index c4968415ffb..49fae46c73d 100644 --- a/chromium/media/cast/video_sender/video_sender_unittest.cc +++ b/chromium/media/cast/video_sender/video_sender_unittest.cc @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include <stdint.h> + #include <vector> #include "base/bind.h" @@ -9,11 +11,14 @@ #include "base/test/simple_test_tick_clock.h" #include "media/base/video_frame.h" #include "media/cast/cast_environment.h" -#include "media/cast/net/pacing/mock_paced_packet_sender.h" -#include "media/cast/net/pacing/paced_sender.h" -#include "media/cast/test/fake_task_runner.h" -#include "media/cast/test/video_utility.h" -#include "media/cast/video_sender/mock_video_encoder_controller.h" +#include "media/cast/logging/simple_event_subscriber.h" +#include "media/cast/test/fake_single_thread_task_runner.h" +#include "media/cast/test/fake_video_encode_accelerator.h" +#include "media/cast/test/utility/default_config.h" +#include "media/cast/test/utility/video_utility.h" +#include "media/cast/transport/cast_transport_config.h" +#include "media/cast/transport/cast_transport_sender_impl.h" +#include "media/cast/transport/pacing/paced_sender.h" #include "media/cast/video_sender/video_sender.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" @@ -22,25 +27,94 @@ namespace media { namespace cast { namespace { -static const int64 kStartMillisecond = GG_INT64_C(12345678900000); static const uint8 kPixelValue = 123; static const int kWidth = 320; static const int kHeight = 240; -} using testing::_; using testing::AtLeast; -namespace { -class PeerVideoSender : public VideoSender { +void CreateVideoEncodeAccelerator( + const scoped_refptr<base::SingleThreadTaskRunner>& task_runner, + scoped_ptr<VideoEncodeAccelerator> fake_vea, + const ReceiveVideoEncodeAcceleratorCallback& callback) { + callback.Run(task_runner, fake_vea.Pass()); +} + +void CreateSharedMemory( + size_t size, const ReceiveVideoEncodeMemoryCallback& callback) { + scoped_ptr<base::SharedMemory> shm(new base::SharedMemory()); + if (!shm->CreateAndMapAnonymous(size)) { + NOTREACHED(); + return; + } + callback.Run(shm.Pass()); +} + +class TestPacketSender : public transport::PacketSender { public: - PeerVideoSender(scoped_refptr<CastEnvironment> cast_environment, - const VideoSenderConfig& video_config, - VideoEncoderController* const video_encoder_controller, - PacedPacketSender* const paced_packet_sender) - : VideoSender(cast_environment, video_config, - video_encoder_controller, paced_packet_sender) { + TestPacketSender() + : number_of_rtp_packets_(0), + number_of_rtcp_packets_(0), + paused_(false) {} + + // A singular packet implies a RTCP packet. + virtual bool SendPacket(transport::PacketRef packet, + const base::Closure& cb) OVERRIDE { + if (paused_) { + stored_packet_ = packet; + callback_ = cb; + return false; + } + if (Rtcp::IsRtcpPacket(&packet->data[0], packet->data.size())) { + ++number_of_rtcp_packets_; + } else { + // Check that at least one RTCP packet was sent before the first RTP + // packet. This confirms that the receiver will have the necessary lip + // sync info before it has to calculate the playout time of the first + // frame. + if (number_of_rtp_packets_ == 0) + EXPECT_LE(1, number_of_rtcp_packets_); + ++number_of_rtp_packets_; + } + return true; } + + int number_of_rtp_packets() const { return number_of_rtp_packets_; } + + int number_of_rtcp_packets() const { return number_of_rtcp_packets_; } + + void SetPause(bool paused) { + paused_ = paused; + if (!paused && stored_packet_) { + SendPacket(stored_packet_, callback_); + callback_.Run(); + } + } + + private: + int number_of_rtp_packets_; + int number_of_rtcp_packets_; + bool paused_; + base::Closure callback_; + transport::PacketRef stored_packet_; + + DISALLOW_COPY_AND_ASSIGN(TestPacketSender); +}; + +class PeerVideoSender : public VideoSender { + public: + PeerVideoSender( + scoped_refptr<CastEnvironment> cast_environment, + const VideoSenderConfig& video_config, + const CreateVideoEncodeAcceleratorCallback& create_vea_cb, + const CreateVideoEncodeMemoryCallback& create_video_encode_mem_cb, + transport::CastTransportSender* const transport_sender) + : VideoSender(cast_environment, + video_config, + create_vea_cb, + create_video_encode_mem_cb, + transport_sender) {} using VideoSender::OnReceivedCastFeedback; }; } // namespace @@ -48,17 +122,44 @@ class PeerVideoSender : public VideoSender { class VideoSenderTest : public ::testing::Test { protected: VideoSenderTest() { - testing_clock_.Advance( - base::TimeDelta::FromMilliseconds(kStartMillisecond)); + testing_clock_ = new base::SimpleTestTickClock(); + testing_clock_->Advance(base::TimeTicks::Now() - base::TimeTicks()); + task_runner_ = new test::FakeSingleThreadTaskRunner(testing_clock_); + cast_environment_ = + new CastEnvironment(scoped_ptr<base::TickClock>(testing_clock_).Pass(), + task_runner_, + task_runner_, + task_runner_); + last_pixel_value_ = kPixelValue; + net::IPEndPoint dummy_endpoint; + transport_sender_.reset(new transport::CastTransportSenderImpl( + NULL, + testing_clock_, + dummy_endpoint, + base::Bind(&UpdateCastTransportStatus), + transport::BulkRawEventsCallback(), + base::TimeDelta(), + task_runner_, + &transport_)); } virtual ~VideoSenderTest() {} + virtual void TearDown() OVERRIDE { + video_sender_.reset(); + task_runner_->RunTasks(); + } + + static void UpdateCastTransportStatus(transport::CastTransportStatus status) { + EXPECT_EQ(transport::TRANSPORT_VIDEO_INITIALIZED, status); + } + void InitEncoder(bool external) { VideoSenderConfig video_config; - video_config.sender_ssrc = 1; + video_config.rtp_config.ssrc = 1; video_config.incoming_feedback_ssrc = 2; - video_config.rtp_payload_type = 127; + video_config.rtcp_c_name = "video_test@10.1.1.1"; + video_config.rtp_config.payload_type = 127; video_config.use_external_encoder = external; video_config.width = kWidth; video_config.height = kHeight; @@ -69,111 +170,123 @@ class VideoSenderTest : public ::testing::Test { video_config.min_qp = 0; video_config.max_frame_rate = 30; video_config.max_number_of_video_buffers_used = 1; - video_config.codec = kVp8; + video_config.codec = transport::kVp8; if (external) { - video_sender_.reset(new PeerVideoSender(cast_environment_, - video_config, &mock_video_encoder_controller_, &mock_transport_)); + scoped_ptr<VideoEncodeAccelerator> fake_vea( + new test::FakeVideoEncodeAccelerator(task_runner_)); + video_sender_.reset( + new PeerVideoSender(cast_environment_, + video_config, + base::Bind(&CreateVideoEncodeAccelerator, + task_runner_, + base::Passed(&fake_vea)), + base::Bind(&CreateSharedMemory), + transport_sender_.get())); } else { - video_sender_.reset(new PeerVideoSender(cast_environment_, video_config, - NULL, &mock_transport_)); + video_sender_.reset( + new PeerVideoSender(cast_environment_, + video_config, + CreateDefaultVideoEncodeAcceleratorCallback(), + CreateDefaultVideoEncodeMemoryCallback(), + transport_sender_.get())); } + ASSERT_EQ(STATUS_VIDEO_INITIALIZED, video_sender_->InitializationResult()); } - virtual void SetUp() { - task_runner_ = new test::FakeTaskRunner(&testing_clock_); - cast_environment_ = new CastEnvironment(&testing_clock_, task_runner_, - task_runner_, task_runner_, task_runner_, task_runner_, - GetDefaultCastLoggingConfig()); + scoped_refptr<media::VideoFrame> GetNewVideoFrame() { + gfx::Size size(kWidth, kHeight); + scoped_refptr<media::VideoFrame> video_frame = + media::VideoFrame::CreateFrame( + VideoFrame::I420, size, gfx::Rect(size), size, base::TimeDelta()); + PopulateVideoFrame(video_frame, last_pixel_value_++); + return video_frame; } - scoped_refptr<media::VideoFrame> GetNewVideoFrame() { + scoped_refptr<media::VideoFrame> GetLargeNewVideoFrame() { gfx::Size size(kWidth, kHeight); scoped_refptr<media::VideoFrame> video_frame = - media::VideoFrame::CreateFrame(VideoFrame::I420, size, gfx::Rect(size), - size, base::TimeDelta()); - PopulateVideoFrame(video_frame, kPixelValue); + media::VideoFrame::CreateFrame( + VideoFrame::I420, size, gfx::Rect(size), size, base::TimeDelta()); + PopulateVideoFrameWithNoise(video_frame); return video_frame; } - MockVideoEncoderController mock_video_encoder_controller_; - base::SimpleTestTickClock testing_clock_; - MockPacedPacketSender mock_transport_; - scoped_refptr<test::FakeTaskRunner> task_runner_; + void RunTasks(int during_ms) { + task_runner_->Sleep(base::TimeDelta::FromMilliseconds(during_ms)); + } + + base::SimpleTestTickClock* testing_clock_; // Owned by CastEnvironment. + TestPacketSender transport_; + scoped_ptr<transport::CastTransportSenderImpl> transport_sender_; + scoped_refptr<test::FakeSingleThreadTaskRunner> task_runner_; scoped_ptr<PeerVideoSender> video_sender_; scoped_refptr<CastEnvironment> cast_environment_; + int last_pixel_value_; + + DISALLOW_COPY_AND_ASSIGN(VideoSenderTest); }; TEST_F(VideoSenderTest, BuiltInEncoder) { - EXPECT_CALL(mock_transport_, SendPackets(_)).Times(1); - InitEncoder(false); scoped_refptr<media::VideoFrame> video_frame = GetNewVideoFrame(); - base::TimeTicks capture_time; + const base::TimeTicks capture_time = testing_clock_->NowTicks(); video_sender_->InsertRawVideoFrame(video_frame, capture_time); task_runner_->RunTasks(); + EXPECT_LE(1, transport_.number_of_rtp_packets()); + EXPECT_LE(1, transport_.number_of_rtcp_packets()); } TEST_F(VideoSenderTest, ExternalEncoder) { - EXPECT_CALL(mock_transport_, SendPackets(_)).Times(1); - EXPECT_CALL(mock_video_encoder_controller_, SkipNextFrame(false)).Times(1); InitEncoder(true); + task_runner_->RunTasks(); + + scoped_refptr<media::VideoFrame> video_frame = GetNewVideoFrame(); - EncodedVideoFrame video_frame; - base::TimeTicks capture_time; + const base::TimeTicks capture_time = testing_clock_->NowTicks(); + video_sender_->InsertRawVideoFrame(video_frame, capture_time); - video_frame.codec = kVp8; - video_frame.key_frame = true; - video_frame.frame_id = 0; - video_frame.last_referenced_frame_id = 0; - video_frame.data.insert(video_frame.data.begin(), 1000, kPixelValue); + task_runner_->RunTasks(); - video_sender_->InsertCodedVideoFrame(&video_frame, capture_time, - base::Bind(base::DoNothing)); + // We need to run the task to cleanup the GPU instance. + video_sender_.reset(NULL); + task_runner_->RunTasks(); } TEST_F(VideoSenderTest, RtcpTimer) { - EXPECT_CALL(mock_transport_, SendPackets(_)).Times(AtLeast(1)); - EXPECT_CALL(mock_transport_, SendRtcpPacket(_)).Times(1); - EXPECT_CALL(mock_video_encoder_controller_, - SkipNextFrame(false)).Times(AtLeast(1)); - InitEncoder(true); - - EncodedVideoFrame video_frame; - base::TimeTicks capture_time; + InitEncoder(false); - video_frame.codec = kVp8; - video_frame.key_frame = true; - video_frame.frame_id = 0; - video_frame.last_referenced_frame_id = 0; - video_frame.data.insert(video_frame.data.begin(), 1000, kPixelValue); + scoped_refptr<media::VideoFrame> video_frame = GetNewVideoFrame(); - video_sender_->InsertCodedVideoFrame(&video_frame, capture_time, - base::Bind(base::DoNothing)); + const base::TimeTicks capture_time = testing_clock_->NowTicks(); + video_sender_->InsertRawVideoFrame(video_frame, capture_time); // Make sure that we send at least one RTCP packet. base::TimeDelta max_rtcp_timeout = base::TimeDelta::FromMilliseconds(1 + kDefaultRtcpIntervalMs * 3 / 2); - testing_clock_.Advance(max_rtcp_timeout); - task_runner_->RunTasks(); + RunTasks(max_rtcp_timeout.InMilliseconds()); + EXPECT_LE(1, transport_.number_of_rtp_packets()); + EXPECT_LE(1, transport_.number_of_rtcp_packets()); + // Build Cast msg and expect RTCP packet. + RtcpCastMessage cast_feedback(1); + cast_feedback.media_ssrc_ = 2; + cast_feedback.ack_frame_id_ = 0; + video_sender_->OnReceivedCastFeedback(cast_feedback); + RunTasks(max_rtcp_timeout.InMilliseconds()); + EXPECT_LE(1, transport_.number_of_rtcp_packets()); } TEST_F(VideoSenderTest, ResendTimer) { - EXPECT_CALL(mock_transport_, SendPackets(_)).Times(2); - EXPECT_CALL(mock_transport_, ResendPackets(_)).Times(1); - InitEncoder(false); scoped_refptr<media::VideoFrame> video_frame = GetNewVideoFrame(); - base::TimeTicks capture_time; + const base::TimeTicks capture_time = testing_clock_->NowTicks(); video_sender_->InsertRawVideoFrame(video_frame, capture_time); - task_runner_->RunTasks(); - // ACK the key frame. RtcpCastMessage cast_feedback(1); cast_feedback.media_ssrc_ = 2; @@ -183,16 +296,230 @@ TEST_F(VideoSenderTest, ResendTimer) { video_frame = GetNewVideoFrame(); video_sender_->InsertRawVideoFrame(video_frame, capture_time); - task_runner_->RunTasks(); - base::TimeDelta max_resend_timeout = base::TimeDelta::FromMilliseconds(1 + kDefaultRtpMaxDelayMs); // Make sure that we do a re-send. - testing_clock_.Advance(max_resend_timeout); + RunTasks(max_resend_timeout.InMilliseconds()); + // Should have sent at least 3 packets. + EXPECT_LE( + 3, + transport_.number_of_rtp_packets() + transport_.number_of_rtcp_packets()); +} + +TEST_F(VideoSenderTest, LogAckReceivedEvent) { + InitEncoder(false); + SimpleEventSubscriber event_subscriber; + cast_environment_->Logging()->AddRawEventSubscriber(&event_subscriber); + + int num_frames = 10; + for (int i = 0; i < num_frames; i++) { + scoped_refptr<media::VideoFrame> video_frame = GetNewVideoFrame(); + + const base::TimeTicks capture_time = testing_clock_->NowTicks(); + video_sender_->InsertRawVideoFrame(video_frame, capture_time); + RunTasks(33); + } + task_runner_->RunTasks(); + + RtcpCastMessage cast_feedback(1); + cast_feedback.ack_frame_id_ = num_frames - 1; + + video_sender_->OnReceivedCastFeedback(cast_feedback); + + std::vector<FrameEvent> frame_events; + event_subscriber.GetFrameEventsAndReset(&frame_events); + + ASSERT_TRUE(!frame_events.empty()); + EXPECT_EQ(FRAME_ACK_RECEIVED, frame_events.rbegin()->type); + EXPECT_EQ(VIDEO_EVENT, frame_events.rbegin()->media_type); + EXPECT_EQ(num_frames - 1u, frame_events.rbegin()->frame_id); + + cast_environment_->Logging()->RemoveRawEventSubscriber(&event_subscriber); +} + +TEST_F(VideoSenderTest, StopSendingInTheAbsenceOfAck) { + InitEncoder(false); + // Send a stream of frames and don't ACK; by default we shouldn't have more + // than 4 frames in flight. + scoped_refptr<media::VideoFrame> video_frame = GetNewVideoFrame(); + video_sender_->InsertRawVideoFrame(video_frame, testing_clock_->NowTicks()); + RunTasks(33); + + // Send 3 more frames and record the number of packets sent. + for (int i = 0; i < 3; ++i) { + scoped_refptr<media::VideoFrame> video_frame = GetNewVideoFrame(); + video_sender_->InsertRawVideoFrame(video_frame, testing_clock_->NowTicks()); + RunTasks(33); + } + const int number_of_packets_sent = transport_.number_of_rtp_packets(); + + // Send 3 more frames - they should not be encoded, as we have not received + // any acks. + for (int i = 0; i < 3; ++i) { + scoped_refptr<media::VideoFrame> video_frame = GetNewVideoFrame(); + video_sender_->InsertRawVideoFrame(video_frame, testing_clock_->NowTicks()); + RunTasks(33); + } + + // We expect a frame to be retransmitted because of duplicated ACKs. + // Only one packet of the frame is re-transmitted. + EXPECT_EQ(number_of_packets_sent + 1, + transport_.number_of_rtp_packets()); + + // Start acking and make sure we're back to steady-state. + RtcpCastMessage cast_feedback(1); + cast_feedback.media_ssrc_ = 2; + cast_feedback.ack_frame_id_ = 0; + video_sender_->OnReceivedCastFeedback(cast_feedback); + EXPECT_LE( + 4, + transport_.number_of_rtp_packets() + transport_.number_of_rtcp_packets()); + + // Empty the pipeline. + RunTasks(100); + // Should have sent at least 7 packets. + EXPECT_LE( + 7, + transport_.number_of_rtp_packets() + transport_.number_of_rtcp_packets()); +} + +TEST_F(VideoSenderTest, DuplicateAckRetransmit) { + InitEncoder(false); + scoped_refptr<media::VideoFrame> video_frame = GetNewVideoFrame(); + video_sender_->InsertRawVideoFrame(video_frame, testing_clock_->NowTicks()); + RunTasks(33); + RtcpCastMessage cast_feedback(1); + cast_feedback.media_ssrc_ = 2; + cast_feedback.ack_frame_id_ = 0; + + // Send 3 more frames but don't ACK. + for (int i = 0; i < 3; ++i) { + scoped_refptr<media::VideoFrame> video_frame = GetNewVideoFrame(); + video_sender_->InsertRawVideoFrame(video_frame, testing_clock_->NowTicks()); + RunTasks(33); + } + const int number_of_packets_sent = transport_.number_of_rtp_packets(); + + // Send duplicated ACKs and mix some invalid NACKs. + for (int i = 0; i < 10; ++i) { + RtcpCastMessage ack_feedback(1); + ack_feedback.media_ssrc_ = 2; + ack_feedback.ack_frame_id_ = 0; + RtcpCastMessage nack_feedback(1); + nack_feedback.media_ssrc_ = 2; + nack_feedback.missing_frames_and_packets_[255] = PacketIdSet(); + video_sender_->OnReceivedCastFeedback(ack_feedback); + video_sender_->OnReceivedCastFeedback(nack_feedback); + } + EXPECT_EQ(number_of_packets_sent, transport_.number_of_rtp_packets()); + + // Re-transmit one packet because of duplicated ACKs. + for (int i = 0; i < 3; ++i) { + RtcpCastMessage ack_feedback(1); + ack_feedback.media_ssrc_ = 2; + ack_feedback.ack_frame_id_ = 0; + video_sender_->OnReceivedCastFeedback(ack_feedback); + } + EXPECT_EQ(number_of_packets_sent + 1, transport_.number_of_rtp_packets()); +} + +TEST_F(VideoSenderTest, DuplicateAckRetransmitDoesNotCancelRetransmits) { + InitEncoder(false); + scoped_refptr<media::VideoFrame> video_frame = GetNewVideoFrame(); + video_sender_->InsertRawVideoFrame(video_frame, testing_clock_->NowTicks()); + RunTasks(33); + RtcpCastMessage cast_feedback(1); + cast_feedback.media_ssrc_ = 2; + cast_feedback.ack_frame_id_ = 0; + + // Send 2 more frames but don't ACK. + for (int i = 0; i < 2; ++i) { + scoped_refptr<media::VideoFrame> video_frame = GetNewVideoFrame(); + video_sender_->InsertRawVideoFrame(video_frame, testing_clock_->NowTicks()); + RunTasks(33); + } + // Pause the transport + transport_.SetPause(true); + + // Insert one more video frame. + video_frame = GetLargeNewVideoFrame(); + video_sender_->InsertRawVideoFrame(video_frame, testing_clock_->NowTicks()); + RunTasks(33); + + const int number_of_packets_sent = transport_.number_of_rtp_packets(); + + // Send duplicated ACKs and mix some invalid NACKs. + for (int i = 0; i < 10; ++i) { + RtcpCastMessage ack_feedback(1); + ack_feedback.media_ssrc_ = 2; + ack_feedback.ack_frame_id_ = 0; + RtcpCastMessage nack_feedback(1); + nack_feedback.media_ssrc_ = 2; + nack_feedback.missing_frames_and_packets_[255] = PacketIdSet(); + video_sender_->OnReceivedCastFeedback(ack_feedback); + video_sender_->OnReceivedCastFeedback(nack_feedback); + } + EXPECT_EQ(number_of_packets_sent, transport_.number_of_rtp_packets()); + + // Re-transmit one packet because of duplicated ACKs. + for (int i = 0; i < 3; ++i) { + RtcpCastMessage ack_feedback(1); + ack_feedback.media_ssrc_ = 2; + ack_feedback.ack_frame_id_ = 0; + video_sender_->OnReceivedCastFeedback(ack_feedback); + } + + transport_.SetPause(false); + RunTasks(100); + EXPECT_LT(number_of_packets_sent + 1, transport_.number_of_rtp_packets()); +} + +TEST_F(VideoSenderTest, AcksCancelRetransmits) { + InitEncoder(false); + transport_.SetPause(true); + scoped_refptr<media::VideoFrame> video_frame = GetLargeNewVideoFrame(); + video_sender_->InsertRawVideoFrame(video_frame, testing_clock_->NowTicks()); + RunTasks(33); + + // Frame should be in buffer, waiting. Now let's ack it. + RtcpCastMessage cast_feedback(1); + cast_feedback.media_ssrc_ = 2; + cast_feedback.ack_frame_id_ = 0; + video_sender_->OnReceivedCastFeedback(cast_feedback); + + transport_.SetPause(false); + RunTasks(33); + EXPECT_EQ(0, transport_.number_of_rtp_packets()); +} + +TEST_F(VideoSenderTest, NAcksCancelRetransmits) { + InitEncoder(false); + transport_.SetPause(true); + // Send two video frames. + scoped_refptr<media::VideoFrame> video_frame = GetLargeNewVideoFrame(); + video_sender_->InsertRawVideoFrame(video_frame, testing_clock_->NowTicks()); + RunTasks(33); + video_frame = GetLargeNewVideoFrame(); + video_sender_->InsertRawVideoFrame(video_frame, testing_clock_->NowTicks()); + RunTasks(33); + + // Frames should be in buffer, waiting. Now let's ack the first one and nack + // one packet in the second one. + RtcpCastMessage cast_feedback(1); + cast_feedback.media_ssrc_ = 2; + cast_feedback.ack_frame_id_ = 0; + PacketIdSet missing_packets; + missing_packets.insert(0); + cast_feedback.missing_frames_and_packets_[1] = missing_packets; + video_sender_->OnReceivedCastFeedback(cast_feedback); + + transport_.SetPause(false); + RunTasks(33); + // Only one packet should be retransmitted. + EXPECT_EQ(1, transport_.number_of_rtp_packets()); } } // namespace cast } // namespace media - |