diff options
author | Torne (Richard Coles) <torne@google.com> | 2014-08-28 12:05:23 +0100 |
---|---|---|
committer | Torne (Richard Coles) <torne@google.com> | 2014-08-28 12:05:23 +0100 |
commit | 03b57e008b61dfcb1fbad3aea950ae0e001748b0 (patch) | |
tree | 9a740c1a5fbe659ec83484b67cbc679332f5a408 /media | |
parent | ca7d0c81aa30d24514c34c963f43cd24da34a2bf (diff) | |
download | chromium_org-03b57e008b61dfcb1fbad3aea950ae0e001748b0.tar.gz |
Merge from Chromium at DEPS revision 291560
This commit was generated by merge_to_master.py.
Change-Id: Ic58269055810d51286b4109e59b90b6856887a30
Diffstat (limited to 'media')
103 files changed, 2472 insertions, 2813 deletions
diff --git a/media/audio/audio_input_controller.cc b/media/audio/audio_input_controller.cc index bcd9c7281e..c49cb34f34 100644 --- a/media/audio/audio_input_controller.cc +++ b/media/audio/audio_input_controller.cc @@ -8,6 +8,7 @@ #include "base/strings/stringprintf.h" #include "base/threading/thread_restrictions.h" #include "base/time/time.h" +#include "media/audio/audio_parameters.h" #include "media/base/limits.h" #include "media/base/scoped_histogram_timer.h" #include "media/base/user_input_monitor.h" @@ -85,6 +86,7 @@ AudioInputController::AudioInputController(EventHandler* handler, max_volume_(0.0), user_input_monitor_(user_input_monitor), #if defined(AUDIO_POWER_MONITORING) + log_silence_state_(false), silence_state_(SILENCE_STATE_NO_MEASUREMENT), #endif prev_key_down_count_(0) { @@ -150,7 +152,7 @@ scoped_refptr<AudioInputController> AudioInputController::CreateLowLatency( // Create and open a new audio input stream from the existing // audio-device thread. Use the provided audio-input device. if (!controller->task_runner_->PostTask(FROM_HERE, - base::Bind(&AudioInputController::DoCreate, controller, + base::Bind(&AudioInputController::DoCreateForLowLatency, controller, base::Unretained(audio_manager), params, device_id))) { controller = NULL; } @@ -238,6 +240,21 @@ void AudioInputController::DoCreate(AudioManager* audio_manager, DoCreateForStream(audio_manager->MakeAudioInputStream(params, device_id)); } +void AudioInputController::DoCreateForLowLatency(AudioManager* audio_manager, + const AudioParameters& params, + const std::string& device_id) { + DCHECK(task_runner_->BelongsToCurrentThread()); + +#if defined(AUDIO_POWER_MONITORING) + // We only log silence state UMA stats for low latency mode and if we use a + // real device. + if (params.format() != AudioParameters::AUDIO_FAKE) + log_silence_state_ = true; +#endif + + DoCreate(audio_manager, params, device_id); +} + void AudioInputController::DoCreateForStream( AudioInputStream* stream_to_control) { DCHECK(task_runner_->BelongsToCurrentThread()); @@ -329,10 +346,10 @@ void AudioInputController::DoClose() { user_input_monitor_->DisableKeyPressMonitoring(); #if defined(AUDIO_POWER_MONITORING) - // Send UMA stats if we have enabled power monitoring. - if (audio_level_) { + // Send UMA stats if enabled. + if (log_silence_state_) LogSilenceState(silence_state_); - } + log_silence_state_ = false; #endif state_ = CLOSED; @@ -503,26 +520,11 @@ void AudioInputController::DoLogAudioLevel(float level_dbfs) { std::string log_string = base::StringPrintf( "AIC::OnData: average audio level=%.2f dBFS", level_dbfs); static const float kSilenceThresholdDBFS = -72.24719896f; - if (level_dbfs < kSilenceThresholdDBFS) { + if (level_dbfs < kSilenceThresholdDBFS) log_string += " <=> no audio input!"; - if (silence_state_ == SILENCE_STATE_NO_MEASUREMENT) - silence_state_ = SILENCE_STATE_ONLY_SILENCE; - else if (silence_state_ == SILENCE_STATE_ONLY_AUDIO) - silence_state_ = SILENCE_STATE_AUDIO_AND_SILENCE; - else - DCHECK(silence_state_ == SILENCE_STATE_ONLY_SILENCE || - silence_state_ == SILENCE_STATE_AUDIO_AND_SILENCE); - } else { - if (silence_state_ == SILENCE_STATE_NO_MEASUREMENT) - silence_state_ = SILENCE_STATE_ONLY_AUDIO; - else if (silence_state_ == SILENCE_STATE_ONLY_SILENCE) - silence_state_ = SILENCE_STATE_AUDIO_AND_SILENCE; - else - DCHECK(silence_state_ == SILENCE_STATE_ONLY_AUDIO || - silence_state_ == SILENCE_STATE_AUDIO_AND_SILENCE); - } - handler_->OnLog(this, log_string); + + UpdateSilenceState(level_dbfs < kSilenceThresholdDBFS); #endif } @@ -555,6 +557,28 @@ bool AudioInputController::GetDataIsActive() { } #if defined(AUDIO_POWER_MONITORING) +void AudioInputController::UpdateSilenceState(bool silence) { + if (silence) { + if (silence_state_ == SILENCE_STATE_NO_MEASUREMENT) { + silence_state_ = SILENCE_STATE_ONLY_SILENCE; + } else if (silence_state_ == SILENCE_STATE_ONLY_AUDIO) { + silence_state_ = SILENCE_STATE_AUDIO_AND_SILENCE; + } else { + DCHECK(silence_state_ == SILENCE_STATE_ONLY_SILENCE || + silence_state_ == SILENCE_STATE_AUDIO_AND_SILENCE); + } + } else { + if (silence_state_ == SILENCE_STATE_NO_MEASUREMENT) { + silence_state_ = SILENCE_STATE_ONLY_AUDIO; + } else if (silence_state_ == SILENCE_STATE_ONLY_SILENCE) { + silence_state_ = SILENCE_STATE_AUDIO_AND_SILENCE; + } else { + DCHECK(silence_state_ == SILENCE_STATE_ONLY_AUDIO || + silence_state_ == SILENCE_STATE_AUDIO_AND_SILENCE); + } + } +} + void AudioInputController::LogSilenceState(SilenceState value) { UMA_HISTOGRAM_ENUMERATION("Media.AudioInputControllerSessionSilenceReport", value, diff --git a/media/audio/audio_input_controller.h b/media/audio/audio_input_controller.h index e1e14ee3c3..5d555766a1 100644 --- a/media/audio/audio_input_controller.h +++ b/media/audio/audio_input_controller.h @@ -191,11 +191,11 @@ class MEDIA_EXPORT AudioInputController SyncWriter* sync_writer, UserInputMonitor* user_input_monitor); - // Factory method for creating an AudioInputController for low-latency mode, - // taking ownership of |stream|. The stream will be opened on the audio - // thread, and when that is done, the event handler will receive an - // OnCreated() call from that same thread. |user_input_monitor| is used for - // typing detection and can be NULL. + // Factory method for creating an AudioInputController with an existing + // |stream| for low-latency mode, taking ownership of |stream|. The stream + // will be opened on the audio thread, and when that is done, the event + // handler will receive an OnCreated() call from that same thread. + // |user_input_monitor| is used for typing detection and can be NULL. static scoped_refptr<AudioInputController> CreateForStream( const scoped_refptr<base::SingleThreadTaskRunner>& task_runner, EventHandler* event_handler, @@ -251,6 +251,12 @@ class MEDIA_EXPORT AudioInputController // Elements in this enum should not be deleted or rearranged; the only // permitted operation is to add new elements before SILENCE_STATE_MAX and // update SILENCE_STATE_MAX. + // Possible silence state transitions: + // SILENCE_STATE_AUDIO_AND_SILENCE + // ^ ^ + // SILENCE_STATE_ONLY_AUDIO SILENCE_STATE_ONLY_SILENCE + // ^ ^ + // SILENCE_STATE_NO_MEASUREMENT enum SilenceState { SILENCE_STATE_NO_MEASUREMENT = 0, SILENCE_STATE_ONLY_AUDIO = 1, @@ -268,6 +274,9 @@ class MEDIA_EXPORT AudioInputController // Methods called on the audio thread (owned by the AudioManager). void DoCreate(AudioManager* audio_manager, const AudioParameters& params, const std::string& device_id); + void DoCreateForLowLatency(AudioManager* audio_manager, + const AudioParameters& params, + const std::string& device_id); void DoCreateForStream(AudioInputStream* stream_to_control); void DoRecord(); void DoClose(); @@ -292,6 +301,11 @@ class MEDIA_EXPORT AudioInputController bool GetDataIsActive(); #if defined(AUDIO_POWER_MONITORING) + // Updates the silence state, see enum SilenceState above for state + // transitions. + void UpdateSilenceState(bool silence); + + // Logs the silence state as UMA stat. void LogSilenceState(SilenceState value); #endif @@ -345,6 +359,9 @@ class MEDIA_EXPORT AudioInputController media::AudioParameters audio_params_; base::TimeTicks last_audio_level_log_time_; + // Whether the silence state should sent as UMA stat. + bool log_silence_state_; + // The silence report sent as UMA stat at the end of a session. SilenceState silence_state_; #endif diff --git a/media/audio/pulse/pulse_input.cc b/media/audio/pulse/pulse_input.cc index 4976b5610e..6c9855e504 100644 --- a/media/audio/pulse/pulse_input.cc +++ b/media/audio/pulse/pulse_input.cc @@ -281,7 +281,10 @@ void PulseAudioInputStream::ReadData() { hardware_delay += fifo_.GetAvailableFrames() * params_.GetBytesPerFrame(); callback_->OnData(this, audio_bus, hardware_delay, normalized_volume); - base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(5)); + // Sleep 5ms to wait until render consumes the data in order to avoid + // back to back OnData() method. + if (fifo_.available_blocks()) + base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(5)); } pa_threaded_mainloop_signal(pa_mainloop_, 0); diff --git a/media/base/android/BUILD.gn b/media/base/android/BUILD.gn index a1b999935c..5df4dc35e0 100644 --- a/media/base/android/BUILD.gn +++ b/media/base/android/BUILD.gn @@ -45,7 +45,10 @@ source_set("android") { "webaudio_media_codec_info.h", ] configs += [ "//media:media_config" ] - deps = [ ":media_jni_headers" ] + deps = [ + ":media_jni_headers", + "//third_party/widevine/cdm:version_h", + ] } source_set("unittests") { @@ -59,6 +62,7 @@ source_set("unittests") { "//media/base:test_support", "//testing/gmock", "//testing/gtest", + "//third_party/widevine/cdm:version_h", ] configs += [ "//media:media_config" ] } diff --git a/media/base/demuxer_perftest.cc b/media/base/demuxer_perftest.cc index f5a11526b2..ad6736302f 100644 --- a/media/base/demuxer_perftest.cc +++ b/media/base/demuxer_perftest.cc @@ -17,7 +17,7 @@ namespace media { -static const int kBenchmarkIterations = 500; +static const int kBenchmarkIterations = 100; class DemuxerHostImpl : public media::DemuxerHost { public: diff --git a/media/base/video_frame.cc b/media/base/video_frame.cc index 456cf6e9d8..6b3427db1a 100644 --- a/media/base/video_frame.cc +++ b/media/base/video_frame.cc @@ -689,19 +689,20 @@ VideoFrame::~VideoFrame() { base::ResetAndReturn(&no_longer_needed_cb_).Run(); } -bool VideoFrame::IsValidPlane(size_t plane) const { - return (plane < NumPlanes(format_)); +// static +bool VideoFrame::IsValidPlane(size_t plane, VideoFrame::Format format) { + return (plane < NumPlanes(format)); } int VideoFrame::stride(size_t plane) const { - DCHECK(IsValidPlane(plane)); + DCHECK(IsValidPlane(plane, format_)); return strides_[plane]; } -int VideoFrame::row_bytes(size_t plane) const { - DCHECK(IsValidPlane(plane)); - int width = coded_size_.width(); - switch (format_) { +// static +int VideoFrame::RowBytes(size_t plane, VideoFrame::Format format, int width) { + DCHECK(IsValidPlane(plane, format)); + switch (format) { case VideoFrame::YV24: switch (plane) { case kYPlane: @@ -754,13 +755,17 @@ int VideoFrame::row_bytes(size_t plane) const { case VideoFrame::NATIVE_TEXTURE: break; } - NOTREACHED() << "Unsupported video frame format/plane: " - << format_ << "/" << plane; + NOTREACHED() << "Unsupported video frame format/plane: " << format << "/" + << plane; return 0; } +int VideoFrame::row_bytes(size_t plane) const { + return RowBytes(plane, format_, coded_size_.width()); +} + int VideoFrame::rows(size_t plane) const { - DCHECK(IsValidPlane(plane)); + DCHECK(IsValidPlane(plane, format_)); int height = coded_size_.height(); switch (format_) { case VideoFrame::YV24: @@ -822,7 +827,7 @@ int VideoFrame::rows(size_t plane) const { } uint8* VideoFrame::data(size_t plane) const { - DCHECK(IsValidPlane(plane)); + DCHECK(IsValidPlane(plane, format_)); return data_[plane]; } @@ -854,7 +859,7 @@ int VideoFrame::dmabuf_fd(size_t plane) const { void VideoFrame::HashFrameForTesting(base::MD5Context* context) { for (int plane = 0; plane < kMaxPlanes; ++plane) { - if (!IsValidPlane(plane)) + if (!IsValidPlane(plane, format_)) break; for (int row = 0; row < rows(plane); ++row) { base::MD5Update(context, base::StringPiece( diff --git a/media/base/video_frame.h b/media/base/video_frame.h index 5939c45211..9ad9a3d937 100644 --- a/media/base/video_frame.h +++ b/media/base/video_frame.h @@ -219,6 +219,10 @@ class MEDIA_EXPORT VideoFrame : public base::RefCountedThreadSafe<VideoFrame> { // Returns horizontal bits per pixel for given |plane| and |format|. static int PlaneHorizontalBitsPerPixel(Format format, size_t plane); + // Returns the number of bytes per row for the given plane, format, and width. + // The width may be aligned to format requirements. + static int RowBytes(size_t plane, Format format, int width); + Format format() const { return format_; } const gfx::Size& coded_size() const { return coded_size_; } @@ -284,6 +288,11 @@ class MEDIA_EXPORT VideoFrame : public base::RefCountedThreadSafe<VideoFrame> { private: friend class base::RefCountedThreadSafe<VideoFrame>; + + // Returns true if |plane| is a valid plane number for the given format. This + // can be used to DCHECK() plane parameters. + static bool IsValidPlane(size_t plane, VideoFrame::Format format); + // Clients must use the static CreateFrame() method to create a new frame. VideoFrame(Format format, const gfx::Size& coded_size, @@ -296,9 +305,6 @@ class MEDIA_EXPORT VideoFrame : public base::RefCountedThreadSafe<VideoFrame> { void AllocateYUV(); - // Used to DCHECK() plane parameters. - bool IsValidPlane(size_t plane) const; - // Frame format. const Format format_; diff --git a/media/cast/BUILD.gn b/media/cast/BUILD.gn index 8d38089dae..e1c9e66330 100644 --- a/media/cast/BUILD.gn +++ b/media/cast/BUILD.gn @@ -72,8 +72,6 @@ source_set("net") { "net/rtcp/rtcp_defines.h", "net/rtcp/rtcp.h", "net/rtcp/rtcp.cc", - "net/rtcp/rtcp_receiver.cc", - "net/rtcp/rtcp_receiver.h", "net/rtcp/rtcp_sender.cc", "net/rtcp/rtcp_sender.h", "net/rtcp/rtcp_utility.cc", @@ -237,11 +235,9 @@ test("cast_unittests") { "net/pacing/mock_paced_packet_sender.cc", "net/pacing/mock_paced_packet_sender.h", "net/pacing/paced_sender_unittest.cc", - "net/rtcp/mock_rtcp_receiver_feedback.cc", - "net/rtcp/mock_rtcp_receiver_feedback.h", - "net/rtcp/rtcp_receiver_unittest.cc", "net/rtcp/rtcp_sender_unittest.cc", "net/rtcp/rtcp_unittest.cc", + "net/rtcp/rtcp_utility_unittest.cc", "net/rtcp/receiver_rtcp_event_subscriber_unittest.cc", # TODO(miu): The following two are test utility modules. Rename/move the files. "net/rtcp/test_rtcp_packet_builder.cc", diff --git a/media/cast/cast.gyp b/media/cast/cast.gyp index 303748a54f..2f83f75cf2 100644 --- a/media/cast/cast.gyp +++ b/media/cast/cast.gyp @@ -192,8 +192,6 @@ 'net/rtcp/rtcp_defines.h', 'net/rtcp/rtcp.h', 'net/rtcp/rtcp.cc', - 'net/rtcp/rtcp_receiver.cc', - 'net/rtcp/rtcp_receiver.h', 'net/rtcp/rtcp_sender.cc', 'net/rtcp/rtcp_sender.h', 'net/rtcp/rtcp_utility.cc', diff --git a/media/cast/cast_defines.h b/media/cast/cast_defines.h index c02fa2bfa7..faafddb885 100644 --- a/media/cast/cast_defines.h +++ b/media/cast/cast_defines.h @@ -27,7 +27,10 @@ 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 = 60; +// This value is carefully choosen such that it fits in the 8-bits range for +// frame IDs. It is also less than half of the full 8-bits range such that we +// can handle wrap around and compare two frame IDs. +const int kMaxUnackedFrames = 120; const int kStartRttMs = 20; const int64 kCastMessageUpdateIntervalMs = 33; diff --git a/media/cast/cast_sender.h b/media/cast/cast_sender.h index c9bad7aa1e..7615c28602 100644 --- a/media/cast/cast_sender.h +++ b/media/cast/cast_sender.h @@ -86,6 +86,11 @@ class CastSender { const CastInitializationCallback& cast_initialization_cb, const CreateVideoEncodeAcceleratorCallback& create_vea_cb, const CreateVideoEncodeMemoryCallback& create_video_encode_mem_cb) = 0; + + // Change the target delay. This is only valid if the receiver + // supports the "adaptive_target_delay" rtp extension. + virtual void SetTargetPlayoutDelay( + base::TimeDelta new_target_playout_delay) = 0; }; } // namespace cast diff --git a/media/cast/cast_sender_impl.cc b/media/cast/cast_sender_impl.cc index c684858775..19a054f513 100644 --- a/media/cast/cast_sender_impl.cc +++ b/media/cast/cast_sender_impl.cc @@ -9,7 +9,6 @@ #include "base/logging.h" #include "base/message_loop/message_loop.h" #include "media/base/video_frame.h" -#include "media/cast/net/rtcp/rtcp_receiver.h" namespace media { namespace cast { @@ -111,6 +110,10 @@ void CastSenderImpl::InitializeAudio( new LocalAudioFrameInput(cast_environment_, audio_sender_->AsWeakPtr()); } cast_initialization_cb.Run(status); + if (video_sender_) { + DCHECK(audio_sender_->GetTargetPlayoutDelay() == + video_sender_->GetTargetPlayoutDelay()); + } } void CastSenderImpl::InitializeVideo( @@ -137,6 +140,10 @@ void CastSenderImpl::InitializeVideo( new LocalVideoFrameInput(cast_environment_, video_sender_->AsWeakPtr()); } cast_initialization_cb.Run(status); + if (audio_sender_) { + DCHECK(audio_sender_->GetTargetPlayoutDelay() == + video_sender_->GetTargetPlayoutDelay()); + } } CastSenderImpl::~CastSenderImpl() { @@ -151,5 +158,17 @@ scoped_refptr<VideoFrameInput> CastSenderImpl::video_frame_input() { return video_frame_input_; } +void CastSenderImpl::SetTargetPlayoutDelay( + base::TimeDelta new_target_playout_delay) { + VLOG(1) << "CastSenderImpl@" << this << "::SetTargetPlayoutDelay(" + << new_target_playout_delay.InMilliseconds() << " ms)"; + if (audio_sender_) { + audio_sender_->SetTargetPlayoutDelay(new_target_playout_delay); + } + if (video_sender_) { + video_sender_->SetTargetPlayoutDelay(new_target_playout_delay); + } +} + } // namespace cast } // namespace media diff --git a/media/cast/cast_sender_impl.h b/media/cast/cast_sender_impl.h index c34fccf0b1..c4680abf16 100644 --- a/media/cast/cast_sender_impl.h +++ b/media/cast/cast_sender_impl.h @@ -37,6 +37,9 @@ class CastSenderImpl : public CastSender { const CreateVideoEncodeMemoryCallback& create_video_encode_mem_cb) OVERRIDE; + virtual void SetTargetPlayoutDelay( + base::TimeDelta new_target_playout_delay) OVERRIDE; + virtual ~CastSenderImpl(); virtual scoped_refptr<AudioFrameInput> audio_frame_input() OVERRIDE; diff --git a/media/cast/cast_testing.gypi b/media/cast/cast_testing.gypi index 21585c81ab..ef12af72f2 100644 --- a/media/cast/cast_testing.gypi +++ b/media/cast/cast_testing.gypi @@ -86,11 +86,9 @@ 'net/pacing/mock_paced_packet_sender.cc', 'net/pacing/mock_paced_packet_sender.h', 'net/pacing/paced_sender_unittest.cc', - 'net/rtcp/mock_rtcp_receiver_feedback.cc', - 'net/rtcp/mock_rtcp_receiver_feedback.h', - 'net/rtcp/rtcp_receiver_unittest.cc', 'net/rtcp/rtcp_sender_unittest.cc', 'net/rtcp/rtcp_unittest.cc', + 'net/rtcp/rtcp_utility_unittest.cc', 'net/rtcp/receiver_rtcp_event_subscriber_unittest.cc', # TODO(miu): The following two are test utility modules. Rename/move the files. 'net/rtcp/test_rtcp_packet_builder.cc', diff --git a/media/cast/net/cast_transport_config.cc b/media/cast/net/cast_transport_config.cc index a607f6fcb4..b6c9a07aa6 100644 --- a/media/cast/net/cast_transport_config.cc +++ b/media/cast/net/cast_transport_config.cc @@ -16,7 +16,8 @@ EncodedFrame::EncodedFrame() : dependency(UNKNOWN_DEPENDENCY), frame_id(0), referenced_frame_id(0), - rtp_timestamp(0) {} + rtp_timestamp(0), + new_playout_delay_ms(0) {} EncodedFrame::~EncodedFrame() {} diff --git a/media/cast/net/cast_transport_config.h b/media/cast/net/cast_transport_config.h index fa0f8f2ac8..dcfd133bd0 100644 --- a/media/cast/net/cast_transport_config.h +++ b/media/cast/net/cast_transport_config.h @@ -117,6 +117,10 @@ struct EncodedFrame { // timestamps; and it may not necessarily increment with precise regularity. base::TimeTicks reference_time; + // Playout delay for this and all future frames. Used by the Adaptive + // Playout delay extension. Zero means no change. + uint16 new_playout_delay_ms; + // The encoded signal data. std::string data; }; @@ -135,6 +139,10 @@ class PacketSender { // 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; + + // Returns the number of bytes ever sent. + virtual int64 GetBytesSent() = 0; + virtual ~PacketSender() {} }; diff --git a/media/cast/net/cast_transport_defines.h b/media/cast/net/cast_transport_defines.h index 63407aa7ba..f7d681c7ad 100644 --- a/media/cast/net/cast_transport_defines.h +++ b/media/cast/net/cast_transport_defines.h @@ -47,15 +47,6 @@ enum RtcpPacketFields { kPacketTypeHigh = 210, // Port Mapping. }; -enum RtcpPacketField { - kRtcpSr = 0x0002, - kRtcpRr = 0x0004, - kRtcpDlrr = 0x0400, - kRtcpRrtr = 0x0800, - kRtcpCast = 0x20000, - kRtcpReceiverLog = 0x80000, - }; - // Each uint16 represents one packet id within a cast frame. typedef std::set<uint16> PacketIdSet; // Each uint8 represents one cast frame. diff --git a/media/cast/net/cast_transport_sender.h b/media/cast/net/cast_transport_sender.h index 31166f549d..9c75d12275 100644 --- a/media/cast/net/cast_transport_sender.h +++ b/media/cast/net/cast_transport_sender.h @@ -85,19 +85,15 @@ class CastTransportSender : public base::NonThreadSafe { base::TimeTicks current_time, uint32 current_time_as_rtp_timestamp) = 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; + // Cancels sending packets for the frames in the set. + // |ssrc| is the SSRC for the stream. + // |frame_ids| contains the IDs of the frames that will be cancelled. + virtual void CancelSendingFrames(uint32 ssrc, + const std::vector<uint32>& frame_ids) = 0; + + // Resends a frame or part of a frame to kickstart. This is used when the + // stream appears to be stalled. + virtual void ResendFrameForKickstart(uint32 ssrc, uint32 frame_id) = 0; // Returns a callback for receiving packets for testing purposes. virtual PacketReceiverCallback PacketReceiverForTesting(); diff --git a/media/cast/net/cast_transport_sender_impl.cc b/media/cast/net/cast_transport_sender_impl.cc index 248c69d6bb..478f75a39b 100644 --- a/media/cast/net/cast_transport_sender_impl.cc +++ b/media/cast/net/cast_transport_sender_impl.cc @@ -60,6 +60,7 @@ CastTransportSenderImpl::CastTransportSenderImpl( transport_task_runner), raw_events_callback_(raw_events_callback), raw_events_callback_interval_(raw_events_callback_interval), + last_byte_acked_for_audio_(0), weak_factory_(this) { DCHECK(clock_); if (!raw_events_callback_.is_null()) { @@ -111,7 +112,9 @@ void CastTransportSenderImpl::InitializeAudio( } audio_rtcp_session_.reset( - new Rtcp(cast_message_cb, + new Rtcp(base::Bind(&CastTransportSenderImpl::OnReceivedCastMessage, + weak_factory_.GetWeakPtr(), config.ssrc, + cast_message_cb), rtt_cb, base::Bind(&CastTransportSenderImpl::OnReceivedLogMessage, weak_factory_.GetWeakPtr(), AUDIO_EVENT), @@ -142,7 +145,9 @@ void CastTransportSenderImpl::InitializeVideo( } video_rtcp_session_.reset( - new Rtcp(cast_message_cb, + new Rtcp(base::Bind(&CastTransportSenderImpl::OnReceivedCastMessage, + weak_factory_.GetWeakPtr(), config.ssrc, + cast_message_cb), rtt_cb, base::Bind(&CastTransportSenderImpl::OnReceivedLogMessage, weak_factory_.GetWeakPtr(), VIDEO_EVENT), @@ -202,21 +207,48 @@ void CastTransportSenderImpl::SendSenderReport( } } +void CastTransportSenderImpl::CancelSendingFrames( + uint32 ssrc, + const std::vector<uint32>& frame_ids) { + if (audio_sender_ && ssrc == audio_sender_->ssrc()) { + audio_sender_->CancelSendingFrames(frame_ids); + } else if (video_sender_ && ssrc == video_sender_->ssrc()) { + video_sender_->CancelSendingFrames(frame_ids); + } else { + NOTREACHED() << "Invalid request for cancel sending."; + } +} + +void CastTransportSenderImpl::ResendFrameForKickstart(uint32 ssrc, + uint32 frame_id) { + if (audio_sender_ && ssrc == audio_sender_->ssrc()) { + DCHECK(audio_rtcp_session_); + audio_sender_->ResendFrameForKickstart(frame_id, + audio_rtcp_session_->rtt()); + } else if (video_sender_ && ssrc == video_sender_->ssrc()) { + DCHECK(video_rtcp_session_); + video_sender_->ResendFrameForKickstart(frame_id, + video_rtcp_session_->rtt()); + } else { + NOTREACHED() << "Invalid request for kickstart."; + } +} + void CastTransportSenderImpl::ResendPackets( - bool is_audio, + uint32 ssrc, const MissingFramesAndPacketsMap& missing_packets, bool cancel_rtx_if_not_in_list, - base::TimeDelta dedupe_window) { - if (is_audio) { - DCHECK(audio_sender_) << "Audio sender uninitialized"; + const DedupInfo& dedup_info) { + if (audio_sender_ && ssrc == audio_sender_->ssrc()) { audio_sender_->ResendPackets(missing_packets, cancel_rtx_if_not_in_list, - dedupe_window); - } else { - DCHECK(video_sender_) << "Video sender uninitialized"; + dedup_info); + } else if (video_sender_ && ssrc == video_sender_->ssrc()) { video_sender_->ResendPackets(missing_packets, cancel_rtx_if_not_in_list, - dedupe_window); + dedup_info); + } else { + NOTREACHED() << "Invalid request for retransmission."; } } @@ -293,5 +325,41 @@ void CastTransportSenderImpl::OnReceivedLogMessage( } } +void CastTransportSenderImpl::OnReceivedCastMessage( + uint32 ssrc, + const RtcpCastMessageCallback& cast_message_cb, + const RtcpCastMessage& cast_message) { + if (!cast_message_cb.is_null()) + cast_message_cb.Run(cast_message); + + DedupInfo dedup_info; + if (audio_sender_ && audio_sender_->ssrc() == ssrc) { + const int64 acked_bytes = + audio_sender_->GetLastByteSentForFrame(cast_message.ack_frame_id); + last_byte_acked_for_audio_ = + std::max(acked_bytes, last_byte_acked_for_audio_); + } else if (video_sender_ && video_sender_->ssrc() == ssrc) { + dedup_info.resend_interval = video_rtcp_session_->rtt(); + + // Only use audio stream to dedup if there is one. + if (audio_sender_) { + dedup_info.last_byte_acked_for_audio = last_byte_acked_for_audio_; + } + } + + if (cast_message.missing_frames_and_packets.empty()) + return; + + // This call does two things. + // 1. Specifies that retransmissions for packets not listed in the set are + // cancelled. + // 2. Specifies a deduplication window. For video this would be the most + // recent RTT. For audio there is no deduplication. + ResendPackets(ssrc, + cast_message.missing_frames_and_packets, + true, + dedup_info); +} + } // namespace cast } // namespace media diff --git a/media/cast/net/cast_transport_sender_impl.h b/media/cast/net/cast_transport_sender_impl.h index cee845223c..a9c92f7f47 100644 --- a/media/cast/net/cast_transport_sender_impl.h +++ b/media/cast/net/cast_transport_sender_impl.h @@ -25,6 +25,7 @@ #define MEDIA_CAST_NET_CAST_TRANSPORT_IMPL_H_ #include "base/callback.h" +#include "base/gtest_prod_util.h" #include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" #include "base/memory/weak_ptr.h" @@ -79,15 +80,30 @@ class CastTransportSenderImpl : public CastTransportSender { base::TimeTicks current_time, uint32 current_time_as_rtp_timestamp) OVERRIDE; - virtual void ResendPackets(bool is_audio, - const MissingFramesAndPacketsMap& missing_packets, - bool cancel_rtx_if_not_in_list, - base::TimeDelta dedupe_window) - OVERRIDE; + virtual void CancelSendingFrames( + uint32 ssrc, + const std::vector<uint32>& frame_ids) OVERRIDE; + + virtual void ResendFrameForKickstart(uint32 ssrc, uint32 frame_id) OVERRIDE; virtual PacketReceiverCallback PacketReceiverForTesting() OVERRIDE; private: + FRIEND_TEST_ALL_PREFIXES(CastTransportSenderImplTest, NacksCancelRetransmits); + FRIEND_TEST_ALL_PREFIXES(CastTransportSenderImplTest, CancelRetransmits); + FRIEND_TEST_ALL_PREFIXES(CastTransportSenderImplTest, Kickstart); + FRIEND_TEST_ALL_PREFIXES(CastTransportSenderImplTest, + DedupRetransmissionWithAudio); + + // Resend packets for the stream identified by |ssrc|. + // If |cancel_rtx_if_not_in_list| is true then transmission of packets for the + // frames but not in the list will be dropped. + // See PacedSender::ResendPackets() to see how |dedup_info| works. + void ResendPackets(uint32 ssrc, + const MissingFramesAndPacketsMap& missing_packets, + bool cancel_rtx_if_not_in_list, + const DedupInfo& dedup_info); + // If |raw_events_callback_| is non-null, calls it with events collected // by |event_subscriber_| since last call. void SendRawEvents(); @@ -99,6 +115,11 @@ class CastTransportSenderImpl : public CastTransportSender { void OnReceivedLogMessage(EventMediaType media_type, const RtcpReceiverLogMessage& log); + // Called when a RTCP Cast message is received. + void OnReceivedCastMessage(uint32 ssrc, + const RtcpCastMessageCallback& cast_message_cb, + const RtcpCastMessage& cast_message); + base::TickClock* clock_; // Not owned by this class. CastTransportStatusCallback status_callback_; scoped_refptr<base::SingleThreadTaskRunner> transport_task_runner_; @@ -132,6 +153,11 @@ class CastTransportSenderImpl : public CastTransportSender { BulkRawEventsCallback raw_events_callback_; base::TimeDelta raw_events_callback_interval_; + // Right after a frame is sent we record the number of bytes sent to the + // socket. We record the corresponding bytes sent for the most recent ACKed + // audio packet. + int64 last_byte_acked_for_audio_; + base::WeakPtrFactory<CastTransportSenderImpl> weak_factory_; DISALLOW_COPY_AND_ASSIGN(CastTransportSenderImpl); diff --git a/media/cast/net/cast_transport_sender_impl_unittest.cc b/media/cast/net/cast_transport_sender_impl_unittest.cc index b8a49cce1f..1cbdf5293a 100644 --- a/media/cast/net/cast_transport_sender_impl_unittest.cc +++ b/media/cast/net/cast_transport_sender_impl_unittest.cc @@ -19,15 +19,50 @@ namespace media { namespace cast { -static const int64 kStartMillisecond = INT64_C(12345678900000); +namespace { +const int64 kStartMillisecond = INT64_C(12345678900000); +const uint32 kVideoSsrc = 1; +const uint32 kAudioSsrc = 2; +} // namespace class FakePacketSender : public PacketSender { public: - FakePacketSender() {} + FakePacketSender() + : paused_(false), packets_sent_(0), bytes_sent_(0) {} virtual bool SendPacket(PacketRef packet, const base::Closure& cb) OVERRIDE { + if (paused_) { + stored_packet_ = packet; + callback_ = cb; + return false; + } + ++packets_sent_; + bytes_sent_ += packet->data.size(); return true; } + + virtual int64 GetBytesSent() OVERRIDE { + return bytes_sent_; + } + + void SetPaused(bool paused) { + paused_ = paused; + if (!paused && stored_packet_) { + SendPacket(stored_packet_, callback_); + callback_.Run(); + } + } + + int packets_sent() const { return packets_sent_; } + + private: + bool paused_; + base::Closure callback_; + PacketRef stored_packet_; + int packets_sent_; + int64 bytes_sent_; + + DISALLOW_COPY_AND_ASSIGN(FakePacketSender); }; class CastTransportSenderImplTest : public ::testing::Test { @@ -68,6 +103,28 @@ class CastTransportSenderImplTest : public ::testing::Test { task_runner_->RunTasks(); } + void InitializeVideo() { + CastTransportRtpConfig rtp_config; + rtp_config.ssrc = kVideoSsrc; + rtp_config.feedback_ssrc = 2; + rtp_config.rtp_payload_type = 3; + rtp_config.stored_frames = 10; + transport_sender_->InitializeVideo(rtp_config, + RtcpCastMessageCallback(), + RtcpRttCallback()); + } + + void InitializeAudio() { + CastTransportRtpConfig rtp_config; + rtp_config.ssrc = kAudioSsrc; + rtp_config.feedback_ssrc = 3; + rtp_config.rtp_payload_type = 4; + rtp_config.stored_frames = 10; + transport_sender_->InitializeAudio(rtp_config, + RtcpCastMessageCallback(), + RtcpRttCallback()); + } + void LogRawEvents(const std::vector<PacketEvent>& packet_events, const std::vector<FrameEvent>& frame_events) { num_times_callback_called_++; @@ -95,5 +152,199 @@ TEST_F(CastTransportSenderImplTest, InitWithLogging) { EXPECT_EQ(5, num_times_callback_called_); } +TEST_F(CastTransportSenderImplTest, NacksCancelRetransmits) { + InitWithoutLogging(); + InitializeVideo(); + task_runner_->Sleep(base::TimeDelta::FromMilliseconds(50)); + + // A fake frame that will be decomposed into 4 packets. + EncodedFrame fake_frame; + fake_frame.frame_id = 1; + fake_frame.rtp_timestamp = 1; + fake_frame.dependency = EncodedFrame::KEY; + fake_frame.data.resize(5000, ' '); + + transport_sender_->InsertCodedVideoFrame(fake_frame); + task_runner_->Sleep(base::TimeDelta::FromMilliseconds(10)); + EXPECT_EQ(4, transport_.packets_sent()); + + // Resend packet 0. + MissingFramesAndPacketsMap missing_packets; + missing_packets[1].insert(0); + missing_packets[1].insert(1); + missing_packets[1].insert(2); + + transport_.SetPaused(true); + DedupInfo dedup_info; + dedup_info.resend_interval = base::TimeDelta::FromMilliseconds(10); + transport_sender_->ResendPackets( + kVideoSsrc, missing_packets, true, dedup_info); + + task_runner_->Sleep(base::TimeDelta::FromMilliseconds(10)); + + RtcpCastMessage cast_message; + cast_message.media_ssrc = kVideoSsrc; + cast_message.ack_frame_id = 1; + cast_message.missing_frames_and_packets[1].insert(3); + transport_sender_->OnReceivedCastMessage(kVideoSsrc, + RtcpCastMessageCallback(), + cast_message); + transport_.SetPaused(false); + task_runner_->Sleep(base::TimeDelta::FromMilliseconds(10)); + + // Resend one packet in the socket when unpaused. + // Resend one more packet from NACK. + EXPECT_EQ(6, transport_.packets_sent()); +} + +TEST_F(CastTransportSenderImplTest, CancelRetransmits) { + InitWithoutLogging(); + InitializeVideo(); + task_runner_->Sleep(base::TimeDelta::FromMilliseconds(50)); + + // A fake frame that will be decomposed into 4 packets. + EncodedFrame fake_frame; + fake_frame.frame_id = 1; + fake_frame.rtp_timestamp = 1; + fake_frame.dependency = EncodedFrame::KEY; + fake_frame.data.resize(5000, ' '); + + transport_sender_->InsertCodedVideoFrame(fake_frame); + task_runner_->Sleep(base::TimeDelta::FromMilliseconds(10)); + EXPECT_EQ(4, transport_.packets_sent()); + + // Resend all packets for frame 1. + MissingFramesAndPacketsMap missing_packets; + missing_packets[1].insert(kRtcpCastAllPacketsLost); + + transport_.SetPaused(true); + DedupInfo dedup_info; + dedup_info.resend_interval = base::TimeDelta::FromMilliseconds(10); + transport_sender_->ResendPackets( + kVideoSsrc, missing_packets, true, dedup_info); + + task_runner_->Sleep(base::TimeDelta::FromMilliseconds(10)); + std::vector<uint32> cancel_sending_frames; + cancel_sending_frames.push_back(1); + transport_sender_->CancelSendingFrames(kVideoSsrc, + cancel_sending_frames); + transport_.SetPaused(false); + task_runner_->Sleep(base::TimeDelta::FromMilliseconds(10)); + + // Resend one packet in the socket when unpaused. + EXPECT_EQ(5, transport_.packets_sent()); +} + +TEST_F(CastTransportSenderImplTest, Kickstart) { + InitWithoutLogging(); + InitializeVideo(); + task_runner_->Sleep(base::TimeDelta::FromMilliseconds(50)); + + // A fake frame that will be decomposed into 4 packets. + EncodedFrame fake_frame; + fake_frame.frame_id = 1; + fake_frame.rtp_timestamp = 1; + fake_frame.dependency = EncodedFrame::KEY; + fake_frame.data.resize(5000, ' '); + + transport_.SetPaused(true); + transport_sender_->InsertCodedVideoFrame(fake_frame); + transport_sender_->ResendFrameForKickstart(kVideoSsrc, 1); + transport_.SetPaused(false); + task_runner_->Sleep(base::TimeDelta::FromMilliseconds(10)); + EXPECT_EQ(4, transport_.packets_sent()); + + // Resend 2 packets for frame 1. + MissingFramesAndPacketsMap missing_packets; + missing_packets[1].insert(0); + missing_packets[1].insert(1); + + transport_.SetPaused(true); + DedupInfo dedup_info; + dedup_info.resend_interval = base::TimeDelta::FromMilliseconds(10); + transport_sender_->ResendPackets( + kVideoSsrc, missing_packets, true, dedup_info); + transport_sender_->ResendFrameForKickstart(kVideoSsrc, 1); + transport_.SetPaused(false); + task_runner_->Sleep(base::TimeDelta::FromMilliseconds(10)); + + // Resend one packet in the socket when unpaused. + // Two more retransmission packets sent. + EXPECT_EQ(7, transport_.packets_sent()); +} + +TEST_F(CastTransportSenderImplTest, DedupRetransmissionWithAudio) { + InitWithoutLogging(); + InitializeAudio(); + InitializeVideo(); + task_runner_->Sleep(base::TimeDelta::FromMilliseconds(50)); + + // Send two audio frames. + EncodedFrame fake_audio; + fake_audio.frame_id = 1; + fake_audio.reference_time = testing_clock_.NowTicks(); + fake_audio.dependency = EncodedFrame::KEY; + fake_audio.data.resize(100, ' '); + transport_sender_->InsertCodedAudioFrame(fake_audio); + task_runner_->Sleep(base::TimeDelta::FromMilliseconds(2)); + fake_audio.frame_id = 2; + fake_audio.reference_time = testing_clock_.NowTicks(); + transport_sender_->InsertCodedAudioFrame(fake_audio); + task_runner_->Sleep(base::TimeDelta::FromMilliseconds(2)); + EXPECT_EQ(2, transport_.packets_sent()); + + // Ack the first audio frame. + RtcpCastMessage cast_message; + cast_message.media_ssrc = kAudioSsrc; + cast_message.ack_frame_id = 1; + transport_sender_->OnReceivedCastMessage(kAudioSsrc, + RtcpCastMessageCallback(), + cast_message); + task_runner_->RunTasks(); + EXPECT_EQ(2, transport_.packets_sent()); + + // Send a fake video frame that will be decomposed into 4 packets. + EncodedFrame fake_video; + fake_video.frame_id = 1; + fake_video.dependency = EncodedFrame::KEY; + fake_video.data.resize(5000, ' '); + transport_sender_->InsertCodedVideoFrame(fake_video); + task_runner_->RunTasks(); + EXPECT_EQ(6, transport_.packets_sent()); + + // Retransmission is reject because audio is not acked yet. + cast_message.media_ssrc = kVideoSsrc; + cast_message.ack_frame_id = 0; + cast_message.missing_frames_and_packets[1].insert(3); + task_runner_->Sleep(base::TimeDelta::FromMilliseconds(10)); + transport_sender_->OnReceivedCastMessage(kVideoSsrc, + RtcpCastMessageCallback(), + cast_message); + task_runner_->RunTasks(); + EXPECT_EQ(6, transport_.packets_sent()); + + // Ack the second audio frame. + cast_message.media_ssrc = kAudioSsrc; + cast_message.ack_frame_id = 2; + cast_message.missing_frames_and_packets.clear(); + task_runner_->Sleep(base::TimeDelta::FromMilliseconds(2)); + transport_sender_->OnReceivedCastMessage(kAudioSsrc, + RtcpCastMessageCallback(), + cast_message); + task_runner_->RunTasks(); + EXPECT_EQ(6, transport_.packets_sent()); + + // Retransmission of video packet now accepted. + cast_message.media_ssrc = kVideoSsrc; + cast_message.ack_frame_id = 1; + cast_message.missing_frames_and_packets[1].insert(3); + task_runner_->Sleep(base::TimeDelta::FromMilliseconds(2)); + transport_sender_->OnReceivedCastMessage(kVideoSsrc, + RtcpCastMessageCallback(), + cast_message); + task_runner_->RunTasks(); + EXPECT_EQ(7, transport_.packets_sent()); +} + } // namespace cast } // namespace media diff --git a/media/cast/net/pacing/mock_paced_packet_sender.h b/media/cast/net/pacing/mock_paced_packet_sender.h index 2f7f1b2650..1920ef23ae 100644 --- a/media/cast/net/pacing/mock_paced_packet_sender.h +++ b/media/cast/net/pacing/mock_paced_packet_sender.h @@ -18,7 +18,7 @@ class MockPacedPacketSender : public PacedPacketSender { MOCK_METHOD1(SendPackets, bool(const SendPacketVector& packets)); MOCK_METHOD2(ResendPackets, bool(const SendPacketVector& packets, - base::TimeDelta dedupe_window)); + const DedupInfo& dedup_info)); MOCK_METHOD2(SendRtcpPacket, bool(unsigned int ssrc, PacketRef packet)); MOCK_METHOD1(CancelSendingPacket, void(const PacketKey& packet_key)); }; diff --git a/media/cast/net/pacing/paced_sender.cc b/media/cast/net/pacing/paced_sender.cc index 3677da736d..b83dc0f1c8 100644 --- a/media/cast/net/pacing/paced_sender.cc +++ b/media/cast/net/pacing/paced_sender.cc @@ -22,8 +22,15 @@ static const size_t kTargetBurstSize = 10; static const size_t kMaxBurstSize = 20; static const size_t kMaxDedupeWindowMs = 500; +// Number of packets that we keep the information of sent time and sent bytes. +// This number allows 0.5 seconds of history if sending at maximum rate. +static const size_t kPacketHistorySize = + kMaxBurstSize * kMaxDedupeWindowMs / kPacingIntervalMs; + } // namespace +DedupInfo::DedupInfo() : last_byte_acked_for_audio(0) {} + // static PacketKey PacedPacketSender::MakePacketKey(const base::TimeTicks& ticks, uint32 ssrc, @@ -31,6 +38,9 @@ PacketKey PacedPacketSender::MakePacketKey(const base::TimeTicks& ticks, return std::make_pair(ticks, std::make_pair(ssrc, packet_id)); } +PacedSender::PacketSendRecord::PacketSendRecord() + : last_byte_sent(0), last_byte_sent_for_audio(0) {} + PacedSender::PacedSender( base::TickClock* clock, LoggingImpl* logging, @@ -64,6 +74,20 @@ void PacedSender::RegisterPrioritySsrc(uint32 ssrc) { priority_ssrcs_.push_back(ssrc); } +int64 PacedSender::GetLastByteSentForPacket(const PacketKey& packet_key) { + PacketSendHistory::const_iterator it = send_history_.find(packet_key); + if (it == send_history_.end()) + return 0; + return it->second.last_byte_sent; +} + +int64 PacedSender::GetLastByteSentForSsrc(uint32 ssrc) { + std::map<uint32, int64>::const_iterator it = last_byte_sent_.find(ssrc); + if (it == last_byte_sent_.end()) + return 0; + return it->second; +} + bool PacedSender::SendPackets(const SendPacketVector& packets) { if (packets.empty()) { return true; @@ -85,18 +109,42 @@ bool PacedSender::SendPackets(const SendPacketVector& packets) { return true; } +bool PacedSender::ShouldResend(const PacketKey& packet_key, + const DedupInfo& dedup_info, + const base::TimeTicks& now) { + PacketSendHistory::const_iterator it = send_history_.find(packet_key); + + // No history of previous transmission. It might be sent too long ago. + if (it == send_history_.end()) + return true; + + // Suppose there is request to retransmit X and there is an audio + // packet Y sent just before X. Reject retransmission of X if ACK for + // Y has not been received. + // Only do this for video packets. + if (packet_key.second.first == video_ssrc_) { + if (dedup_info.last_byte_acked_for_audio && + it->second.last_byte_sent_for_audio && + dedup_info.last_byte_acked_for_audio < + it->second.last_byte_sent_for_audio) { + return false; + } + } + // Retransmission interval has to be greater than |resend_interval|. + if (now - it->second.time < dedup_info.resend_interval) + return false; + return true; +} + bool PacedSender::ResendPackets(const SendPacketVector& packets, - base::TimeDelta dedupe_window) { + const DedupInfo& dedup_info) { if (packets.empty()) { return true; } const bool high_priority = IsHighPriority(packets.begin()->first); - base::TimeTicks now = clock_->NowTicks(); + const 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) { + if (!ShouldResend(packets[i].first, dedup_info, now)) { LogPacketEvent(packets[i].second->data, PACKET_RTX_REJECTED); continue; } @@ -225,8 +273,8 @@ void PacedSender::SendStoredPackets() { PacketType packet_type; PacketKey packet_key; PacketRef packet = PopNextPacket(&packet_type, &packet_key); - sent_time_[packet_key] = now; - sent_time_buffer_[packet_key] = now; + PacketSendRecord send_record; + send_record.time = now; switch (packet_type) { case PacketType_Resend: @@ -238,20 +286,29 @@ void PacedSender::SendStoredPackets() { case PacketType_RTCP: break; } - if (!transport_->SendPacket(packet, cb)) { + + const bool socket_blocked = !transport_->SendPacket(packet, cb); + + // Save the send record. + send_record.last_byte_sent = transport_->GetBytesSent(); + send_record.last_byte_sent_for_audio = GetLastByteSentForSsrc(audio_ssrc_); + send_history_[packet_key] = send_record; + send_history_buffer_[packet_key] = send_record; + last_byte_sent_[packet_key.second.first] = send_record.last_byte_sent; + + if (socket_blocked) { 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(); + + // Keep ~0.5 seconds of data (1000 packets). + if (send_history_buffer_.size() >= kPacketHistorySize) { + send_history_.swap(send_history_buffer_); + send_history_buffer_.clear(); } - DCHECK_LE(sent_time_buffer_.size(), - kMaxBurstSize * kMaxDedupeWindowMs / kPacingIntervalMs); + DCHECK_LE(send_history_buffer_.size(), kPacketHistorySize); state_ = State_Unblocked; } diff --git a/media/cast/net/pacing/paced_sender.h b/media/cast/net/pacing/paced_sender.h index 094e5299e1..8e5a60366a 100644 --- a/media/cast/net/pacing/paced_sender.h +++ b/media/cast/net/pacing/paced_sender.h @@ -35,12 +35,32 @@ class LoggingImpl; typedef std::pair<base::TimeTicks, std::pair<uint32, uint16> > PacketKey; typedef std::vector<std::pair<PacketKey, PacketRef> > SendPacketVector; +// Information used to deduplicate retransmission packets. +// There are two criteria for deduplication. +// +// 1. Using another muxed stream. +// Suppose there are multiple streams muxed and sent via the same +// socket. When there is a retransmission request for packet X, we +// will reject the retransmission if there is a packet sent from +// another stream just before X but not acked. Typically audio stream +// is used for this purpose. |last_byte_acked_for_audio| provides this +// information. +// +// 2. Using a time interval. +// Time between sending the same packet must be greater than +// |resend_interval|. +struct DedupInfo { + DedupInfo(); + base::TimeDelta resend_interval; + int64 last_byte_acked_for_audio; +}; + // 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; + const DedupInfo& dedup_info) = 0; virtual bool SendRtcpPacket(uint32 ssrc, PacketRef packet) = 0; virtual void CancelSendingPacket(const PacketKey& packet_key) = 0; @@ -75,10 +95,19 @@ class PacedSender : public PacedPacketSender, // Because IsHigherPriority() is determined in linear time. void RegisterPrioritySsrc(uint32 ssrc); + // Returns the total number of bytes sent to the socket when the specified + // packet was just sent. + // Returns 0 if the packet cannot be found or not yet sent. + int64 GetLastByteSentForPacket(const PacketKey& packet_key); + + // Returns the total number of bytes sent to the socket when the last payload + // identified by SSRC is just sent. + int64 GetLastByteSentForSsrc(uint32 ssrc); + // PacedPacketSender implementation. virtual bool SendPackets(const SendPacketVector& packets) OVERRIDE; virtual bool ResendPackets(const SendPacketVector& packets, - base::TimeDelta dedupe_window) OVERRIDE; + const DedupInfo& dedup_info) OVERRIDE; virtual bool SendRtcpPacket(uint32 ssrc, PacketRef packet) OVERRIDE; virtual void CancelSendingPacket(const PacketKey& packet_key) OVERRIDE; @@ -87,6 +116,14 @@ class PacedSender : public PacedPacketSender, void SendStoredPackets(); void LogPacketEvent(const Packet& packet, CastLoggingEvent event); + // Returns true if retransmission for packet indexed by |packet_key| is + // accepted. |dedup_info| contains information to help deduplicate + // retransmission. |now| is the current time to save on fetching it from the + // clock multiple times. + bool ShouldResend(const PacketKey& packet_key, + const DedupInfo& dedup_info, + const base::TimeTicks& now); + enum PacketType { PacketType_RTCP, PacketType_Resend, @@ -133,8 +170,20 @@ class PacedSender : public PacedPacketSender, typedef std::map<PacketKey, std::pair<PacketType, PacketRef> > PacketList; PacketList packet_list_; PacketList priority_packet_list_; - std::map<PacketKey, base::TimeTicks> sent_time_; - std::map<PacketKey, base::TimeTicks> sent_time_buffer_; + + struct PacketSendRecord { + PacketSendRecord(); + base::TimeTicks time; // Time when the packet was sent. + int64 last_byte_sent; // Number of bytes sent to network just after this + // packet was sent. + int64 last_byte_sent_for_audio; // Number of bytes sent to network from + // audio stream just before this packet. + }; + typedef std::map<PacketKey, PacketSendRecord> PacketSendHistory; + PacketSendHistory send_history_; + PacketSendHistory send_history_buffer_; + // Records the last byte sent for payload with a specific SSRC. + std::map<uint32, int64> last_byte_sent_; // Maximum burst size for the next three bursts. size_t max_burst_size_; diff --git a/media/cast/net/pacing/paced_sender_unittest.cc b/media/cast/net/pacing/paced_sender_unittest.cc index 43e7603385..68b8c06fcc 100644 --- a/media/cast/net/pacing/paced_sender_unittest.cc +++ b/media/cast/net/pacing/paced_sender_unittest.cc @@ -30,16 +30,21 @@ static const uint32 kAudioSsrc = 0x5678; class TestPacketSender : public PacketSender { public: - TestPacketSender() {} + TestPacketSender() : bytes_sent_(0) {} 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()); + bytes_sent_ += packet->data.size(); return true; } + virtual int64 GetBytesSent() OVERRIDE { + return bytes_sent_; + } + 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); @@ -48,6 +53,7 @@ class TestPacketSender : public PacketSender { public: std::list<int> expected_packet_size_; + int64 bytes_sent_; DISALLOW_COPY_AND_ASSIGN(TestPacketSender); }; @@ -132,7 +138,7 @@ TEST_F(PacedSenderTest, PassThroughRtcp) { SendPacketVector packets = CreateSendPacketVector(kSize1, 1, true); EXPECT_TRUE(paced_sender_->SendPackets(packets)); - EXPECT_TRUE(paced_sender_->ResendPackets(packets, base::TimeDelta())); + EXPECT_TRUE(paced_sender_->ResendPackets(packets, DedupInfo())); mock_transport_.AddExpectedSize(kSize2, 1); Packet tmp(kSize2, kValue); @@ -205,7 +211,7 @@ TEST_F(PacedSenderTest, PaceWithNack) { EXPECT_TRUE(paced_sender_->SendPackets(first_frame_packets)); // Add first NACK request. - EXPECT_TRUE(paced_sender_->ResendPackets(nack_packets, base::TimeDelta())); + EXPECT_TRUE(paced_sender_->ResendPackets(nack_packets, DedupInfo())); // Check that we get the first NACK burst. mock_transport_.AddExpectedSize(kNackSize, 10); @@ -214,7 +220,7 @@ TEST_F(PacedSenderTest, PaceWithNack) { task_runner_->RunTasks(); // Add second NACK request. - EXPECT_TRUE(paced_sender_->ResendPackets(nack_packets, base::TimeDelta())); + EXPECT_TRUE(paced_sender_->ResendPackets(nack_packets, DedupInfo())); // Check that we get the next NACK burst. mock_transport_.AddExpectedSize(kNackSize, 10); @@ -388,8 +394,7 @@ TEST_F(PacedSenderTest, SendPriority) { // Resend video packets. This is queued and will be sent // earlier than normal video packets. - EXPECT_TRUE(paced_sender_->ResendPackets( - resend_packets, base::TimeDelta())); + EXPECT_TRUE(paced_sender_->ResendPackets(resend_packets, DedupInfo())); // Roll the clock. Queued packets will be sent in this order: // 1. RTCP packet x 1. @@ -400,5 +405,62 @@ TEST_F(PacedSenderTest, SendPriority) { EXPECT_TRUE(RunUntilEmpty(4)); } +TEST_F(PacedSenderTest, GetLastByteSent) { + mock_transport_.AddExpectedSize(kSize1, 4); + + SendPacketVector packets1 = CreateSendPacketVector(kSize1, 1, true); + SendPacketVector packets2 = CreateSendPacketVector(kSize1, 1, false); + + EXPECT_TRUE(paced_sender_->SendPackets(packets1)); + EXPECT_EQ(static_cast<int64>(kSize1), + paced_sender_->GetLastByteSentForPacket(packets1[0].first)); + EXPECT_EQ(static_cast<int64>(kSize1), + paced_sender_->GetLastByteSentForSsrc(kAudioSsrc)); + EXPECT_EQ(0, paced_sender_->GetLastByteSentForSsrc(kVideoSsrc)); + + EXPECT_TRUE(paced_sender_->SendPackets(packets2)); + EXPECT_EQ(static_cast<int64>(2 * kSize1), + paced_sender_->GetLastByteSentForPacket(packets2[0].first)); + EXPECT_EQ(static_cast<int64>(kSize1), + paced_sender_->GetLastByteSentForSsrc(kAudioSsrc)); + EXPECT_EQ(static_cast<int64>(2 * kSize1), + paced_sender_->GetLastByteSentForSsrc(kVideoSsrc)); + + EXPECT_TRUE(paced_sender_->ResendPackets(packets1, DedupInfo())); + EXPECT_EQ(static_cast<int64>(3 * kSize1), + paced_sender_->GetLastByteSentForPacket(packets1[0].first)); + EXPECT_EQ(static_cast<int64>(3 * kSize1), + paced_sender_->GetLastByteSentForSsrc(kAudioSsrc)); + EXPECT_EQ(static_cast<int64>(2 * kSize1), + paced_sender_->GetLastByteSentForSsrc(kVideoSsrc)); + + EXPECT_TRUE(paced_sender_->ResendPackets(packets2, DedupInfo())); + EXPECT_EQ(static_cast<int64>(4 * kSize1), + paced_sender_->GetLastByteSentForPacket(packets2[0].first)); + EXPECT_EQ(static_cast<int64>(3 * kSize1), + paced_sender_->GetLastByteSentForSsrc(kAudioSsrc)); + EXPECT_EQ(static_cast<int64>(4 * kSize1), + paced_sender_->GetLastByteSentForSsrc(kVideoSsrc)); +} + +TEST_F(PacedSenderTest, DedupWithResendInterval) { + mock_transport_.AddExpectedSize(kSize1, 2); + + SendPacketVector packets = CreateSendPacketVector(kSize1, 1, true); + EXPECT_TRUE(paced_sender_->SendPackets(packets)); + testing_clock_.Advance(base::TimeDelta::FromMilliseconds(10)); + + DedupInfo dedup_info; + dedup_info.resend_interval = base::TimeDelta::FromMilliseconds(20); + + // This packet will not be sent. + EXPECT_TRUE(paced_sender_->ResendPackets(packets, dedup_info)); + EXPECT_EQ(static_cast<int64>(kSize1), mock_transport_.GetBytesSent()); + + dedup_info.resend_interval = base::TimeDelta::FromMilliseconds(5); + EXPECT_TRUE(paced_sender_->ResendPackets(packets, dedup_info)); + EXPECT_EQ(static_cast<int64>(2 * kSize1), mock_transport_.GetBytesSent()); +} + } // namespace cast } // namespace media diff --git a/media/cast/net/rtcp/mock_rtcp_receiver_feedback.cc b/media/cast/net/rtcp/mock_rtcp_receiver_feedback.cc deleted file mode 100644 index d9818ff8f2..0000000000 --- a/media/cast/net/rtcp/mock_rtcp_receiver_feedback.cc +++ /dev/null @@ -1,15 +0,0 @@ -// 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/net/rtcp/mock_rtcp_receiver_feedback.h" - -namespace media { -namespace cast { - -MockRtcpReceiverFeedback::MockRtcpReceiverFeedback() {} - -MockRtcpReceiverFeedback::~MockRtcpReceiverFeedback() {} - -} // namespace cast -} // namespace media diff --git a/media/cast/net/rtcp/mock_rtcp_receiver_feedback.h b/media/cast/net/rtcp/mock_rtcp_receiver_feedback.h deleted file mode 100644 index ae6b96e56d..0000000000 --- a/media/cast/net/rtcp/mock_rtcp_receiver_feedback.h +++ /dev/null @@ -1,45 +0,0 @@ -// 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_MOCK_RTCP_RECEIVER_FEEDBACK_H_ -#define MEDIA_CAST_RTCP_MOCK_RTCP_RECEIVER_FEEDBACK_H_ - -#include <vector> - -#include "media/cast/net/cast_transport_defines.h" -#include "media/cast/net/rtcp/rtcp_defines.h" -#include "media/cast/net/rtcp/rtcp_receiver.h" -#include "testing/gmock/include/gmock/gmock.h" - -namespace media { -namespace cast { - -// TODO(hclam): Should be renamed to MockRtcpMessageHandler. -class MockRtcpReceiverFeedback : public RtcpMessageHandler { - public: - MockRtcpReceiverFeedback(); - virtual ~MockRtcpReceiverFeedback(); - - MOCK_METHOD1(OnReceivedSenderReport, - void(const RtcpSenderInfo& remote_sender_info)); - - MOCK_METHOD1(OnReceiverReferenceTimeReport, - void(const RtcpReceiverReferenceTimeReport& remote_time_report)); - - MOCK_METHOD0(OnReceivedSendReportRequest, void()); - - MOCK_METHOD1(OnReceivedReceiverLog, - void(const RtcpReceiverLogMessage& receiver_log)); - - MOCK_METHOD2(OnReceivedDelaySinceLastReport, - void(uint32 last_report, uint32 delay_since_last_report)); - - MOCK_METHOD1(OnReceivedCastFeedback, - void(const RtcpCastMessage& cast_message)); -}; - -} // namespace cast -} // namespace media - -#endif // MEDIA_CAST_RTCP_MOCK_RTCP_RECEIVER_FEEDBACK_H_ diff --git a/media/cast/net/rtcp/mock_rtcp_sender_feedback.cc b/media/cast/net/rtcp/mock_rtcp_sender_feedback.cc deleted file mode 100644 index 2c51c7448d..0000000000 --- a/media/cast/net/rtcp/mock_rtcp_sender_feedback.cc +++ /dev/null @@ -1,15 +0,0 @@ -// 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/net/rtcp/mock_rtcp_sender_feedback.h" - -namespace media { -namespace cast { - -MockRtcpSenderFeedback::MockRtcpSenderFeedback() {} - -MockRtcpSenderFeedback::~MockRtcpSenderFeedback() {} - -} // namespace cast -} // namespace media diff --git a/media/cast/net/rtcp/mock_rtcp_sender_feedback.h b/media/cast/net/rtcp/mock_rtcp_sender_feedback.h deleted file mode 100644 index a6af0aaa3e..0000000000 --- a/media/cast/net/rtcp/mock_rtcp_sender_feedback.h +++ /dev/null @@ -1,28 +0,0 @@ -// 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_MOCK_RTCP_SENDER_FEEDBACK_H_ -#define MEDIA_CAST_RTCP_MOCK_RTCP_SENDER_FEEDBACK_H_ - -#include <vector> - -#include "media/cast/net/rtcp/rtcp_receiver.h" -#include "testing/gmock/include/gmock/gmock.h" - -namespace media { -namespace cast { - -class MockRtcpSenderFeedback : public RtcpSenderFeedback { - public: - MockRtcpSenderFeedback(); - virtual ~MockRtcpSenderFeedback(); - - MOCK_METHOD1(OnReceivedCastFeedback, - void(const RtcpCastMessage& cast_feedback)); -}; - -} // namespace cast -} // namespace media - -#endif // MEDIA_CAST_RTCP_MOCK_RTCP_SENDER_FEEDBACK_H_ diff --git a/media/cast/net/rtcp/rtcp.cc b/media/cast/net/rtcp/rtcp.cc index ac413d39c9..6b7718dde9 100644 --- a/media/cast/net/rtcp/rtcp.cc +++ b/media/cast/net/rtcp/rtcp.cc @@ -9,7 +9,6 @@ #include "media/cast/cast_environment.h" #include "media/cast/net/cast_transport_defines.h" #include "media/cast/net/rtcp/rtcp_defines.h" -#include "media/cast/net/rtcp/rtcp_receiver.h" #include "media/cast/net/rtcp/rtcp_sender.h" #include "media/cast/net/rtcp/rtcp_utility.h" @@ -19,48 +18,40 @@ namespace media { namespace cast { static const int32 kMaxRttMs = 10000; // 10 seconds. +// Reject packets that are older than 0.5 seconds older than +// the newest packet we've seen so far. This protect internal +// states from crazy routers. (Based on RRTR) +static const int32 kOutOfOrderMaxAgeMs = 500; + +namespace { + +// 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())); +} -class Rtcp::RtcpMessageHandlerImpl : public RtcpMessageHandler { - public: - explicit RtcpMessageHandlerImpl(Rtcp* rtcp) - : rtcp_(rtcp) {} - - virtual void OnReceivedSenderReport( - const 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) { - rtcp_->OnReceivedLipSyncInfo(remote_sender_info.rtp_timestamp, - remote_sender_info.ntp_seconds, - remote_sender_info.ntp_fraction); - } - } - - virtual void OnReceiverReferenceTimeReport( - const RtcpReceiverReferenceTimeReport& remote_time_report) OVERRIDE { - rtcp_->OnReceivedNtp(remote_time_report.ntp_seconds, - remote_time_report.ntp_fraction); - } - - virtual void OnReceivedReceiverLog(const RtcpReceiverLogMessage& receiver_log) - OVERRIDE { - rtcp_->OnReceivedReceiverLog(receiver_log); - } +} // namespace - virtual void OnReceivedDelaySinceLastReport( - uint32 last_report, - uint32 delay_since_last_report) OVERRIDE { - rtcp_->OnReceivedDelaySinceLastReport(last_report, delay_since_last_report); - } - - virtual void OnReceivedCastFeedback( - const RtcpCastMessage& cast_message) OVERRIDE { - rtcp_->OnReceivedCastFeedback(cast_message); - } - - private: - Rtcp* rtcp_; -}; Rtcp::Rtcp(const RtcpCastMessageCallback& cast_callback, const RtcpRttCallback& rtt_callback, @@ -76,75 +67,146 @@ Rtcp::Rtcp(const RtcpCastMessageCallback& cast_callback, rtcp_sender_(new RtcpSender(packet_sender, local_ssrc)), local_ssrc_(local_ssrc), remote_ssrc_(remote_ssrc), - handler_(new RtcpMessageHandlerImpl(this)), - rtcp_receiver_(new RtcpReceiver(handler_.get(), local_ssrc)), last_report_truncated_ntp_(0), local_clock_ahead_by_(ClockDriftSmoother::GetDefaultTimeConstant()), lip_sync_rtp_timestamp_(0), lip_sync_ntp_timestamp_(0), min_rtt_(TimeDelta::FromMilliseconds(kMaxRttMs)), number_of_rtt_in_avg_(0) { - rtcp_receiver_->SetRemoteSSRC(remote_ssrc); - - // This value is the same in FrameReceiver. - rtcp_receiver_->SetCastReceiverEventHistorySize( - kReceiverRtcpEventHistorySize); } Rtcp::~Rtcp() {} +bool Rtcp::IsRtcpPacket(const uint8* packet, size_t length) { + if (length < kMinLengthOfRtcp) { + LOG(ERROR) << "Invalid RTCP packet received."; + return false; + } + + uint8 packet_type = packet[1]; + return packet_type >= kPacketTypeLow && packet_type <= kPacketTypeHigh; +} + +uint32 Rtcp::GetSsrcOfSender(const uint8* rtcp_buffer, size_t length) { + if (length < kMinLengthOfRtcp) + return 0; + uint32 ssrc_of_sender; + 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; +} + bool Rtcp::IncomingRtcpPacket(const uint8* data, size_t length) { // Check if this is a valid RTCP packet. - if (!RtcpReceiver::IsRtcpPacket(data, length)) { + if (!IsRtcpPacket(data, length)) { VLOG(1) << "Rtcp@" << this << "::IncomingRtcpPacket() -- " << "Received an invalid (non-RTCP?) packet."; return false; } // Check if this packet is to us. - uint32 ssrc_of_sender = RtcpReceiver::GetSsrcOfSender(data, length); + uint32 ssrc_of_sender = GetSsrcOfSender(data, length); if (ssrc_of_sender != remote_ssrc_) { return false; } // Parse this packet. - RtcpParser rtcp_parser(data, length); - if (!rtcp_parser.IsValid()) { - // Silently ignore packet. - VLOG(1) << "Received invalid RTCP packet"; - return false; + RtcpParser parser(local_ssrc_, remote_ssrc_); + base::BigEndianReader reader(reinterpret_cast<const char*>(data), length); + if (parser.Parse(&reader)) { + if (parser.has_receiver_reference_time_report()) { + base::TimeTicks t = ConvertNtpToTimeTicks( + parser.receiver_reference_time_report().ntp_seconds, + parser.receiver_reference_time_report().ntp_fraction); + if (t > largest_seen_timestamp_) { + largest_seen_timestamp_ = t; + } else if ((largest_seen_timestamp_ - t).InMilliseconds() > + kOutOfOrderMaxAgeMs) { + // Reject packet, it is too old. + VLOG(1) << "Rejecting RTCP packet as it is too old (" + << (largest_seen_timestamp_ - t).InMilliseconds() + << " ms)"; + return true; + } + + OnReceivedNtp(parser.receiver_reference_time_report().ntp_seconds, + parser.receiver_reference_time_report().ntp_fraction); + } + if (parser.has_sender_report()) { + OnReceivedNtp(parser.sender_report().ntp_seconds, + parser.sender_report().ntp_fraction); + OnReceivedLipSyncInfo(parser.sender_report().rtp_timestamp, + parser.sender_report().ntp_seconds, + parser.sender_report().ntp_fraction); + } + if (parser.has_receiver_log()) { + if (DedupeReceiverLog(parser.mutable_receiver_log())) { + OnReceivedReceiverLog(parser.receiver_log()); + } + } + if (parser.has_last_report()) { + OnReceivedDelaySinceLastReport(parser.last_report(), + parser.delay_since_last_report()); + } + if (parser.has_cast_message()) { + parser.mutable_cast_message()->ack_frame_id = + ack_frame_id_wrap_helper_.MapTo32bitsFrameId( + parser.mutable_cast_message()->ack_frame_id); + OnReceivedCastFeedback(parser.cast_message()); + } } - rtcp_receiver_->IncomingRtcpPacket(&rtcp_parser); return true; } +bool Rtcp::DedupeReceiverLog(RtcpReceiverLogMessage* receiver_log) { + RtcpReceiverLogMessage::iterator i = receiver_log->begin(); + while (i != receiver_log->end()) { + RtcpReceiverEventLogMessages* messages = &i->event_log_messages_; + RtcpReceiverEventLogMessages::iterator j = messages->begin(); + while (j != messages->end()) { + ReceiverEventKey key = GetReceiverEventKey(i->rtp_timestamp_, + j->event_timestamp, + j->type, + j->packet_id); + RtcpReceiverEventLogMessages::iterator tmp = j; + ++j; + if (receiver_event_key_set_.insert(key).second) { + receiver_event_key_queue_.push(key); + if (receiver_event_key_queue_.size() > kReceiverRtcpEventHistorySize) { + receiver_event_key_set_.erase(receiver_event_key_queue_.front()); + receiver_event_key_queue_.pop(); + } + } else { + messages->erase(tmp); + } + } + + RtcpReceiverLogMessage::iterator tmp = i; + ++i; + if (messages->empty()) { + receiver_log->erase(tmp); + } + } + return !receiver_log->empty(); +} + void Rtcp::SendRtcpFromRtpReceiver( const RtcpCastMessage* cast_message, base::TimeDelta target_delay, const ReceiverRtcpEventSubscriber::RtcpEventMultiMap* rtcp_events, RtpReceiverStatistics* rtp_receiver_statistics) { - uint32 packet_type_flags = 0; - base::TimeTicks now = clock_->NowTicks(); 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 |= kRtcpRrtr; ConvertTimeTicksToNtp(now, &rrtr.ntp_seconds, &rrtr.ntp_fraction); SaveLastSentNtpTime(now, rrtr.ntp_seconds, rrtr.ntp_fraction); - if (cast_message) { - packet_type_flags |= kRtcpCast; - } - if (rtcp_events) { - packet_type_flags |= kRtcpReceiverLog; - } - // If RTCP is in compound mode then we always send a RR. if (rtp_receiver_statistics) { - packet_type_flags |= kRtcpRr; - 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) { @@ -166,19 +228,18 @@ void Rtcp::SendRtcpFromRtpReceiver( report_block.delay_since_last_sr = 0; } } - rtcp_sender_->SendRtcpFromRtpReceiver(packet_type_flags, - &report_block, - &rrtr, - cast_message, - rtcp_events, - target_delay); + rtcp_sender_->SendRtcpFromRtpReceiver( + rtp_receiver_statistics ? &report_block : NULL, + &rrtr, + cast_message, + rtcp_events, + target_delay); } void Rtcp::SendRtcpFromRtpSender(base::TimeTicks current_time, uint32 current_time_as_rtp_timestamp, uint32 send_packet_count, size_t send_octet_count) { - uint32 packet_type_flags = kRtcpSr; uint32 current_ntp_seconds = 0; uint32 current_ntp_fractions = 0; ConvertTimeTicksToNtp(current_time, ¤t_ntp_seconds, @@ -186,26 +247,6 @@ void Rtcp::SendRtcpFromRtpSender(base::TimeTicks current_time, SaveLastSentNtpTime(current_time, current_ntp_seconds, current_ntp_fractions); - RtcpDlrrReportBlock dlrr; - if (!time_last_report_received_.is_null()) { - packet_type_flags |= kRtcpDlrr; - dlrr.last_rr = last_report_truncated_ntp_; - uint32 delay_seconds = 0; - uint32 delay_fraction = 0; - base::TimeDelta delta = current_time - time_last_report_received_; - // TODO(hclam): DLRR is not used by any receiver. Consider removing - // it. There is one race condition in the computation of the time for - // DLRR: current time is submitted to this method while - // |time_last_report_received_| is updated just before that. This can - // happen if current time is not submitted synchronously. - if (delta < base::TimeDelta()) - delta = base::TimeDelta(); - ConvertTimeToFractions(delta.InMicroseconds(), &delay_seconds, - &delay_fraction); - - dlrr.delay_since_last_rr = ConvertToNtpDiff(delay_seconds, delay_fraction); - } - RtcpSenderInfo sender_info; sender_info.ntp_seconds = current_ntp_seconds; sender_info.ntp_fraction = current_ntp_fractions; @@ -213,7 +254,7 @@ void Rtcp::SendRtcpFromRtpSender(base::TimeTicks current_time, sender_info.send_packet_count = send_packet_count; sender_info.send_octet_count = send_octet_count; - rtcp_sender_->SendRtcpFromRtpSender(packet_type_flags, sender_info, dlrr); + rtcp_sender_->SendRtcpFromRtpSender(sender_info); } void Rtcp::OnReceivedNtp(uint32 ntp_seconds, uint32 ntp_fraction) { diff --git a/media/cast/net/rtcp/rtcp.h b/media/cast/net/rtcp/rtcp.h index 439b963502..a1fdc9e489 100644 --- a/media/cast/net/rtcp/rtcp.h +++ b/media/cast/net/rtcp/rtcp.h @@ -107,6 +107,10 @@ class Rtcp { void OnReceivedReceiverLog(const RtcpReceiverLogMessage& receiver_log); + static bool IsRtcpPacket(const uint8* packet, size_t length); + static uint32 GetSsrcOfSender(const uint8* rtcp_buffer, size_t length); + const base::TimeDelta& rtt() const { return rtt_; } + protected: void OnReceivedNtp(uint32 ntp_seconds, uint32 ntp_fraction); void OnReceivedLipSyncInfo(uint32 rtp_timestamp, @@ -114,8 +118,6 @@ class Rtcp { uint32 ntp_fraction); private: - class RtcpMessageHandlerImpl; - void OnReceivedDelaySinceLastReport(uint32 last_report, uint32 delay_since_last_report); @@ -128,6 +130,10 @@ class Rtcp { uint32 last_ntp_seconds, uint32 last_ntp_fraction); + // Remove duplicate events in |receiver_log|. + // Returns true if any events remain. + bool DedupeReceiverLog(RtcpReceiverLogMessage* receiver_log); + const RtcpCastMessageCallback cast_callback_; const RtcpRttCallback rtt_callback_; const RtcpLogMessageCallback log_callback_; @@ -135,8 +141,6 @@ class Rtcp { const scoped_ptr<RtcpSender> rtcp_sender_; const uint32 local_ssrc_; const uint32 remote_ssrc_; - const scoped_ptr<RtcpMessageHandlerImpl> handler_; - const scoped_ptr<RtcpReceiver> rtcp_receiver_; RtcpSendTimeMap last_reports_sent_map_; RtcpSendTimeQueue last_reports_sent_queue_; @@ -167,6 +171,16 @@ class Rtcp { int number_of_rtt_in_avg_; base::TimeDelta avg_rtt_; + base::TimeTicks largest_seen_timestamp_; + + // For extending received ACK frame IDs from 8-bit to 32-bit. + FrameIdWrapHelper ack_frame_id_wrap_helper_; + + // Maintains a history of receiver events. + 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(Rtcp); }; diff --git a/media/cast/net/rtcp/rtcp_builder.cc b/media/cast/net/rtcp/rtcp_builder.cc deleted file mode 100644 index f2626d3582..0000000000 --- a/media/cast/net/rtcp/rtcp_builder.cc +++ /dev/null @@ -1,123 +0,0 @@ -// 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/net/rtcp/rtcp_builder.h" - -#include <algorithm> -#include <string> -#include <vector> - -#include "base/big_endian.h" -#include "base/logging.h" -#include "media/cast/net/cast_transport_defines.h" -#include "media/cast/net/pacing/paced_sender.h" - -namespace media { -namespace cast { - -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) { - if (packet_type_flags & kRtcpRr || - packet_type_flags & kRtcpRrtr || - packet_type_flags & kRtcpCast || - packet_type_flags & kRtcpReceiverLog || - packet_type_flags & kRtcpNack) { - NOTREACHED() << "Invalid argument"; - } - ssrc_ = sending_ssrc; - 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 & 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; -} - -/* - 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 cast -} // namespace media diff --git a/media/cast/net/rtcp/rtcp_builder.h b/media/cast/net/rtcp/rtcp_builder.h deleted file mode 100644 index b07ace1fe3..0000000000 --- a/media/cast/net/rtcp/rtcp_builder.h +++ /dev/null @@ -1,43 +0,0 @@ -// 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_NET_RTCP_RTCP_BUILDER_H_ -#define MEDIA_CAST_NET_RTCP_RTCP_BUILDER_H_ - -#include <list> -#include <string> -#include <vector> - -#include "media/cast/net/cast_transport_defines.h" -#include "media/cast/net/pacing/paced_sender.h" - -namespace media { -namespace cast { - -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); - - private: - bool BuildSR(const RtcpSenderInfo& sender_info, Packet* packet) const; - bool BuildDlrrRb(const RtcpDlrrReportBlock& dlrr, - Packet* packet) const; - - PacedSender* const transport_; // Not owned by this class. - uint32 ssrc_; - - DISALLOW_COPY_AND_ASSIGN(RtcpBuilder); -}; - -} // namespace cast -} // namespace media - -#endif // MEDIA_CAST_NET_RTCP_RTCP_BUILDER_H_ diff --git a/media/cast/net/rtcp/rtcp_builder_unittest.cc b/media/cast/net/rtcp/rtcp_builder_unittest.cc deleted file mode 100644 index 7e1fcd457f..0000000000 --- a/media/cast/net/rtcp/rtcp_builder_unittest.cc +++ /dev/null @@ -1,159 +0,0 @@ -// 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/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/paced_sender.h" -#include "media/cast/net/rtcp/rtcp_builder.h" -#include "media/cast/net/rtcp/rtcp_utility.h" -#include "media/cast/net/rtcp/test_rtcp_packet_builder.h" -#include "media/cast/test/fake_single_thread_task_runner.h" -#include "testing/gmock/include/gmock/gmock.h" - -namespace media { -namespace cast { - -namespace { -static const uint32 kSendingSsrc = 0x12345678; -} // 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)) { - } - - 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. - TestRtcpPacketBuilder p; - p.AddSr(kSendingSsrc, 0); - test_transport_.SetExpectedRtcpPacket(p.Packet(), p.Length()); - - rtcp_builder_->SendRtcpFromRtpSender(RtcpBuilder::kRtcpSr, - &sender_info, - NULL, - NULL, - kSendingSsrc); - - 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 + dlrr. - TestRtcpPacketBuilder p1; - p1.AddSr(kSendingSsrc, 0); - p1.AddSdesCname(kSendingSsrc); - 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); - - 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 + + dlrr + sender log. - TestRtcpPacketBuilder p; - p.AddSr(kSendingSsrc, 0); - p.AddSdesCname(kSendingSsrc); - 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); - - EXPECT_EQ(1, test_transport_.packet_count()); -} - -} // namespace cast -} // namespace media diff --git a/media/cast/net/rtcp/rtcp_receiver.cc b/media/cast/net/rtcp/rtcp_receiver.cc deleted file mode 100644 index 4ba9986a17..0000000000 --- a/media/cast/net/rtcp/rtcp_receiver.cc +++ /dev/null @@ -1,414 +0,0 @@ -// 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/net/rtcp/rtcp_receiver.h" - -#include "base/big_endian.h" -#include "base/logging.h" -#include "media/cast/net/cast_transport_defines.h" -#include "media/cast/net/rtcp/rtcp_utility.h" - -namespace { - -// 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 - -namespace media { -namespace cast { - -RtcpReceiver::RtcpReceiver(RtcpMessageHandler* handler, - uint32 local_ssrc) - : ssrc_(local_ssrc), - remote_ssrc_(0), - handler_(handler), - receiver_event_history_size_(0) { - DCHECK(handler_); -} - -RtcpReceiver::~RtcpReceiver() {} - -// static -bool RtcpReceiver::IsRtcpPacket(const uint8* packet, size_t length) { - if (length < kMinLengthOfRtcp) { - LOG(ERROR) << "Invalid RTCP packet received."; - return false; - } - - uint8 packet_type = packet[1]; - if (packet_type >= kPacketTypeLow && - packet_type <= kPacketTypeHigh) { - return true; - } - return false; -} - -// static -uint32 RtcpReceiver::GetSsrcOfSender(const uint8* rtcp_buffer, size_t length) { - if (length < kMinLengthOfRtcp) - return 0; - uint32 ssrc_of_sender; - 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; -} - -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) { - RtcpFieldTypes field_type = rtcp_parser->Begin(); - while (field_type != kRtcpNotValidCode) { - // Each "case" is responsible for iterate the parser to the next top - // level packet. - switch (field_type) { - case kRtcpSrCode: - HandleSenderReport(rtcp_parser); - break; - case kRtcpRrCode: - HandleReceiverReport(rtcp_parser); - break; - case kRtcpXrCode: - HandleXr(rtcp_parser); - break; - case kRtcpPayloadSpecificAppCode: - HandlePayloadSpecificApp(rtcp_parser); - break; - case kRtcpApplicationSpecificCastReceiverLogCode: - HandleApplicationSpecificCastReceiverLog(rtcp_parser); - break; - case kRtcpPayloadSpecificCastCode: - case kRtcpPayloadSpecificCastNackItemCode: - case kRtcpApplicationSpecificCastReceiverLogFrameCode: - case kRtcpApplicationSpecificCastReceiverLogEventCode: - case kRtcpNotValidCode: - case kRtcpReportBlockItemCode: - case kRtcpXrRrtrCode: - case kRtcpXrDlrrCode: - case kRtcpXrUnknownItemCode: - rtcp_parser->Iterate(); - NOTREACHED() << "Invalid state"; - break; - } - field_type = rtcp_parser->FieldType(); - } -} - -void RtcpReceiver::HandleSenderReport(RtcpParser* rtcp_parser) { - RtcpFieldTypes rtcp_field_type = rtcp_parser->FieldType(); - const RtcpField& rtcp_field = rtcp_parser->Field(); - - DCHECK(rtcp_field_type == kRtcpSrCode) << "Invalid state"; - - // Synchronization source identifier for the originator of this SR packet. - uint32 remote_ssrc = rtcp_field.sender_report.sender_ssrc; - - VLOG(2) << "Cast RTCP received SR from SSRC " << remote_ssrc; - - if (remote_ssrc_ == remote_ssrc) { - 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.send_packet_count = - rtcp_field.sender_report.sender_packet_count; - remote_sender_info.send_octet_count = - rtcp_field.sender_report.sender_octet_count; - handler_->OnReceivedSenderReport(remote_sender_info); - } - rtcp_field_type = rtcp_parser->Iterate(); - while (rtcp_field_type == kRtcpReportBlockItemCode) { - HandleReportBlock(&rtcp_field, remote_ssrc); - rtcp_field_type = rtcp_parser->Iterate(); - } -} - -void RtcpReceiver::HandleReceiverReport(RtcpParser* rtcp_parser) { - RtcpFieldTypes rtcp_field_type = rtcp_parser->FieldType(); - const RtcpField& rtcp_field = rtcp_parser->Field(); - - DCHECK(rtcp_field_type == kRtcpRrCode) << "Invalid state"; - - uint32 remote_ssrc = rtcp_field.receiver_report.sender_ssrc; - - VLOG(2) << "Cast RTCP received RR from SSRC " << remote_ssrc; - - rtcp_field_type = rtcp_parser->Iterate(); - while (rtcp_field_type == kRtcpReportBlockItemCode) { - HandleReportBlock(&rtcp_field, remote_ssrc); - rtcp_field_type = rtcp_parser->Iterate(); - } -} - -void RtcpReceiver::HandleReportBlock(const RtcpField* rtcp_field, - uint32 remote_ssrc) { - // This will be called once per report block in the Rtcp packet. - // We filter out all report blocks that are not for us. - // Each packet has max 31 RR blocks. - // - // We can calculate RTT if we send a send report and get a report block back. - - // |rtcp_field.ReportBlockItem.ssrc| is the ssrc identifier of the source to - // which the information in this reception report block pertains. - - const RtcpFieldReportBlockItem& rb = rtcp_field->report_block_item; - - // Filter out all report blocks that are not for us. - if (rb.ssrc != ssrc_) { - // This block is not for us ignore it. - return; - } - VLOG(2) << "Cast RTCP received RB from SSRC " << remote_ssrc; - - RtcpReportBlock report_block; - report_block.remote_ssrc = remote_ssrc; - report_block.media_ssrc = rb.ssrc; - report_block.fraction_lost = rb.fraction_lost; - report_block.cumulative_lost = rb.cumulative_number_of_packets_lost; - report_block.extended_high_sequence_number = - rb.extended_highest_sequence_number; - report_block.jitter = rb.jitter; - report_block.last_sr = rb.last_sender_report; - report_block.delay_since_last_sr = rb.delay_last_sender_report; - handler_->OnReceivedDelaySinceLastReport( - rb.last_sender_report, rb.delay_last_sender_report); -} - -void RtcpReceiver::HandleXr(RtcpParser* rtcp_parser) { - RtcpFieldTypes rtcp_field_type = rtcp_parser->FieldType(); - const RtcpField& rtcp_field = rtcp_parser->Field(); - - DCHECK(rtcp_field_type == kRtcpXrCode) << "Invalid state"; - - uint32 remote_ssrc = rtcp_field.extended_report.sender_ssrc; - rtcp_field_type = rtcp_parser->Iterate(); - - while (rtcp_field_type == kRtcpXrDlrrCode || - rtcp_field_type == kRtcpXrRrtrCode || - rtcp_field_type == kRtcpXrUnknownItemCode) { - if (rtcp_field_type == kRtcpXrRrtrCode) { - HandleRrtr(rtcp_parser, remote_ssrc); - } else if (rtcp_field_type == kRtcpXrDlrrCode) { - HandleDlrr(rtcp_parser); - } - rtcp_field_type = rtcp_parser->Iterate(); - } -} - -void RtcpReceiver::HandleRrtr(RtcpParser* rtcp_parser, uint32 remote_ssrc) { - if (remote_ssrc_ != remote_ssrc) { - // Not to us. - return; - } - const RtcpField& rtcp_field = rtcp_parser->Field(); - RtcpReceiverReferenceTimeReport remote_time_report; - remote_time_report.remote_ssrc = remote_ssrc; - remote_time_report.ntp_seconds = rtcp_field.rrtr.ntp_most_significant; - remote_time_report.ntp_fraction = rtcp_field.rrtr.ntp_least_significant; - handler_->OnReceiverReferenceTimeReport(remote_time_report); -} - -void RtcpReceiver::HandleDlrr(RtcpParser* rtcp_parser) { - const RtcpField& rtcp_field = rtcp_parser->Field(); - if (remote_ssrc_ != rtcp_field.dlrr.receivers_ssrc) { - // Not to us. - return; - } - handler_->OnReceivedDelaySinceLastReport( - rtcp_field.dlrr.last_receiver_report, - rtcp_field.dlrr.delay_last_receiver_report); -} - -void RtcpReceiver::HandlePayloadSpecificApp(RtcpParser* rtcp_parser) { - const RtcpField& rtcp_field = rtcp_parser->Field(); - uint32 remote_ssrc = rtcp_field.application_specific.sender_ssrc; - if (remote_ssrc_ != remote_ssrc) { - // Message not to us. Iterate until we have passed this message. - RtcpFieldTypes field_type; - do { - field_type = rtcp_parser->Iterate(); - } while (field_type == kRtcpPayloadSpecificCastCode || - field_type == kRtcpPayloadSpecificCastNackItemCode); - return; - } - - RtcpFieldTypes packet_type = rtcp_parser->Iterate(); - switch (packet_type) { - case kRtcpPayloadSpecificCastCode: - packet_type = rtcp_parser->Iterate(); - if (packet_type == kRtcpPayloadSpecificCastCode) { - HandlePayloadSpecificCastItem(rtcp_parser); - } - break; - default: - return; - } -} - -void RtcpReceiver::HandleApplicationSpecificCastReceiverLog( - RtcpParser* rtcp_parser) { - const RtcpField& rtcp_field = rtcp_parser->Field(); - - uint32 remote_ssrc = rtcp_field.cast_receiver_log.sender_ssrc; - if (remote_ssrc_ != remote_ssrc) { - // Message not to us. Iterate until we have passed this message. - RtcpFieldTypes field_type; - do { - field_type = rtcp_parser->Iterate(); - } while (field_type == kRtcpApplicationSpecificCastReceiverLogFrameCode || - field_type == kRtcpApplicationSpecificCastReceiverLogEventCode); - return; - } - RtcpReceiverLogMessage receiver_log; - RtcpFieldTypes field_type = rtcp_parser->Iterate(); - while (field_type == kRtcpApplicationSpecificCastReceiverLogFrameCode) { - RtcpReceiverFrameLogMessage frame_log( - rtcp_field.cast_receiver_log.rtp_timestamp); - - field_type = rtcp_parser->Iterate(); - while (field_type == kRtcpApplicationSpecificCastReceiverLogEventCode) { - HandleApplicationSpecificCastReceiverEventLog( - rtcp_field.cast_receiver_log.rtp_timestamp, - rtcp_parser, - &frame_log.event_log_messages_); - field_type = rtcp_parser->Iterate(); - } - - if (!frame_log.event_log_messages_.empty()) - receiver_log.push_back(frame_log); - } - - if (!receiver_log.empty()) - handler_->OnReceivedReceiverLog(receiver_log); -} - -void RtcpReceiver::HandleApplicationSpecificCastReceiverEventLog( - uint32 frame_rtp_timestamp, - RtcpParser* rtcp_parser, - RtcpReceiverEventLogMessages* event_log_messages) { - const RtcpField& rtcp_field = rtcp_parser->Field(); - - 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); - - // 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); - } - } - - 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) { - const RtcpField& rtcp_field = rtcp_parser->Field(); - 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) { - const RtcpField& rtcp_field = rtcp_parser->Field(); - HandlePayloadSpecificCastNackItem( - &rtcp_field, &cast_message.missing_frames_and_packets); - packet_type = rtcp_parser->Iterate(); - } - handler_->OnReceivedCastFeedback(cast_message); -} - -void RtcpReceiver::HandlePayloadSpecificCastNackItem( - const RtcpField* rtcp_field, - MissingFramesAndPacketsMap* missing_frames_and_packets) { - - MissingFramesAndPacketsMap::iterator frame_it = - missing_frames_and_packets->find(rtcp_field->cast_nack_item.frame_id); - - if (frame_it == missing_frames_and_packets->end()) { - // First missing packet in a frame. - PacketIdSet empty_set; - std::pair<MissingFramesAndPacketsMap::iterator, bool> ret = - missing_frames_and_packets->insert(std::pair<uint8, PacketIdSet>( - rtcp_field->cast_nack_item.frame_id, empty_set)); - frame_it = ret.first; - DCHECK(frame_it != missing_frames_and_packets->end()) << "Invalid state"; - } - 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; - } - uint8 bitmask = rtcp_field->cast_nack_item.bitmask; - - if (bitmask) { - for (int i = 1; i <= 8; ++i) { - if (bitmask & 1) { - frame_it->second.insert(packet_id + i); - } - bitmask = bitmask >> 1; - } - } -} - -} // namespace cast -} // namespace media diff --git a/media/cast/net/rtcp/rtcp_receiver.h b/media/cast/net/rtcp/rtcp_receiver.h deleted file mode 100644 index e7d432e390..0000000000 --- a/media/cast/net/rtcp/rtcp_receiver.h +++ /dev/null @@ -1,101 +0,0 @@ -// 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_RTCP_RECEIVER_H_ -#define MEDIA_CAST_RTCP_RTCP_RECEIVER_H_ - -#include <queue> - -#include "base/containers/hash_tables.h" -#include "media/cast/net/cast_transport_defines.h" -#include "media/cast/net/rtcp/rtcp.h" -#include "media/cast/net/rtcp/rtcp_defines.h" -#include "media/cast/net/rtcp/rtcp_utility.h" - -namespace media { -namespace cast { - -// Interface for receiving RTCP messages. -class RtcpMessageHandler { - public: - virtual void OnReceivedSenderReport( - const RtcpSenderInfo& remote_sender_info) = 0; - - virtual void OnReceiverReferenceTimeReport( - const RtcpReceiverReferenceTimeReport& remote_time_report) = 0; - - virtual void OnReceivedReceiverLog( - const RtcpReceiverLogMessage& receiver_log) = 0; - - virtual void OnReceivedDelaySinceLastReport( - uint32 last_report, - uint32 delay_since_last_report) = 0; - - virtual void OnReceivedCastFeedback( - const RtcpCastMessage& cast_message) = 0; - - virtual ~RtcpMessageHandler() {} -}; - -class RtcpReceiver { - public: - RtcpReceiver(RtcpMessageHandler* handler, uint32 local_ssrc); - virtual ~RtcpReceiver(); - - static bool IsRtcpPacket(const uint8* rtcp_buffer, size_t length); - - static uint32 GetSsrcOfSender(const uint8* rtcp_buffer, size_t length); - - 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: - void HandleSenderReport(RtcpParser* rtcp_parser); - - void HandleReceiverReport(RtcpParser* rtcp_parser); - - void HandleReportBlock(const RtcpField* rtcp_field, uint32 remote_ssrc); - - void HandleXr(RtcpParser* rtcp_parser); - void HandleRrtr(RtcpParser* rtcp_parser, uint32 remote_ssrc); - void HandleDlrr(RtcpParser* rtcp_parser); - - void HandlePayloadSpecificApp(RtcpParser* rtcp_parser); - void HandlePayloadSpecificCastItem(RtcpParser* rtcp_parser); - void HandlePayloadSpecificCastNackItem( - const RtcpField* rtcp_field, - MissingFramesAndPacketsMap* missing_frames_and_packets); - - void HandleApplicationSpecificCastReceiverLog(RtcpParser* rtcp_parser); - void HandleApplicationSpecificCastReceiverEventLog( - uint32 frame_rtp_timestamp, - RtcpParser* rtcp_parser, - RtcpReceiverEventLogMessages* event_log_messages); - - const uint32 ssrc_; - uint32 remote_ssrc_; - - // Not owned by this class. - RtcpMessageHandler* const handler_; - - 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); -}; - -} // namespace cast -} // namespace media - -#endif // MEDIA_CAST_RTCP_RTCP_RECEIVER_H_ diff --git a/media/cast/net/rtcp/rtcp_receiver_unittest.cc b/media/cast/net/rtcp/rtcp_receiver_unittest.cc deleted file mode 100644 index a32edbd224..0000000000 --- a/media/cast/net/rtcp/rtcp_receiver_unittest.cc +++ /dev/null @@ -1,493 +0,0 @@ -// 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/scoped_ptr.h" -#include "base/test/simple_test_tick_clock.h" -#include "media/cast/cast_environment.h" -#include "media/cast/net/cast_transport_defines.h" -#include "media/cast/net/rtcp/mock_rtcp_receiver_feedback.h" -#include "media/cast/net/rtcp/rtcp_receiver.h" -#include "media/cast/net/rtcp/rtcp_utility.h" -#include "media/cast/net/rtcp/test_rtcp_packet_builder.h" -#include "media/cast/test/fake_single_thread_task_runner.h" -#include "testing/gmock/include/gmock/gmock.h" - -namespace media { -namespace cast { - -using testing::_; - -static const uint32 kSenderSsrc = 0x10203; -static const uint32 kSourceSsrc = 0x40506; -static const uint32 kUnknownSsrc = 0xDEAD; -static const base::TimeDelta kTargetDelay = - base::TimeDelta::FromMilliseconds(100); - -namespace { - -class RtcpMessageVerification : public MockRtcpReceiverFeedback { - public: - RtcpMessageVerification() - : called_on_received_sender_log_(false), - called_on_received_receiver_log_(false), - called_on_received_cast_message_(false) {} - - 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(); - 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_); - EXPECT_EQ(expected_it->event_log_messages_.size(), - incoming_it->event_log_messages_.size()); - - RtcpReceiverEventLogMessages::const_iterator event_incoming_it = - incoming_it->event_log_messages_.begin(); - RtcpReceiverEventLogMessages::const_iterator event_expected_it = - expected_it->event_log_messages_.begin(); - for (; event_incoming_it != incoming_it->event_log_messages_.end(); - ++event_incoming_it, ++event_expected_it) { - 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 == PACKET_RECEIVED) { - EXPECT_EQ(event_expected_it->packet_id, event_incoming_it->packet_id); - } else { - EXPECT_EQ(event_expected_it->delay_delta, - event_incoming_it->delay_delta); - } - } - expected_receiver_log_.pop_front(); - expected_it = expected_receiver_log_.begin(); - } - called_on_received_receiver_log_ = true; - } - - virtual void OnReceivedCastFeedback(const RtcpCastMessage& cast_message) - OVERRIDE { - EXPECT_EQ(cast_message.media_ssrc, kSenderSsrc); - EXPECT_EQ(cast_message.ack_frame_id, kAckFrameId); - - MissingFramesAndPacketsMap::const_iterator frame_it = - cast_message.missing_frames_and_packets.begin(); - - EXPECT_TRUE(frame_it != cast_message.missing_frames_and_packets.end()); - EXPECT_EQ(kLostFrameId, frame_it->first); - EXPECT_EQ(frame_it->second.size(), 1UL); - EXPECT_EQ(*frame_it->second.begin(), kRtcpCastAllPacketsLost); - ++frame_it; - EXPECT_TRUE(frame_it != cast_message.missing_frames_and_packets.end()); - EXPECT_EQ(kFrameIdWithLostPackets, frame_it->first); - EXPECT_EQ(3UL, frame_it->second.size()); - PacketIdSet::const_iterator packet_it = frame_it->second.begin(); - EXPECT_EQ(kLostPacketId1, *packet_it); - ++packet_it; - EXPECT_EQ(kLostPacketId2, *packet_it); - ++packet_it; - EXPECT_EQ(kLostPacketId3, *packet_it); - ++frame_it; - EXPECT_TRUE(frame_it == cast_message.missing_frames_and_packets.end()); - called_on_received_cast_message_ = true; - } - - bool OnReceivedReceiverLogCalled() const { - return called_on_received_receiver_log_ && expected_receiver_log_.empty(); - } - - bool OnReceivedCastFeedbackCalled() const { - return called_on_received_cast_message_; - } - - void SetExpectedReceiverLog(const RtcpReceiverLogMessage& receiver_log) { - expected_receiver_log_ = receiver_log; - } - - private: - RtcpReceiverLogMessage expected_receiver_log_; - bool called_on_received_sender_log_; - bool called_on_received_receiver_log_; - bool called_on_received_cast_message_; - - DISALLOW_COPY_AND_ASSIGN(RtcpMessageVerification); -}; - -} // namespace - -class RtcpReceiverTest : public ::testing::Test { - protected: - RtcpReceiverTest() - : testing_clock_(new base::SimpleTestTickClock()), - task_runner_(new test::FakeSingleThreadTaskRunner( - testing_clock_.get())), - rtcp_receiver_(new RtcpReceiver(&mock_receiver_feedback_, - kSourceSsrc)) { - EXPECT_CALL(mock_receiver_feedback_, OnReceivedSenderReport(_)).Times(0); - EXPECT_CALL(mock_receiver_feedback_, OnReceiverReferenceTimeReport(_)) - .Times(0); - EXPECT_CALL(mock_receiver_feedback_, OnReceivedCastFeedback(_)).Times(0); - EXPECT_CALL(mock_receiver_feedback_, OnReceivedDelaySinceLastReport(_, _)) - .Times(0); - - expected_sender_info_.ntp_seconds = kNtpHigh; - expected_sender_info_.ntp_fraction = kNtpLow; - expected_sender_info_.rtp_timestamp = kRtpTimestamp; - expected_sender_info_.send_packet_count = kSendPacketCount; - expected_sender_info_.send_octet_count = kSendOctetCount; - - expected_report_block_.remote_ssrc = kSenderSsrc; - expected_report_block_.media_ssrc = kSourceSsrc; - expected_report_block_.fraction_lost = kLoss >> 24; - expected_report_block_.cumulative_lost = kLoss & 0xffffff; - expected_report_block_.extended_high_sequence_number = kExtendedMax; - expected_report_block_.jitter = kTestJitter; - expected_report_block_.last_sr = kLastSr; - expected_report_block_.delay_since_last_sr = kDelayLastSr; - expected_receiver_reference_report_.remote_ssrc = kSenderSsrc; - expected_receiver_reference_report_.ntp_seconds = kNtpHigh; - 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); - } - - scoped_ptr<base::SimpleTestTickClock> testing_clock_; - scoped_refptr<test::FakeSingleThreadTaskRunner> task_runner_; - MockRtcpReceiverFeedback mock_receiver_feedback_; - scoped_ptr<RtcpReceiver> rtcp_receiver_; - RtcpSenderInfo expected_sender_info_; - RtcpReportBlock expected_report_block_; - RtcpReceiverReferenceTimeReport expected_receiver_reference_report_; - - DISALLOW_COPY_AND_ASSIGN(RtcpReceiverTest); -}; - -TEST_F(RtcpReceiverTest, BrokenPacketIsIgnored) { - const uint8 bad_packet[] = {0, 0, 0, 0}; - InjectRtcpPacket(bad_packet, sizeof(bad_packet)); -} - -TEST_F(RtcpReceiverTest, InjectSenderReportPacket) { - TestRtcpPacketBuilder p; - p.AddSr(kSenderSsrc, 0); - - // Expected to be ignored since the sender ssrc does not match our - // remote ssrc. - 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.Data(), p.Length()); -} - -TEST_F(RtcpReceiverTest, InjectReceiveReportPacket) { - TestRtcpPacketBuilder p1; - p1.AddRr(kSenderSsrc, 1); - p1.AddRb(kUnknownSsrc); - - // Expected to be ignored since the source ssrc does not match our - // local ssrc. - InjectRtcpPacket(p1.Data(), p1.Length()); - - EXPECT_CALL(mock_receiver_feedback_, - OnReceivedDelaySinceLastReport(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.Data(), p2.Length()); -} - -TEST_F(RtcpReceiverTest, InjectSenderReportWithReportBlockPacket) { - TestRtcpPacketBuilder p1; - p1.AddSr(kSenderSsrc, 1); - p1.AddRb(kUnknownSsrc); - - // Sender report expected to be ignored since the sender ssrc does not match - // our remote ssrc. - // Report block expected to be ignored since the source ssrc does not match - // our local ssrc. - InjectRtcpPacket(p1.Data(), p1.Length()); - - EXPECT_CALL(mock_receiver_feedback_, - OnReceivedSenderReport(expected_sender_info_)).Times(1); - rtcp_receiver_->SetRemoteSSRC(kSenderSsrc); - - // Sender report expected to be pass through since the sender ssrc match our - // remote ssrc. - // Report block expected to be ignored since the source ssrc does not match - // our local ssrc. - InjectRtcpPacket(p1.Data(), p1.Length()); - - EXPECT_CALL(mock_receiver_feedback_, OnReceivedSenderReport(_)).Times(0); - EXPECT_CALL(mock_receiver_feedback_, - OnReceivedDelaySinceLastReport(kLastSr, kDelayLastSr)).Times(1); - - rtcp_receiver_->SetRemoteSSRC(0); - - TestRtcpPacketBuilder p2; - p2.AddSr(kSenderSsrc, 1); - p2.AddRb(kSourceSsrc); - - // Sender report expected to be ignored since the sender ssrc does not match - // our remote ssrc. - // Receiver report expected to be pass through since the sender ssrc match - // our local ssrc. - InjectRtcpPacket(p2.Data(), p2.Length()); - - EXPECT_CALL(mock_receiver_feedback_, - OnReceivedSenderReport(expected_sender_info_)).Times(1); - EXPECT_CALL(mock_receiver_feedback_, - OnReceivedDelaySinceLastReport(kLastSr, kDelayLastSr)).Times(1); - - rtcp_receiver_->SetRemoteSSRC(kSenderSsrc); - - // Sender report expected to be pass through since the sender ssrc match our - // remote ssrc. - // Receiver report expected to be pass through since the sender ssrc match - // our local ssrc. - InjectRtcpPacket(p2.Data(), p2.Length()); -} - -TEST_F(RtcpReceiverTest, InjectSenderReportPacketWithDlrr) { - TestRtcpPacketBuilder p; - p.AddSr(kSenderSsrc, 0); - p.AddXrHeader(kSenderSsrc); - p.AddXrUnknownBlock(); - p.AddXrExtendedDlrrBlock(kSenderSsrc); - p.AddXrUnknownBlock(); - - // Expected to be ignored since the source ssrc does not match our - // local ssrc. - InjectRtcpPacket(p.Data(), p.Length()); - - EXPECT_CALL(mock_receiver_feedback_, - OnReceivedSenderReport(expected_sender_info_)).Times(1); - EXPECT_CALL(mock_receiver_feedback_, - OnReceivedDelaySinceLastReport(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.Data(), p.Length()); -} - -TEST_F(RtcpReceiverTest, InjectReceiverReportPacketWithRrtr) { - TestRtcpPacketBuilder p1; - p1.AddRr(kSenderSsrc, 1); - p1.AddRb(kUnknownSsrc); - p1.AddXrHeader(kSenderSsrc); - p1.AddXrRrtrBlock(); - - // Expected to be ignored since the source ssrc does not match our - // local ssrc. - InjectRtcpPacket(p1.Data(), p1.Length()); - - EXPECT_CALL(mock_receiver_feedback_, - OnReceivedDelaySinceLastReport(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); - - TestRtcpPacketBuilder p2; - p2.AddRr(kSenderSsrc, 1); - p2.AddRb(kSourceSsrc); - p2.AddXrHeader(kSenderSsrc); - p2.AddXrRrtrBlock(); - - // Expected to be pass through since the sender ssrc match our local ssrc. - InjectRtcpPacket(p2.Data(), p2.Length()); -} - -TEST_F(RtcpReceiverTest, InjectReceiverReportPacketWithIntraFrameRequest) { - TestRtcpPacketBuilder p1; - p1.AddRr(kSenderSsrc, 1); - p1.AddRb(kUnknownSsrc); - - // Expected to be ignored since the source ssrc does not match our - // local ssrc. - InjectRtcpPacket(p1.Data(), p1.Length()); - - EXPECT_CALL(mock_receiver_feedback_, - OnReceivedDelaySinceLastReport(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.Data(), p2.Length()); -} - -TEST_F(RtcpReceiverTest, InjectReceiverReportPacketWithCastFeedback) { - TestRtcpPacketBuilder p1; - p1.AddRr(kSenderSsrc, 1); - p1.AddRb(kUnknownSsrc); - p1.AddCast(kSenderSsrc, kUnknownSsrc, kTargetDelay); - - // Expected to be ignored since the source ssrc does not match our - // local ssrc. - InjectRtcpPacket(p1.Data(), p1.Length()); - - EXPECT_CALL(mock_receiver_feedback_, - OnReceivedDelaySinceLastReport(kLastSr, kDelayLastSr)).Times(1); - EXPECT_CALL(mock_receiver_feedback_, OnReceivedCastFeedback(_)).Times(1); - - // Enable receiving the cast feedback. - rtcp_receiver_->SetRemoteSSRC(kSenderSsrc); - - TestRtcpPacketBuilder p2; - p2.AddRr(kSenderSsrc, 1); - p2.AddRb(kSourceSsrc); - p2.AddCast(kSenderSsrc, kSourceSsrc, kTargetDelay); - - // Expected to be pass through since the sender ssrc match our local ssrc. - InjectRtcpPacket(p2.Data(), p2.Length()); -} - -TEST_F(RtcpReceiverTest, InjectReceiverReportPacketWithCastVerification) { - RtcpMessageVerification verification; - RtcpReceiver rtcp_receiver(&verification, kSourceSsrc); - - EXPECT_CALL(verification, - OnReceivedDelaySinceLastReport(kLastSr, kDelayLastSr)).Times(1); - - // Enable receiving the cast feedback. - rtcp_receiver.SetRemoteSSRC(kSenderSsrc); - - TestRtcpPacketBuilder p; - p.AddRr(kSenderSsrc, 1); - p.AddRb(kSourceSsrc); - p.AddCast(kSenderSsrc, kSourceSsrc, kTargetDelay); - - // Expected to be pass through since the sender ssrc match our local ssrc. - RtcpParser rtcp_parser(p.Data(), p.Length()); - rtcp_receiver.IncomingRtcpPacket(&rtcp_parser); - - EXPECT_TRUE(verification.OnReceivedCastFeedbackCalled()); -} - -TEST_F(RtcpReceiverTest, InjectReceiverReportWithReceiverLogVerificationBase) { - static const uint32 kTimeBaseMs = 12345678; - static const uint32 kTimeDelayMs = 10; - static const uint32 kDelayDeltaMs = 123; - base::SimpleTestTickClock testing_clock; - testing_clock.Advance(base::TimeDelta::FromMilliseconds(kTimeBaseMs)); - - RtcpMessageVerification verification; - RtcpReceiver rtcp_receiver(&verification, - kSourceSsrc); - rtcp_receiver.SetRemoteSSRC(kSenderSsrc); - rtcp_receiver.SetCastReceiverEventHistorySize(100); - - RtcpReceiverLogMessage receiver_log; - RtcpReceiverFrameLogMessage frame_log(kRtpTimestamp); - RtcpReceiverEventLogMessage event_log; - - 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 = 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); - - verification.SetExpectedReceiverLog(receiver_log); - - TestRtcpPacketBuilder p; - p.AddRr(kSenderSsrc, 1); - p.AddRb(kSourceSsrc); - p.AddReceiverLog(kSenderSsrc); - 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(verification, - OnReceivedDelaySinceLastReport(kLastSr, kDelayLastSr)).Times(1); - - RtcpParser rtcp_parser(p.Data(), p.Length()); - rtcp_receiver.IncomingRtcpPacket(&rtcp_parser); - - EXPECT_TRUE(verification.OnReceivedReceiverLogCalled()); -} - -TEST_F(RtcpReceiverTest, InjectReceiverReportWithReceiverLogVerificationMulti) { - static const uint32 kTimeBaseMs = 12345678; - static const uint32 kTimeDelayMs = 10; - static const uint32 kDelayDeltaMs = 123; - base::SimpleTestTickClock testing_clock; - testing_clock.Advance(base::TimeDelta::FromMilliseconds(kTimeBaseMs)); - - RtcpMessageVerification verification; - RtcpReceiver rtcp_receiver(&verification, - kSourceSsrc); - rtcp_receiver.SetRemoteSSRC(kSenderSsrc); - - RtcpReceiverLogMessage receiver_log; - - for (int j = 0; j < 100; ++j) { - RtcpReceiverFrameLogMessage frame_log(kRtpTimestamp); - RtcpReceiverEventLogMessage event_log; - 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); - receiver_log.push_back(frame_log); - testing_clock.Advance(base::TimeDelta::FromMilliseconds(kTimeDelayMs)); - } - - verification.SetExpectedReceiverLog(receiver_log); - - TestRtcpPacketBuilder p; - p.AddRr(kSenderSsrc, 1); - p.AddRb(kSourceSsrc); - p.AddReceiverLog(kSenderSsrc); - for (int i = 0; i < 100; ++i) { - p.AddReceiverFrameLog(kRtpTimestamp, 1, kTimeBaseMs + i * kTimeDelayMs); - p.AddReceiverEventLog(kDelayDeltaMs, FRAME_ACK_SENT, 0); - } - - EXPECT_CALL(verification, - OnReceivedDelaySinceLastReport(kLastSr, kDelayLastSr)).Times(1); - - RtcpParser rtcp_parser(p.Data(), p.Length()); - rtcp_receiver.IncomingRtcpPacket(&rtcp_parser); - - EXPECT_TRUE(verification.OnReceivedReceiverLogCalled()); -} - -} // namespace cast -} // namespace media diff --git a/media/cast/net/rtcp/rtcp_sender.cc b/media/cast/net/rtcp/rtcp_sender.cc index 244c4d3983..4ca6eb581b 100644 --- a/media/cast/net/rtcp/rtcp_sender.cc +++ b/media/cast/net/rtcp/rtcp_sender.cc @@ -154,32 +154,21 @@ RtcpSender::RtcpSender(PacedPacketSender* outgoing_transport, RtcpSender::~RtcpSender() {} void RtcpSender::SendRtcpFromRtpReceiver( - uint32 packet_type_flags, const RtcpReportBlock* report_block, const RtcpReceiverReferenceTimeReport* rrtr, const RtcpCastMessage* cast_message, const ReceiverRtcpEventSubscriber::RtcpEventMultiMap* rtcp_events, base::TimeDelta target_delay) { - if (packet_type_flags & kRtcpDlrr) { - NOTREACHED() << "Invalid argument"; - } PacketRef packet(new base::RefCountedData<Packet>); packet->data.reserve(kMaxIpPacketSize); - if (packet_type_flags & kRtcpRr) { + if (report_block) BuildRR(report_block, &packet->data); - } - if (packet_type_flags & kRtcpRrtr) { - DCHECK(rrtr) << "Invalid argument"; + if (rrtr) BuildRrtr(rrtr, &packet->data); - } - if (packet_type_flags & kRtcpCast) { - DCHECK(cast_message) << "Invalid argument"; + if (cast_message) BuildCast(cast_message, target_delay, &packet->data); - } - if (packet_type_flags & kRtcpReceiverLog) { - DCHECK(rtcp_events) << "Invalid argument"; + if (rtcp_events) BuildReceiverLog(*rtcp_events, &packet->data); - } if (packet->data.empty()) { NOTREACHED() << "Empty packet."; @@ -190,23 +179,11 @@ void RtcpSender::SendRtcpFromRtpReceiver( } void RtcpSender::SendRtcpFromRtpSender( - uint32 packet_type_flags, - const RtcpSenderInfo& sender_info, - const RtcpDlrrReportBlock& dlrr) { - if (packet_type_flags & kRtcpRr || - packet_type_flags & kRtcpRrtr || - packet_type_flags & kRtcpCast || - packet_type_flags & kRtcpReceiverLog) { - NOTREACHED() << "Invalid argument"; - } + const RtcpSenderInfo& sender_info) { PacketRef packet(new base::RefCountedData<Packet>); packet->data.reserve(kMaxIpPacketSize); - if (packet_type_flags & kRtcpSr) { - BuildSR(sender_info, &packet->data); - } - if (packet_type_flags & kRtcpDlrr) { - BuildDlrrRb(dlrr, &packet->data); - } + BuildSR(sender_info, &packet->data); + if (packet->data.empty()) { NOTREACHED() << "Empty packet."; return; // Sanity - don't send empty packets. diff --git a/media/cast/net/rtcp/rtcp_sender.h b/media/cast/net/rtcp/rtcp_sender.h index bfbd0cf3b6..06b11d4844 100644 --- a/media/cast/net/rtcp/rtcp_sender.h +++ b/media/cast/net/rtcp/rtcp_sender.h @@ -51,7 +51,6 @@ class RtcpSender { // TODO(hclam): This method should be to build a packet instead of // sending it. void SendRtcpFromRtpReceiver( - uint32 packet_type_flags, const RtcpReportBlock* report_block, const RtcpReceiverReferenceTimeReport* rrtr, const RtcpCastMessage* cast_message, @@ -60,9 +59,7 @@ class RtcpSender { // TODO(hclam): This method should be to build a packet instead of // sending it. - void SendRtcpFromRtpSender(uint32 packet_type_flags, - const RtcpSenderInfo& sender_info, - const RtcpDlrrReportBlock& dlrr); + void SendRtcpFromRtpSender(const RtcpSenderInfo& sender_info); private: void BuildRR(const RtcpReportBlock* report_block, diff --git a/media/cast/net/rtcp/rtcp_sender_unittest.cc b/media/cast/net/rtcp/rtcp_sender_unittest.cc index a9419e4c53..6dbea4d94e 100644 --- a/media/cast/net/rtcp/rtcp_sender_unittest.cc +++ b/media/cast/net/rtcp/rtcp_sender_unittest.cc @@ -62,7 +62,7 @@ class TestRtcpTransport : public PacedPacketSender { } virtual bool ResendPackets( const SendPacketVector& packets, - base::TimeDelta dedupe_window) OVERRIDE { + const DedupInfo& dedup_info) OVERRIDE { return false; } @@ -105,16 +105,6 @@ class RtcpSenderTest : public ::testing::Test { }; TEST_F(RtcpSenderTest, RtcpReceiverReport) { - // Empty receiver report. - TestRtcpPacketBuilder p1; - p1.AddRr(kSendingSsrc, 0); - test_transport_.SetExpectedRtcpPacket(p1.GetPacket()); - - rtcp_sender_->SendRtcpFromRtpReceiver( - kRtcpRr, NULL, NULL, NULL, NULL, kDefaultDelay); - - EXPECT_EQ(1, test_transport_.packet_count()); - // Receiver report with report block. TestRtcpPacketBuilder p2; p2.AddRr(kSendingSsrc, 1); @@ -124,9 +114,9 @@ TEST_F(RtcpSenderTest, RtcpReceiverReport) { RtcpReportBlock report_block = GetReportBlock(); rtcp_sender_->SendRtcpFromRtpReceiver( - kRtcpRr, &report_block, NULL, NULL, NULL, kDefaultDelay); + &report_block, NULL, NULL, NULL, kDefaultDelay); - EXPECT_EQ(2, test_transport_.packet_count()); + EXPECT_EQ(1, test_transport_.packet_count()); } TEST_F(RtcpSenderTest, RtcpReceiverReportWithRrtr) { @@ -145,7 +135,6 @@ TEST_F(RtcpSenderTest, RtcpReceiverReportWithRrtr) { rrtr.ntp_fraction = kNtpLow; rtcp_sender_->SendRtcpFromRtpReceiver( - kRtcpRr | kRtcpRrtr, &report_block, &rrtr, NULL, @@ -177,7 +166,6 @@ TEST_F(RtcpSenderTest, RtcpReceiverReportWithCast) { missing_packets; rtcp_sender_->SendRtcpFromRtpReceiver( - kRtcpRr | kRtcpCast, &report_block, NULL, &cast_message, @@ -214,7 +202,6 @@ TEST_F(RtcpSenderTest, RtcpReceiverReportWithRrtraAndCastMessage) { missing_packets; rtcp_sender_->SendRtcpFromRtpReceiver( - kRtcpRr | kRtcpRrtr | kRtcpCast, &report_block, &rrtr, &cast_message, @@ -257,8 +244,6 @@ TEST_F(RtcpSenderTest, RtcpReceiverReportWithRrtrCastMessageAndLog) { ReceiverRtcpEventSubscriber::RtcpEventMultiMap rtcp_events; rtcp_sender_->SendRtcpFromRtpReceiver( - kRtcpRr | kRtcpRrtr | kRtcpCast | - kRtcpReceiverLog, &report_block, &rrtr, &cast_message, @@ -294,8 +279,6 @@ TEST_F(RtcpSenderTest, RtcpReceiverReportWithRrtrCastMessageAndLog) { EXPECT_EQ(2u, rtcp_events.size()); rtcp_sender_->SendRtcpFromRtpReceiver( - kRtcpRr | kRtcpRrtr | kRtcpCast | - kRtcpReceiverLog, &report_block, &rrtr, &cast_message, @@ -362,7 +345,6 @@ TEST_F(RtcpSenderTest, RtcpReceiverReportWithOversizedFrameLog) { event_subscriber.GetRtcpEventsAndReset(&rtcp_events); rtcp_sender_->SendRtcpFromRtpReceiver( - kRtcpRr | kRtcpReceiverLog, &report_block, NULL, NULL, @@ -418,7 +400,6 @@ TEST_F(RtcpSenderTest, RtcpReceiverReportWithTooManyLogFrames) { event_subscriber.GetRtcpEventsAndReset(&rtcp_events); rtcp_sender_->SendRtcpFromRtpReceiver( - kRtcpRr | kRtcpReceiverLog, &report_block, NULL, NULL, @@ -468,7 +449,6 @@ TEST_F(RtcpSenderTest, RtcpReceiverReportWithOldLogFrames) { event_subscriber.GetRtcpEventsAndReset(&rtcp_events); rtcp_sender_->SendRtcpFromRtpReceiver( - kRtcpRr | kRtcpReceiverLog, &report_block, NULL, NULL, @@ -526,7 +506,6 @@ TEST_F(RtcpSenderTest, RtcpReceiverReportRedundancy) { event_subscriber.GetRtcpEventsAndReset(&rtcp_events); rtcp_sender_->SendRtcpFromRtpReceiver( - kRtcpRr | kRtcpReceiverLog, &report_block, NULL, NULL, @@ -549,45 +528,12 @@ TEST_F(RtcpSenderTest, RtcpSenderReport) { sender_info.send_packet_count = kSendPacketCount; sender_info.send_octet_count = kSendOctetCount; - RtcpDlrrReportBlock dlrr_rb; - dlrr_rb.last_rr = kLastRr; - dlrr_rb.delay_since_last_rr = kDelayLastRr; - // Sender report. TestRtcpPacketBuilder p; p.AddSr(kSendingSsrc, 0); test_transport_.SetExpectedRtcpPacket(p.GetPacket().Pass()); - rtcp_sender_->SendRtcpFromRtpSender(kRtcpSr, - sender_info, - dlrr_rb); - - EXPECT_EQ(1, 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 + dlrr. - TestRtcpPacketBuilder p1; - p1.AddSr(kSendingSsrc, 0); - p1.AddXrHeader(kSendingSsrc); - p1.AddXrDlrrBlock(kSendingSsrc); - test_transport_.SetExpectedRtcpPacket(p1.GetPacket().Pass()); - - RtcpDlrrReportBlock dlrr_rb; - dlrr_rb.last_rr = kLastRr; - dlrr_rb.delay_since_last_rr = kDelayLastRr; - - rtcp_sender_->SendRtcpFromRtpSender( - kRtcpSr | kRtcpDlrr, - sender_info, - dlrr_rb); + rtcp_sender_->SendRtcpFromRtpSender(sender_info); EXPECT_EQ(1, test_transport_.packet_count()); } diff --git a/media/cast/net/rtcp/rtcp_unittest.cc b/media/cast/net/rtcp/rtcp_unittest.cc index 5ea9ee101e..baa0699bdd 100644 --- a/media/cast/net/rtcp/rtcp_unittest.cc +++ b/media/cast/net/rtcp/rtcp_unittest.cc @@ -9,7 +9,6 @@ #include "media/cast/net/cast_transport_config.h" #include "media/cast/net/cast_transport_sender_impl.h" #include "media/cast/net/pacing/paced_sender.h" -#include "media/cast/net/rtcp/mock_rtcp_receiver_feedback.h" #include "media/cast/net/rtcp/rtcp.h" #include "media/cast/net/rtcp/test_rtcp_packet_builder.h" #include "media/cast/test/fake_single_thread_task_runner.h" @@ -99,8 +98,7 @@ class LocalRtcpTransport : public PacedPacketSender { } virtual bool ResendPackets( - const SendPacketVector& packets, - base::TimeDelta dedupe_window) OVERRIDE { + const SendPacketVector& packets, const DedupInfo& dedup_info) OVERRIDE { return false; } @@ -272,25 +270,13 @@ TEST_F(RtcpTest, RttReducedSizeRtcp) { 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)); rtcp_sender.SendRtcpFromRtpSender(testing_clock_->NowTicks(), 1, 1, 1); RunTasks(33); rtcp_receiver.SendRtcpFromRtpReceiver(NULL, base::TimeDelta(), NULL, &stats_); 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, 1, 1); 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) { @@ -322,7 +308,6 @@ TEST_F(RtcpTest, 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)); rtcp_sender.SendRtcpFromRtpSender(testing_clock_->NowTicks(), 1, 1, 1); RunTasks(33); @@ -331,7 +316,6 @@ TEST_F(RtcpTest, Rtt) { EXPECT_TRUE(rtcp_sender.Rtt(&rtt, &avg_rtt, &min_rtt, &max_rtt)); RunTasks(33); - EXPECT_FALSE(rtcp_receiver.Rtt(&rtt, &avg_rtt, &min_rtt, &max_rtt)); RunTasks(33); EXPECT_NEAR(2 * kAddedDelay, rtt.InMilliseconds(), 2); @@ -341,11 +325,6 @@ TEST_F(RtcpTest, Rtt) { rtcp_sender.SendRtcpFromRtpSender(testing_clock_->NowTicks(), 2, 1, 1); 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(); @@ -359,13 +338,6 @@ TEST_F(RtcpTest, Rtt) { rtcp_sender.SendRtcpFromRtpSender(testing_clock_->NowTicks(), 3, 1, 1); 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(), 2); - EXPECT_NEAR(2 * kAddedDelay, max_rtt.InMilliseconds(), 2); rtcp_receiver.SendRtcpFromRtpReceiver(NULL, base::TimeDelta(), NULL, &stats_); EXPECT_TRUE(rtcp_sender.Rtt(&rtt, &avg_rtt, &min_rtt, &max_rtt)); @@ -413,11 +385,6 @@ TEST_F(RtcpTest, RttWithPacketLoss) { 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_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_to_sender_.set_short_delay(); sender_to_receiver_.set_short_delay(); @@ -427,8 +394,6 @@ TEST_F(RtcpTest, RttWithPacketLoss) { rtcp_sender.SendRtcpFromRtpSender(testing_clock_->NowTicks(), 1, 1, 1); RunTasks(33); - EXPECT_TRUE(rtcp_receiver.Rtt(&rtt, &avg_rtt, &min_rtt, &max_rtt)); - EXPECT_NEAR(kAddedDelay + kAddedShortDelay, rtt.InMilliseconds(), 2); } TEST_F(RtcpTest, NtpAndTime) { diff --git a/media/cast/net/rtcp/rtcp_utility.cc b/media/cast/net/rtcp/rtcp_utility.cc index 4f99cd4174..a1a6a48c1d 100644 --- a/media/cast/net/rtcp/rtcp_utility.cc +++ b/media/cast/net/rtcp/rtcp_utility.cc @@ -4,198 +4,66 @@ #include "media/cast/net/rtcp/rtcp_utility.h" -#include "base/big_endian.h" #include "base/logging.h" #include "media/cast/net/cast_transport_defines.h" namespace media { namespace cast { -RtcpParser::RtcpParser(const uint8* rtcpData, size_t rtcpDataLength) - : rtcp_data_begin_(rtcpData), - rtcp_data_end_(rtcpData + rtcpDataLength), - valid_packet_(false), - rtcp_data_(rtcpData), - rtcp_block_end_(NULL), - state_(kStateTopLevel), - number_of_blocks_(0), - field_type_(kRtcpNotValidCode) { - memset(&field_, 0, sizeof(field_)); - Validate(); +RtcpParser::RtcpParser(uint32 local_ssrc, uint32 remote_ssrc) : + local_ssrc_(local_ssrc), + remote_ssrc_(remote_ssrc), + has_sender_report_(false), + has_last_report_(false), + has_cast_message_(false), + has_receiver_reference_time_report_(false) { } RtcpParser::~RtcpParser() {} -RtcpFieldTypes RtcpParser::FieldType() const { return field_type_; } - -const RtcpField& RtcpParser::Field() const { return field_; } - -RtcpFieldTypes RtcpParser::Begin() { - rtcp_data_ = rtcp_data_begin_; - return Iterate(); -} - -RtcpFieldTypes RtcpParser::Iterate() { - // Reset packet type - field_type_ = kRtcpNotValidCode; - - if (!IsValid()) - return kRtcpNotValidCode; - - switch (state_) { - case kStateTopLevel: - IterateTopLevel(); - break; - case kStateReportBlock: - IterateReportBlockItem(); - break; - case kStateApplicationSpecificCastReceiverFrameLog: - IterateCastReceiverLogFrame(); - break; - case kStateApplicationSpecificCastReceiverEventLog: - IterateCastReceiverLogEvent(); - break; - case kStateExtendedReportBlock: - IterateExtendedReportItem(); - break; - case kStateExtendedReportDelaySinceLastReceiverReport: - IterateExtendedReportDelaySinceLastReceiverReportItem(); - break; - case kStatePayloadSpecificApplication: - IteratePayloadSpecificAppItem(); - break; - case kStatePayloadSpecificCast: - IteratePayloadSpecificCastItem(); - break; - case kStatePayloadSpecificCastNack: - IteratePayloadSpecificCastNackItem(); - break; - } - return field_type_; -} - -void RtcpParser::IterateTopLevel() { - for (;;) { +bool RtcpParser::Parse(base::BigEndianReader* reader) { + while (reader->remaining()) { RtcpCommonHeader header; + if (!ParseCommonHeader(reader, &header)) + return false; - bool success = RtcpParseCommonHeader(rtcp_data_, rtcp_data_end_, &header); - if (!success) - return; - - rtcp_block_end_ = rtcp_data_ + header.length_in_octets; - - if (rtcp_block_end_ > rtcp_data_end_) - return; // Bad block! + base::StringPiece tmp; + if (!reader->ReadPiece(&tmp, header.length_in_octets - 4)) + return false; + base::BigEndianReader chunk(tmp.data(), tmp.size()); switch (header.PT) { case kPacketTypeSenderReport: - // number of Report blocks - number_of_blocks_ = header.IC; - ParseSR(); - return; + if (!ParseSR(&chunk, header)) + return false; + break; + case kPacketTypeReceiverReport: - // number of Report blocks - number_of_blocks_ = header.IC; - ParseRR(); - return; + if (!ParseRR(&chunk, header)) + return false; + break; + case kPacketTypeApplicationDefined: - if (!ParseApplicationDefined(header.IC)) { - // Nothing supported found, continue to next block! - break; - } - return; - case kPacketTypeGenericRtpFeedback: // Fall through! + if (!ParseApplicationDefined(&chunk, header)) + return false; + break; + case kPacketTypePayloadSpecific: - if (!ParseFeedBackCommon(header)) { - // Nothing supported found, continue to next block! - break; - } - return; + if (!ParseFeedbackCommon(&chunk, header)) + return false; + break; + case kPacketTypeXr: - if (!ParseExtendedReport()) { - break; // Nothing supported found, continue to next block! - } - return; - default: - // Not supported! Skip! - EndCurrentBlock(); + if (!ParseExtendedReport(&chunk, header)) + return false; break; } } + return true; } -void RtcpParser::IterateReportBlockItem() { - bool success = ParseReportBlockItem(); - if (!success) - Iterate(); -} - -void RtcpParser::IterateExtendedReportItem() { - bool success = ParseExtendedReportItem(); - if (!success) - Iterate(); -} - -void RtcpParser::IterateExtendedReportDelaySinceLastReceiverReportItem() { - bool success = ParseExtendedReportDelaySinceLastReceiverReport(); - if (!success) - Iterate(); -} - -void RtcpParser::IteratePayloadSpecificAppItem() { - bool success = ParsePayloadSpecificAppItem(); - if (!success) - Iterate(); -} - -void RtcpParser::IteratePayloadSpecificCastItem() { - bool success = ParsePayloadSpecificCastItem(); - if (!success) - Iterate(); -} - -void RtcpParser::IteratePayloadSpecificCastNackItem() { - bool success = ParsePayloadSpecificCastNackItem(); - if (!success) - Iterate(); -} - -void RtcpParser::IterateCastReceiverLogFrame() { - bool success = ParseCastReceiverLogFrameItem(); - if (!success) - Iterate(); -} - -void RtcpParser::IterateCastReceiverLogEvent() { - bool success = ParseCastReceiverLogEventItem(); - if (!success) - Iterate(); -} - -void RtcpParser::Validate() { - if (rtcp_data_ == NULL) - return; // NOT VALID - - RtcpCommonHeader header; - bool success = - RtcpParseCommonHeader(rtcp_data_begin_, rtcp_data_end_, &header); - - if (!success) - return; // NOT VALID! - - valid_packet_ = true; -} - -bool RtcpParser::IsValid() const { return valid_packet_; } - -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; - +bool RtcpParser::ParseCommonHeader(base::BigEndianReader* reader, + RtcpCommonHeader* parsed_header) { // 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 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ @@ -204,448 +72,265 @@ bool RtcpParser::RtcpParseCommonHeader(const uint8* data_begin, // // Common header for all Rtcp packets, 4 octets. - if ((data_end - data_begin) < 4) + uint8 byte; + if (!reader->ReadU8(&byte)) return false; + parsed_header->V = byte >> 6; + parsed_header->P = ((byte & 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]; + // Check if RTP version field == 2. + if (parsed_header->V != 2) + return false; - parsed_header->length_in_octets = - ((data_begin[2] << 8) + data_begin[3] + 1) * 4; + parsed_header->IC = byte & 0x1f; + if (!reader->ReadU8(&parsed_header->PT)) + return false; - if (parsed_header->length_in_octets == 0) + uint16 bytes; + if (!reader->ReadU16(&bytes)) return false; - // Check if RTP version field == 2. - if (parsed_header->V != 2) + parsed_header->length_in_octets = (static_cast<size_t>(bytes) + 1) * 4; + + if (parsed_header->length_in_octets == 0) return false; return true; } -bool RtcpParser::ParseRR() { - ptrdiff_t length = rtcp_block_end_ - rtcp_data_; - if (length < 8) +bool RtcpParser::ParseSR(base::BigEndianReader* reader, + const RtcpCommonHeader& header) { + uint32 sender_ssrc; + if (!reader->ReadU32(&sender_ssrc)) return false; - field_type_ = kRtcpRrCode; + if (sender_ssrc != remote_ssrc_) + return true; + + uint32 tmp; + if (!reader->ReadU32(&sender_report_.ntp_seconds) || + !reader->ReadU32(&sender_report_.ntp_fraction) || + !reader->ReadU32(&sender_report_.rtp_timestamp) || + !reader->ReadU32(&sender_report_.send_packet_count) || + !reader->ReadU32(&tmp)) + return false; + sender_report_.send_octet_count = tmp; + has_sender_report_ = true; - 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_; - rtcp_data_ += 8; + for (size_t block = 0; block < header.IC; block++) + if (!ParseReportBlock(reader)) + return false; - // State transition - state_ = kStateReportBlock; return true; } -bool RtcpParser::ParseSR() { - ptrdiff_t length = rtcp_block_end_ - rtcp_data_; - if (length < 28) { - EndCurrentBlock(); +bool RtcpParser::ParseRR(base::BigEndianReader* reader, + const RtcpCommonHeader& header) { + uint32 receiver_ssrc; + if (!reader->ReadU32(&receiver_ssrc)) return false; - } - field_type_ = kRtcpSrCode; - - 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); - big_endian_reader.ReadU32(&field_.sender_report.ntp_least_significant); - big_endian_reader.ReadU32(&field_.sender_report.rtp_timestamp); - big_endian_reader.ReadU32(&field_.sender_report.sender_packet_count); - big_endian_reader.ReadU32(&field_.sender_report.sender_octet_count); - field_.sender_report.number_of_report_blocks = number_of_blocks_; - rtcp_data_ += 28; - - if (number_of_blocks_ != 0) { - // State transition. - state_ = kStateReportBlock; - } else { - // Don't go to state report block item if 0 report blocks. - state_ = kStateTopLevel; - EndCurrentBlock(); - } + + if (receiver_ssrc != remote_ssrc_) + return true; + + for (size_t block = 0; block < header.IC; block++) + if (!ParseReportBlock(reader)) + return false; + return true; } -bool RtcpParser::ParseReportBlockItem() { - ptrdiff_t length = rtcp_block_end_ - rtcp_data_; - if (length < 24 || number_of_blocks_ <= 0) { - state_ = kStateTopLevel; - EndCurrentBlock(); +bool RtcpParser::ParseReportBlock(base::BigEndianReader* reader) { + uint32 ssrc, last_report, delay; + if (!reader->ReadU32(&ssrc) || + !reader->Skip(12) || + !reader->ReadU32(&last_report) || + !reader->ReadU32(&delay)) return false; + + if (ssrc == local_ssrc_) { + last_report_ = last_report; + delay_since_last_report_ = delay; + has_last_report_ = true; } - 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); - - uint8 temp_number_of_packets_lost; - big_endian_reader.ReadU8(&temp_number_of_packets_lost); - field_.report_block_item.cumulative_number_of_packets_lost = - temp_number_of_packets_lost << 16; - big_endian_reader.ReadU8(&temp_number_of_packets_lost); - field_.report_block_item.cumulative_number_of_packets_lost += - temp_number_of_packets_lost << 8; - big_endian_reader.ReadU8(&temp_number_of_packets_lost); - field_.report_block_item.cumulative_number_of_packets_lost += - temp_number_of_packets_lost; - - big_endian_reader.ReadU32( - &field_.report_block_item.extended_highest_sequence_number); - big_endian_reader.ReadU32(&field_.report_block_item.jitter); - big_endian_reader.ReadU32(&field_.report_block_item.last_sender_report); - big_endian_reader.ReadU32(&field_.report_block_item.delay_last_sender_report); - rtcp_data_ += 24; - - number_of_blocks_--; - field_type_ = kRtcpReportBlockItemCode; return true; } -bool RtcpParser::ParseApplicationDefined(uint8 subtype) { - ptrdiff_t length = rtcp_block_end_ - rtcp_data_; - if (length < 16 || subtype != kReceiverLogSubtype) { - state_ = kStateTopLevel; - EndCurrentBlock(); - return false; - } - +bool RtcpParser::ParseApplicationDefined(base::BigEndianReader* reader, + const RtcpCommonHeader& header) { uint32 sender_ssrc; uint32 name; + if (!reader->ReadU32(&sender_ssrc) || + !reader->ReadU32(&name)) + return false; - 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); + if (sender_ssrc != remote_ssrc_) + return true; - if (name != kCast) { - state_ = kStateTopLevel; - EndCurrentBlock(); + if (name != kCast) return false; - } - rtcp_data_ += 12; - switch (subtype) { + + switch (header.IC /* subtype */ ) { case kReceiverLogSubtype: - state_ = kStateApplicationSpecificCastReceiverFrameLog; - field_type_ = kRtcpApplicationSpecificCastReceiverLogCode; - field_.cast_receiver_log.sender_ssrc = sender_ssrc; + if (!ParseCastReceiverLogFrameItem(reader)) + return false; break; - default: - NOTREACHED(); } return true; } -bool RtcpParser::ParseCastReceiverLogFrameItem() { - ptrdiff_t length = rtcp_block_end_ - rtcp_data_; - if (length < 12) { - state_ = kStateTopLevel; - EndCurrentBlock(); - return false; - } - uint32 rtp_timestamp; - uint32 data; - base::BigEndianReader big_endian_reader( - reinterpret_cast<const char*>(rtcp_data_), length); - big_endian_reader.ReadU32(&rtp_timestamp); - big_endian_reader.ReadU32(&data); - - rtcp_data_ += 8; - - field_.cast_receiver_log.rtp_timestamp = rtp_timestamp; - // We have 24 LSB of the event timestamp base on the wire. - field_.cast_receiver_log.event_timestamp_base = data & 0xffffff; - - number_of_blocks_ = 1 + static_cast<uint8>(data >> 24); - state_ = kStateApplicationSpecificCastReceiverEventLog; - field_type_ = kRtcpApplicationSpecificCastReceiverLogFrameCode; - return true; -} +bool RtcpParser::ParseCastReceiverLogFrameItem( + base::BigEndianReader* reader) { -bool RtcpParser::ParseCastReceiverLogEventItem() { - ptrdiff_t length = rtcp_block_end_ - rtcp_data_; - if (length < 4) { - state_ = kStateTopLevel; - EndCurrentBlock(); - return false; - } - if (number_of_blocks_ == 0) { - // Continue parsing the next receiver frame event. - state_ = kStateApplicationSpecificCastReceiverFrameLog; - return false; - } - number_of_blocks_--; - - uint16 delay_delta_or_packet_id; - uint16 event_type_and_timestamp_delta; - 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); - - rtcp_data_ += 4; - - field_.cast_receiver_log.event = - static_cast<uint8>(event_type_and_timestamp_delta >> 12); - // 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; - - field_type_ = kRtcpApplicationSpecificCastReceiverLogEventCode; - return true; -} + while (reader->remaining()) { + uint32 rtp_timestamp; + uint32 data; + if (!reader->ReadU32(&rtp_timestamp) || + !reader->ReadU32(&data)) + return false; -bool RtcpParser::ParseFeedBackCommon(const RtcpCommonHeader& header) { - DCHECK((header.PT == kPacketTypeGenericRtpFeedback) || - (header.PT == kPacketTypePayloadSpecific)) - << "Invalid state"; + // We have 24 LSB of the event timestamp base on the wire. + base::TimeTicks event_timestamp_base = base::TimeTicks() + + base::TimeDelta::FromMilliseconds(data & 0xffffff); - ptrdiff_t length = rtcp_block_end_ - rtcp_data_; + size_t num_events = 1 + static_cast<uint8>(data >> 24); - if (length < 12) { // 4 * 3, RFC4585 section 6.1 - EndCurrentBlock(); - return false; - } + RtcpReceiverFrameLogMessage frame_log(rtp_timestamp); + for (size_t event = 0; event < num_events; event++) { + uint16 delay_delta_or_packet_id; + uint16 event_type_and_timestamp_delta; + if (!reader->ReadU16(&delay_delta_or_packet_id) || + !reader->ReadU16(&event_type_and_timestamp_delta)) + return false; - uint32 sender_ssrc; - uint32 media_ssrc; - 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 == kPacketTypePayloadSpecific) { - // Payload specific feedback - switch (header.IC) { - case 1: - // PLI - break; - case 2: - // SLI. - break; - case 3: - // RPSI. - break; - case 4: - // FIR. - break; - case 15: - field_type_ = kRtcpPayloadSpecificAppCode; - field_.application_specific.sender_ssrc = sender_ssrc; - field_.application_specific.media_ssrc = media_ssrc; - state_ = kStatePayloadSpecificApplication; - return true; - default: - break; + RtcpReceiverEventLogMessage event_log; + event_log.type = TranslateToLogEventFromWireFormat( + static_cast<uint8>(event_type_and_timestamp_delta >> 12)); + event_log.event_timestamp = + event_timestamp_base + + base::TimeDelta::FromMilliseconds( + event_type_and_timestamp_delta & 0xfff); + if (event_log.type == PACKET_RECEIVED) { + event_log.packet_id = delay_delta_or_packet_id; + } else { + event_log.delay_delta = base::TimeDelta::FromMilliseconds( + delay_delta_or_packet_id); + } + frame_log.event_log_messages_.push_back(event_log); } - EndCurrentBlock(); - return false; - } else { - DCHECK(false) << "Invalid state"; - EndCurrentBlock(); - return false; - } -} - -bool RtcpParser::ParsePayloadSpecificAppItem() { - ptrdiff_t length = rtcp_block_end_ - rtcp_data_; - - if (length < 4) { - state_ = kStateTopLevel; - EndCurrentBlock(); - return false; - } - uint32 name; - base::BigEndianReader big_endian_reader( - reinterpret_cast<const char*>(rtcp_data_), length); - big_endian_reader.ReadU32(&name); - rtcp_data_ += 4; - - if (name == kCast) { - field_type_ = kRtcpPayloadSpecificCastCode; - state_ = kStatePayloadSpecificCast; - return true; + receiver_log_.push_back(frame_log); } - state_ = kStateTopLevel; - EndCurrentBlock(); - return false; -} -bool RtcpParser::ParsePayloadSpecificCastItem() { - ptrdiff_t length = rtcp_block_end_ - rtcp_data_; - if (length < 4) { - state_ = kStateTopLevel; - EndCurrentBlock(); - return false; - } - field_type_ = kRtcpPayloadSpecificCastCode; - - 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; - - if (field_.cast_item.number_of_lost_fields != 0) { - // State transition - state_ = kStatePayloadSpecificCastNack; - } else { - // Don't go to state cast nack item if got 0 fields. - state_ = kStateTopLevel; - EndCurrentBlock(); - } return true; } -bool RtcpParser::ParsePayloadSpecificCastNackItem() { - ptrdiff_t length = rtcp_block_end_ - rtcp_data_; - if (length < 4) { - state_ = kStateTopLevel; - EndCurrentBlock(); - return false; +// RFC 4585. +bool RtcpParser::ParseFeedbackCommon(base::BigEndianReader* reader, + const RtcpCommonHeader& header) { + // See RTC 4585 Section 6.4 for application specific feedback messages. + if (header.IC != 15) { + return true; } - field_type_ = kRtcpPayloadSpecificCastNackItemCode; - - 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); - - rtcp_data_ += 4; - return true; -} - -bool RtcpParser::ParseExtendedReport() { - ptrdiff_t length = rtcp_block_end_ - rtcp_data_; - if (length < 8) + uint32 remote_ssrc; + uint32 media_ssrc; + if (!reader->ReadU32(&remote_ssrc) || + !reader->ReadU32(&media_ssrc)) return false; - field_type_ = kRtcpXrCode; + if (remote_ssrc != remote_ssrc_) + return true; - 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); + uint32 name; + if (!reader->ReadU32(&name)) + return false; - rtcp_data_ += 8; + if (name != kCast) { + return true; + } - state_ = kStateExtendedReportBlock; - return true; -} + cast_message_.media_ssrc = remote_ssrc; -bool RtcpParser::ParseExtendedReportItem() { - ptrdiff_t length = rtcp_block_end_ - rtcp_data_; - if (length < 4) { - state_ = kStateTopLevel; - EndCurrentBlock(); + uint8 last_frame_id; + uint8 number_of_lost_fields; + if (!reader->ReadU8(&last_frame_id) || + !reader->ReadU8(&number_of_lost_fields) || + !reader->ReadU16(&cast_message_.target_delay_ms)) return false; - } - uint8 block_type; - uint16 block_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); - - rtcp_data_ += 4; - - switch (block_type) { - case 4: // RRTR. RFC3611 Section 4.4. - if (block_length != 2) { - // Invalid block length. - state_ = kStateTopLevel; - EndCurrentBlock(); - return false; - } - return ParseExtendedReportReceiverReferenceTimeReport(); - case 5: // DLRR. RFC3611 Section 4.5. - if (block_length % 3 != 0) { - // Invalid block length. - state_ = kStateTopLevel; - EndCurrentBlock(); - return false; - } - if (block_length >= 3) { - number_of_blocks_ = block_length / 3; - state_ = kStateExtendedReportDelaySinceLastReceiverReport; - return ParseExtendedReportDelaySinceLastReceiverReport(); - } - return true; - default: - if (length < block_length * 4) { - state_ = kStateTopLevel; - EndCurrentBlock(); - return false; + // Please note, this frame_id is still only 8-bit! + cast_message_.ack_frame_id = last_frame_id; + + for (size_t i = 0; i < number_of_lost_fields; i++) { + uint8 frame_id; + uint16 packet_id; + uint8 bitmask; + if (!reader->ReadU8(&frame_id) || + !reader->ReadU16(&packet_id) || + !reader->ReadU8(&bitmask)) + return false; + cast_message_.missing_frames_and_packets[frame_id].insert(packet_id); + if (packet_id != kRtcpCastAllPacketsLost) { + while (bitmask) { + packet_id++; + if (bitmask & 1) + cast_message_.missing_frames_and_packets[frame_id].insert(packet_id); + bitmask >>= 1; } - field_type_ = kRtcpXrUnknownItemCode; - rtcp_data_ += block_length * 4; - return true; + } } + + has_cast_message_ = true; + return true; } -bool RtcpParser::ParseExtendedReportReceiverReferenceTimeReport() { - ptrdiff_t length = rtcp_block_end_ - rtcp_data_; - if (length < 8) { - state_ = kStateTopLevel; - EndCurrentBlock(); +bool RtcpParser::ParseExtendedReport(base::BigEndianReader* reader, + const RtcpCommonHeader& header) { + uint32 remote_ssrc; + if (!reader->ReadU32(&remote_ssrc)) return false; - } - 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); + // Is it for us? + if (remote_ssrc != remote_ssrc_) + return true; + + while (reader->remaining()) { + uint8 block_type; + uint16 block_length; + if (!reader->ReadU8(&block_type) || + !reader->Skip(1) || + !reader->ReadU16(&block_length)) + return false; + + switch (block_type) { + case 4: // RRTR. RFC3611 Section 4.4. + if (block_length != 2) + return false; + if (!ParseExtendedReportReceiverReferenceTimeReport(reader, + remote_ssrc)) + return false; + break; - rtcp_data_ += 8; + default: + // Skip unknown item. + if (!reader->Skip(block_length * 4)) + return false; + } + } - field_type_ = kRtcpXrRrtrCode; return true; } -bool RtcpParser::ParseExtendedReportDelaySinceLastReceiverReport() { - ptrdiff_t length = rtcp_block_end_ - rtcp_data_; - if (length < 12) { - state_ = kStateTopLevel; - EndCurrentBlock(); +bool RtcpParser::ParseExtendedReportReceiverReferenceTimeReport( + base::BigEndianReader* reader, + uint32 remote_ssrc) { + receiver_reference_time_report_.remote_ssrc = remote_ssrc; + if(!reader->ReadU32(&receiver_reference_time_report_.ntp_seconds) || + !reader->ReadU32(&receiver_reference_time_report_.ntp_fraction)) return false; - } - if (number_of_blocks_ == 0) { - // Continue parsing the extended report block. - state_ = kStateExtendedReportBlock; - return false; - } - - 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); - - rtcp_data_ += 12; - number_of_blocks_--; - field_type_ = kRtcpXrDlrrCode; + has_receiver_reference_time_report_ = true; return true; } @@ -692,7 +377,6 @@ CastLoggingEvent TranslateToLogEventFromWireFormat(uint8 event) { // 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; } } diff --git a/media/cast/net/rtcp/rtcp_utility.h b/media/cast/net/rtcp/rtcp_utility.h index a20fd8009c..8fd8edcd6a 100644 --- a/media/cast/net/rtcp/rtcp_utility.h +++ b/media/cast/net/rtcp/rtcp_utility.h @@ -5,6 +5,7 @@ #ifndef MEDIA_CAST_RTCP_RTCP_UTILITY_H_ #define MEDIA_CAST_RTCP_RTCP_UTILITY_H_ +#include "base/big_endian.h" #include "media/cast/cast_config.h" #include "media/cast/cast_defines.h" #include "media/cast/logging/logging_defines.h" @@ -23,203 +24,85 @@ static const uint8 kReceiverLogSubtype = 2; static const size_t kRtcpMaxReceiverLogMessages = 256; static const size_t kRtcpMaxCastLossFields = 100; -struct RtcpFieldReceiverReport { - // RFC 3550. - uint32 sender_ssrc; - uint8 number_of_report_blocks; -}; - -struct RtcpFieldSenderReport { - // RFC 3550. - uint32 sender_ssrc; - uint8 number_of_report_blocks; - uint32 ntp_most_significant; - uint32 ntp_least_significant; - uint32 rtp_timestamp; - uint32 sender_packet_count; - uint32 sender_octet_count; -}; - -struct RtcpFieldReportBlockItem { - // RFC 3550. - uint32 ssrc; - uint8 fraction_lost; - uint32 cumulative_number_of_packets_lost; - uint32 extended_highest_sequence_number; - uint32 jitter; - uint32 last_sender_report; - uint32 delay_last_sender_report; -}; - -struct RtcpFieldXr { - // RFC 3611. - uint32 sender_ssrc; -}; - -struct RtcpFieldXrRrtr { - // RFC 3611. - uint32 ntp_most_significant; - uint32 ntp_least_significant; -}; - -struct RtcpFieldXrDlrr { - // RFC 3611. - uint32 receivers_ssrc; - uint32 last_receiver_report; - uint32 delay_last_receiver_report; -}; - -struct RtcpFieldPayloadSpecificApplication { - uint32 sender_ssrc; - uint32 media_ssrc; -}; - -struct RtcpFieldPayloadSpecificCastItem { - uint8 last_frame_id; - uint8 number_of_lost_fields; - uint16 target_delay_ms; -}; - -struct RtcpFieldPayloadSpecificCastNackItem { - uint8 frame_id; - uint16 packet_id; - uint8 bitmask; -}; - -struct RtcpFieldApplicationSpecificCastReceiverLogItem { - uint32 sender_ssrc; - uint32 rtp_timestamp; - uint32 event_timestamp_base; - uint8 event; - union { - uint16 packet_id; - int16 delay_delta; - } delay_delta_or_packet_id; - uint16 event_timestamp_delta; -}; - -union RtcpField { - RtcpFieldReceiverReport receiver_report; - RtcpFieldSenderReport sender_report; - RtcpFieldReportBlockItem report_block_item; - - RtcpFieldXr extended_report; - RtcpFieldXrRrtr rrtr; - RtcpFieldXrDlrr dlrr; - - RtcpFieldPayloadSpecificApplication application_specific; - RtcpFieldPayloadSpecificCastItem cast_item; - RtcpFieldPayloadSpecificCastNackItem cast_nack_item; - - RtcpFieldApplicationSpecificCastReceiverLogItem cast_receiver_log; -}; - -enum RtcpFieldTypes { - kRtcpNotValidCode, - - // RFC 3550. - kRtcpRrCode, - kRtcpSrCode, - kRtcpReportBlockItemCode, - - // RFC 3611. - kRtcpXrCode, - kRtcpXrRrtrCode, - kRtcpXrDlrrCode, - kRtcpXrUnknownItemCode, - - // RFC 4585. - kRtcpPayloadSpecificAppCode, - - // Application specific. - kRtcpPayloadSpecificCastCode, - kRtcpPayloadSpecificCastNackItemCode, - kRtcpApplicationSpecificCastReceiverLogCode, - kRtcpApplicationSpecificCastReceiverLogFrameCode, - kRtcpApplicationSpecificCastReceiverLogEventCode, -}; - struct RtcpCommonHeader { uint8 V; // Version. bool P; // Padding. uint8 IC; // Item count / subtype. uint8 PT; // Packet Type. - uint16 length_in_octets; + size_t length_in_octets; }; class RtcpParser { public: - RtcpParser(const uint8* rtcp_data, size_t rtcp_length); + RtcpParser(uint32 local_ssrc, uint32 remote_ssrc); ~RtcpParser(); - RtcpFieldTypes FieldType() const; - const RtcpField& Field() const; + bool Parse(base::BigEndianReader* reader); - bool IsValid() const; + bool has_sender_report() const { return has_sender_report_; } + const RtcpSenderInfo& sender_report() const { + return sender_report_; + } - RtcpFieldTypes Begin(); - RtcpFieldTypes Iterate(); + bool has_last_report() const { return has_last_report_; } + uint32 last_report() const { return last_report_; } + uint32 delay_since_last_report() const { return delay_since_last_report_; } - private: - enum ParseState { - kStateTopLevel, // Top level packet - kStateReportBlock, // Sender/Receiver report report blocks. - kStateApplicationSpecificCastReceiverFrameLog, - kStateApplicationSpecificCastReceiverEventLog, - kStateExtendedReportBlock, - kStateExtendedReportDelaySinceLastReceiverReport, - kStatePayloadSpecificApplication, - kStatePayloadSpecificCast, // Application specific Cast. - kStatePayloadSpecificCastNack, // Application specific Nack for Cast. - }; - - bool RtcpParseCommonHeader(const uint8* begin, - const uint8* end, - RtcpCommonHeader* parsed_header) const; - - void IterateTopLevel(); - void IterateReportBlockItem(); - void IterateCastReceiverLogFrame(); - void IterateCastReceiverLogEvent(); - void IterateExtendedReportItem(); - void IterateExtendedReportDelaySinceLastReceiverReportItem(); - void IteratePayloadSpecificAppItem(); - void IteratePayloadSpecificCastItem(); - void IteratePayloadSpecificCastNackItem(); - - void Validate(); - void EndCurrentBlock(); - - bool ParseRR(); - bool ParseSR(); - bool ParseReportBlockItem(); - - bool ParseApplicationDefined(uint8 subtype); - bool ParseCastReceiverLogFrameItem(); - bool ParseCastReceiverLogEventItem(); - - bool ParseExtendedReport(); - bool ParseExtendedReportItem(); - bool ParseExtendedReportReceiverReferenceTimeReport(); - bool ParseExtendedReportDelaySinceLastReceiverReport(); - - bool ParseFeedBackCommon(const RtcpCommonHeader& header); - bool ParsePayloadSpecificAppItem(); - bool ParsePayloadSpecificCastItem(); - bool ParsePayloadSpecificCastNackItem(); + bool has_receiver_log() const { return !receiver_log_.empty(); } + const RtcpReceiverLogMessage& receiver_log() const { return receiver_log_; } + RtcpReceiverLogMessage* mutable_receiver_log() { return & receiver_log_; } - private: - const uint8* const rtcp_data_begin_; - const uint8* const rtcp_data_end_; + bool has_cast_message() const { return has_cast_message_; } + const RtcpCastMessage& cast_message() const { return cast_message_; } + RtcpCastMessage* mutable_cast_message() { return &cast_message_; } - bool valid_packet_; - const uint8* rtcp_data_; - const uint8* rtcp_block_end_; + bool has_receiver_reference_time_report() const { + return has_receiver_reference_time_report_; + } + const RtcpReceiverReferenceTimeReport& + receiver_reference_time_report() const { + return receiver_reference_time_report_; + } - ParseState state_; - uint8 number_of_blocks_; - RtcpFieldTypes field_type_; - RtcpField field_; + private: + bool ParseCommonHeader(base::BigEndianReader* reader, + RtcpCommonHeader* parsed_header); + bool ParseSR(base::BigEndianReader* reader, + const RtcpCommonHeader& header); + bool ParseRR(base::BigEndianReader* reader, + const RtcpCommonHeader& header); + bool ParseReportBlock(base::BigEndianReader* reader); + bool ParseApplicationDefined(base::BigEndianReader* reader, + const RtcpCommonHeader& header); + bool ParseCastReceiverLogFrameItem(base::BigEndianReader* reader); + bool ParseFeedbackCommon(base::BigEndianReader* reader, + const RtcpCommonHeader& header); + bool ParseExtendedReport(base::BigEndianReader* reader, + const RtcpCommonHeader& header); + bool ParseExtendedReportReceiverReferenceTimeReport( + base::BigEndianReader* reader, + uint32 remote_ssrc); + bool ParseExtendedReportDelaySinceLastReceiverReport( + base::BigEndianReader* reader); + + uint32 local_ssrc_; + uint32 remote_ssrc_; + + bool has_sender_report_; + RtcpSenderInfo sender_report_; + + uint32 last_report_; + uint32 delay_since_last_report_; + bool has_last_report_; + + // |receiver_log_| is a vector vector, no need for has_*. + RtcpReceiverLogMessage receiver_log_; + + bool has_cast_message_; + RtcpCastMessage cast_message_; + + bool has_receiver_reference_time_report_; + RtcpReceiverReferenceTimeReport receiver_reference_time_report_; DISALLOW_COPY_AND_ASSIGN(RtcpParser); }; diff --git a/media/cast/net/rtcp/rtcp_utility_unittest.cc b/media/cast/net/rtcp/rtcp_utility_unittest.cc new file mode 100644 index 0000000000..ed2ab8d663 --- /dev/null +++ b/media/cast/net/rtcp/rtcp_utility_unittest.cc @@ -0,0 +1,402 @@ +// 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/scoped_ptr.h" +#include "base/test/simple_test_tick_clock.h" +#include "media/cast/cast_environment.h" +#include "media/cast/net/cast_transport_defines.h" +#include "media/cast/net/rtcp/rtcp_utility.h" +#include "media/cast/net/rtcp/test_rtcp_packet_builder.h" +#include "media/cast/test/fake_single_thread_task_runner.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace media { +namespace cast { + +static const uint32 kSenderSsrc = 0x10203; +static const uint32 kSourceSsrc = 0x40506; +static const uint32 kUnknownSsrc = 0xDEAD; +static const base::TimeDelta kTargetDelay = + base::TimeDelta::FromMilliseconds(100); + +class RtcpParserTest : public ::testing::Test { + protected: + RtcpParserTest() + : testing_clock_(new base::SimpleTestTickClock()), + task_runner_(new test::FakeSingleThreadTaskRunner( + testing_clock_.get())) { + } + + bool HasAnything(const RtcpParser& parser) { + return parser.has_sender_report() || + parser.has_last_report() || + parser.has_receiver_log() || + parser.has_cast_message() || + parser.has_receiver_reference_time_report(); + } + + void ExpectSenderInfo(const RtcpParser& parser) { + EXPECT_TRUE(parser.has_sender_report()); + EXPECT_EQ(kNtpHigh, parser.sender_report().ntp_seconds); + EXPECT_EQ(kNtpLow, parser.sender_report().ntp_fraction); + EXPECT_EQ(kRtpTimestamp, parser.sender_report().rtp_timestamp); + EXPECT_EQ(kSendPacketCount, parser.sender_report().send_packet_count); + EXPECT_EQ(kSendOctetCount, parser.sender_report().send_octet_count); + } + + void ExpectLastReport(const RtcpParser& parser) { + EXPECT_TRUE(parser.has_last_report()); + EXPECT_EQ(kLastSr, parser.last_report()); + EXPECT_EQ(kDelayLastSr, parser.delay_since_last_report()); + } + + void ExpectReceiverReference(const RtcpParser& parser) { + EXPECT_TRUE(parser.has_receiver_reference_time_report()); + EXPECT_EQ(kSenderSsrc, parser.receiver_reference_time_report().remote_ssrc); + EXPECT_EQ(kNtpHigh, parser.receiver_reference_time_report().ntp_seconds); + EXPECT_EQ(kNtpLow, parser.receiver_reference_time_report().ntp_fraction); + } + + void ExpectCastFeedback(const RtcpParser& parser) { + EXPECT_TRUE(parser.has_cast_message()); + EXPECT_EQ(kSenderSsrc, parser.cast_message().media_ssrc); + EXPECT_EQ(kAckFrameId, parser.cast_message().ack_frame_id); + + MissingFramesAndPacketsMap::const_iterator frame_it = + parser.cast_message().missing_frames_and_packets.begin(); + + EXPECT_TRUE( + frame_it != parser.cast_message().missing_frames_and_packets.end()); + EXPECT_EQ(kLostFrameId, frame_it->first); + EXPECT_EQ(frame_it->second.size(), 1UL); + EXPECT_EQ(*frame_it->second.begin(), kRtcpCastAllPacketsLost); + ++frame_it; + EXPECT_TRUE( + frame_it != parser.cast_message().missing_frames_and_packets.end()); + EXPECT_EQ(kFrameIdWithLostPackets, frame_it->first); + EXPECT_EQ(3UL, frame_it->second.size()); + PacketIdSet::const_iterator packet_it = frame_it->second.begin(); + EXPECT_EQ(kLostPacketId1, *packet_it); + ++packet_it; + EXPECT_EQ(kLostPacketId2, *packet_it); + ++packet_it; + EXPECT_EQ(kLostPacketId3, *packet_it); + ++frame_it; + EXPECT_TRUE( + frame_it == parser.cast_message().missing_frames_and_packets.end()); + } + + void ExpectReceiverLog(const RtcpParser& parser, + const RtcpReceiverLogMessage& expected_receiver_log) { + EXPECT_TRUE(parser.has_receiver_log()); + EXPECT_EQ(expected_receiver_log.size(), parser.receiver_log().size()); + RtcpReceiverLogMessage::const_iterator expected_it = + expected_receiver_log.begin(); + RtcpReceiverLogMessage::const_iterator incoming_it = + parser.receiver_log().begin(); + for (; incoming_it != parser.receiver_log().end(); + ++incoming_it, ++expected_it) { + EXPECT_EQ(expected_it->rtp_timestamp_, incoming_it->rtp_timestamp_); + EXPECT_EQ(expected_it->event_log_messages_.size(), + incoming_it->event_log_messages_.size()); + + RtcpReceiverEventLogMessages::const_iterator event_incoming_it = + incoming_it->event_log_messages_.begin(); + RtcpReceiverEventLogMessages::const_iterator event_expected_it = + expected_it->event_log_messages_.begin(); + for (; event_incoming_it != incoming_it->event_log_messages_.end(); + ++event_incoming_it, ++event_expected_it) { + 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 == PACKET_RECEIVED) { + EXPECT_EQ(event_expected_it->packet_id, event_incoming_it->packet_id); + } else { + EXPECT_EQ(event_expected_it->delay_delta, + event_incoming_it->delay_delta); + } + } + } + } + + scoped_ptr<base::SimpleTestTickClock> testing_clock_; + scoped_refptr<test::FakeSingleThreadTaskRunner> task_runner_; + + DISALLOW_COPY_AND_ASSIGN(RtcpParserTest); +}; + +TEST_F(RtcpParserTest, BrokenPacketIsIgnored) { + const char bad_packet[] = {0, 0, 0, 0}; + RtcpParser parser(kSourceSsrc, kSenderSsrc); + base::BigEndianReader reader(bad_packet, sizeof(bad_packet)); + EXPECT_FALSE(parser.Parse(&reader)); +} + +TEST_F(RtcpParserTest, UnknownBlockIgnored) { + // Only unknown data, nothing happens. + TestRtcpPacketBuilder p; + p.AddUnknownBlock(); + RtcpParser parser1(kSourceSsrc, 0); + EXPECT_TRUE(parser1.Parse(p.Reader())); + EXPECT_FALSE(HasAnything(parser1)); + + // Add valid sender report *after* unknown data - should work fine. + p.AddSr(kSenderSsrc, 0); + RtcpParser parser2(kSourceSsrc, kSenderSsrc); + EXPECT_TRUE(parser2.Parse(p.Reader())); + ExpectSenderInfo(parser2); +} + +TEST_F(RtcpParserTest, InjectSenderReportPacket) { + TestRtcpPacketBuilder p; + p.AddSr(kSenderSsrc, 0); + + // Expected to be ignored since the sender ssrc does not match our + // remote ssrc. + RtcpParser parser1(kSourceSsrc, 0); + EXPECT_TRUE(parser1.Parse(p.Reader())); + EXPECT_FALSE(HasAnything(parser1)); + + // Expected to be pass through since the sender ssrc match our remote ssrc. + RtcpParser parser2(kSourceSsrc, kSenderSsrc); + EXPECT_TRUE(parser2.Parse(p.Reader())); + ExpectSenderInfo(parser2); +} + +TEST_F(RtcpParserTest, InjectReceiveReportPacket) { + TestRtcpPacketBuilder p1; + p1.AddRr(kSenderSsrc, 1); + p1.AddRb(kUnknownSsrc); + + // Expected to be ignored since the source ssrc does not match our + // local ssrc. + RtcpParser parser1(kSourceSsrc, kSenderSsrc); + EXPECT_TRUE(parser1.Parse(p1.Reader())); + EXPECT_FALSE(HasAnything(parser1)); + + TestRtcpPacketBuilder p2; + p2.AddRr(kSenderSsrc, 1); + p2.AddRb(kSourceSsrc); + + // Expected to be pass through since the sender ssrc match our local ssrc. + RtcpParser parser2(kSourceSsrc, kSenderSsrc); + EXPECT_TRUE(parser2.Parse(p2.Reader())); + ExpectLastReport(parser2); +} + +TEST_F(RtcpParserTest, InjectSenderReportWithReportBlockPacket) { + TestRtcpPacketBuilder p1; + p1.AddSr(kSenderSsrc, 1); + p1.AddRb(kUnknownSsrc); + + // Sender report expected to be ignored since the sender ssrc does not match + // our remote ssrc. + // Report block expected to be ignored since the source ssrc does not match + // our local ssrc. + RtcpParser parser1(kSourceSsrc, 0); + EXPECT_TRUE(parser1.Parse(p1.Reader())); + EXPECT_FALSE(HasAnything(parser1)); + + // Sender report expected to be pass through since the sender ssrc match our + // remote ssrc. + // Report block expected to be ignored since the source ssrc does not match + // our local ssrc. + RtcpParser parser2(kSourceSsrc, kSenderSsrc); + EXPECT_TRUE(parser2.Parse(p1.Reader())); + ExpectSenderInfo(parser2); + EXPECT_FALSE(parser2.has_last_report()); + + // Sender report expected to be ignored since the sender ssrc does not match + // our remote ssrc. + // Report block expected to be ignored too since it's a part of the + // sender report. + TestRtcpPacketBuilder p2; + p2.AddSr(kSenderSsrc, 1); + p2.AddRb(kSourceSsrc); + + RtcpParser parser3(kSourceSsrc, 0); + EXPECT_TRUE(parser3.Parse(p2.Reader())); + EXPECT_FALSE(parser3.has_last_report()); + + // Sender report expected to be pass through since the sender ssrc match our + // remote ssrc. + // Report block expected to be pass through since the sender ssrc match + // our local ssrc. + RtcpParser parser4(kSourceSsrc, kSenderSsrc); + EXPECT_TRUE(parser4.Parse(p2.Reader())); + ExpectSenderInfo(parser4); + ExpectLastReport(parser4); +} + +TEST_F(RtcpParserTest, InjectSenderReportPacketWithDlrr) { + TestRtcpPacketBuilder p; + p.AddSr(kSenderSsrc, 0); + p.AddXrHeader(kSenderSsrc); + p.AddXrUnknownBlock(); + p.AddXrExtendedDlrrBlock(kSenderSsrc); + p.AddXrUnknownBlock(); + + // Expected to be ignored since the source ssrc does not match our + // local ssrc. + RtcpParser parser1(kSourceSsrc, 0); + EXPECT_TRUE(parser1.Parse(p.Reader())); + EXPECT_FALSE(HasAnything(parser1)); + + // Expected to be pass through since the sender ssrc match our local ssrc. + RtcpParser parser2(kSourceSsrc, kSenderSsrc); + EXPECT_TRUE(parser2.Parse(p.Reader())); + ExpectSenderInfo(parser2); + // DLRRs are ignored. + EXPECT_FALSE(parser2.has_last_report()); +} + +TEST_F(RtcpParserTest, InjectReceiverReportPacketWithRrtr) { + TestRtcpPacketBuilder p1; + p1.AddRr(kSenderSsrc, 1); + p1.AddRb(kUnknownSsrc); + p1.AddXrHeader(kSenderSsrc); + p1.AddXrRrtrBlock(); + + // Expected to be ignored since the source ssrc does not match our + // local ssrc. + RtcpParser parser1(kSourceSsrc, 0); + EXPECT_TRUE(parser1.Parse(p1.Reader())); + EXPECT_FALSE(HasAnything(parser1)); + + TestRtcpPacketBuilder p2; + p2.AddRr(kSenderSsrc, 1); + p2.AddRb(kSourceSsrc); + p2.AddXrHeader(kSenderSsrc); + p2.AddXrRrtrBlock(); + + // Expected to be pass through since the sender ssrc match our local ssrc. + RtcpParser parser2(kSourceSsrc, kSenderSsrc); + EXPECT_TRUE(parser2.Parse(p2.Reader())); + ExpectLastReport(parser2); + ExpectReceiverReference(parser2); +} + +TEST_F(RtcpParserTest, InjectReceiverReportPacketWithIntraFrameRequest) { + TestRtcpPacketBuilder p1; + p1.AddRr(kSenderSsrc, 1); + p1.AddRb(kUnknownSsrc); + + // Expected to be ignored since the source ssrc does not match our + // local ssrc. + RtcpParser parser1(kSourceSsrc, 0); + EXPECT_TRUE(parser1.Parse(p1.Reader())); + EXPECT_FALSE(HasAnything(parser1)); + + TestRtcpPacketBuilder p2; + p2.AddRr(kSenderSsrc, 1); + p2.AddRb(kSourceSsrc); + + RtcpParser parser2(kSourceSsrc, kSenderSsrc); + EXPECT_TRUE(parser2.Parse(p2.Reader())); + ExpectLastReport(parser2); +} + +TEST_F(RtcpParserTest, InjectReceiverReportPacketWithCastFeedback) { + TestRtcpPacketBuilder p1; + p1.AddRr(kSenderSsrc, 1); + p1.AddRb(kUnknownSsrc); + p1.AddCast(kSenderSsrc, kUnknownSsrc, kTargetDelay); + + // Expected to be ignored since the source ssrc does not match our + // local ssrc. + RtcpParser parser1(kSourceSsrc, 0); + EXPECT_TRUE(parser1.Parse(p1.Reader())); + EXPECT_FALSE(HasAnything(parser1)); + + TestRtcpPacketBuilder p2; + p2.AddRr(kSenderSsrc, 1); + p2.AddRb(kSourceSsrc); + p2.AddCast(kSenderSsrc, kSourceSsrc, kTargetDelay); + + // Expected to be pass through since the sender ssrc match our local ssrc. + RtcpParser parser2(kSourceSsrc, kSenderSsrc); + EXPECT_TRUE(parser2.Parse(p2.Reader())); + ExpectLastReport(parser2); + ExpectCastFeedback(parser2); +} + +TEST_F(RtcpParserTest, InjectReceiverReportWithReceiverLogVerificationBase) { + static const uint32 kTimeBaseMs = 12345678; + static const uint32 kTimeDelayMs = 10; + static const uint32 kDelayDeltaMs = 123; + base::SimpleTestTickClock testing_clock; + testing_clock.Advance(base::TimeDelta::FromMilliseconds(kTimeBaseMs)); + + RtcpReceiverLogMessage receiver_log; + RtcpReceiverFrameLogMessage frame_log(kRtpTimestamp); + RtcpReceiverEventLogMessage event_log; + + 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 = 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); + + TestRtcpPacketBuilder p; + p.AddRr(kSenderSsrc, 1); + p.AddRb(kSourceSsrc); + p.AddReceiverLog(kSenderSsrc); + p.AddReceiverFrameLog(kRtpTimestamp, 3, kTimeBaseMs); + p.AddReceiverEventLog(kDelayDeltaMs, FRAME_ACK_SENT, 0); + p.AddReceiverEventLog(kLostPacketId1, PACKET_RECEIVED, kTimeDelayMs); + p.AddReceiverEventLog(kLostPacketId2, PACKET_RECEIVED, kTimeDelayMs); + + RtcpParser parser(kSourceSsrc, kSenderSsrc); + EXPECT_TRUE(parser.Parse(p.Reader())); + ExpectReceiverLog(parser, receiver_log); +} + +TEST_F(RtcpParserTest, InjectReceiverReportWithReceiverLogVerificationMulti) { + static const uint32 kTimeBaseMs = 12345678; + static const uint32 kTimeDelayMs = 10; + static const uint32 kDelayDeltaMs = 123; + base::SimpleTestTickClock testing_clock; + testing_clock.Advance(base::TimeDelta::FromMilliseconds(kTimeBaseMs)); + + RtcpReceiverLogMessage receiver_log; + + for (int j = 0; j < 100; ++j) { + RtcpReceiverFrameLogMessage frame_log(kRtpTimestamp); + RtcpReceiverEventLogMessage event_log; + 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); + receiver_log.push_back(frame_log); + testing_clock.Advance(base::TimeDelta::FromMilliseconds(kTimeDelayMs)); + } + + TestRtcpPacketBuilder p; + p.AddRr(kSenderSsrc, 1); + p.AddRb(kSourceSsrc); + p.AddReceiverLog(kSenderSsrc); + for (int i = 0; i < 100; ++i) { + p.AddReceiverFrameLog(kRtpTimestamp, 1, kTimeBaseMs + i * kTimeDelayMs); + p.AddReceiverEventLog(kDelayDeltaMs, FRAME_ACK_SENT, 0); + } + + RtcpParser parser(kSourceSsrc, kSenderSsrc); + EXPECT_TRUE(parser.Parse(p.Reader())); + ExpectReceiverLog(parser, receiver_log); +} + +} // namespace cast +} // namespace media diff --git a/media/cast/net/rtcp/test_rtcp_packet_builder.cc b/media/cast/net/rtcp/test_rtcp_packet_builder.cc index 046cc0446d..32e1883f45 100644 --- a/media/cast/net/rtcp/test_rtcp_packet_builder.cc +++ b/media/cast/net/rtcp/test_rtcp_packet_builder.cc @@ -12,7 +12,8 @@ namespace cast { TestRtcpPacketBuilder::TestRtcpPacketBuilder() : ptr_of_length_(NULL), - big_endian_writer_(reinterpret_cast<char*>(buffer_), kMaxIpPacketSize) {} + big_endian_writer_(reinterpret_cast<char*>(buffer_), kMaxIpPacketSize), + big_endian_reader_(NULL, 0) {} void TestRtcpPacketBuilder::AddSr(uint32 sender_ssrc, int number_of_report_blocks) { @@ -69,6 +70,13 @@ void TestRtcpPacketBuilder::AddXrUnknownBlock() { big_endian_writer_.WriteU32(0); } +void TestRtcpPacketBuilder::AddUnknownBlock() { + AddRtcpHeader(99, 0); + big_endian_writer_.WriteU32(42); + big_endian_writer_.WriteU32(42); + big_endian_writer_.WriteU32(42); +} + void TestRtcpPacketBuilder::AddXrDlrrBlock(uint32 sender_ssrc) { big_endian_writer_.WriteU8(5); // Block type. big_endian_writer_.WriteU8(0); // Reserved. @@ -184,6 +192,12 @@ const uint8* TestRtcpPacketBuilder::Data() { return buffer_; } +base::BigEndianReader* TestRtcpPacketBuilder::Reader() { + big_endian_reader_ = base::BigEndianReader( + reinterpret_cast<const char *>(Data()), Length()); + return &big_endian_reader_; +} + void TestRtcpPacketBuilder::PatchLengthField() { if (ptr_of_length_) { // Back-patch the packet length. The client must have taken diff --git a/media/cast/net/rtcp/test_rtcp_packet_builder.h b/media/cast/net/rtcp/test_rtcp_packet_builder.h index afbf4c6253..3fab3b5069 100644 --- a/media/cast/net/rtcp/test_rtcp_packet_builder.h +++ b/media/cast/net/rtcp/test_rtcp_packet_builder.h @@ -19,18 +19,18 @@ namespace cast { namespace { // Sender report. -static const int kNtpHigh = 0x01020304; -static const int kNtpLow = 0x05060708; -static const int kRtpTimestamp = 0x10203040; -static const int kSendPacketCount = 987; -static const int kSendOctetCount = 87654; +static const uint32 kNtpHigh = 0x01020304; +static const uint32 kNtpLow = 0x05060708; +static const uint32 kRtpTimestamp = 0x10203040; +static const uint32 kSendPacketCount = 987; +static const uint32 kSendOctetCount = 87654; // Report block. static const int kLoss = 0x01000123; static const int kExtendedMax = 0x15678; static const int kTestJitter = 0x10203; -static const int kLastSr = 0x34561234; -static const int kDelayLastSr = 1000; +static const uint32 kLastSr = 0x34561234; +static const uint32 kDelayLastSr = 1000; // DLRR block. static const int kLastRr = 0x34561234; @@ -65,6 +65,7 @@ class TestRtcpPacketBuilder { void AddXrExtendedDlrrBlock(uint32 sender_ssrc); void AddXrRrtrBlock(); void AddXrUnknownBlock(); + void AddUnknownBlock(); void AddNack(uint32 sender_ssrc, uint32 media_ssrc); void AddSendReportRequest(uint32 sender_ssrc, uint32 media_ssrc); @@ -83,6 +84,7 @@ class TestRtcpPacketBuilder { scoped_ptr<Packet> GetPacket(); const uint8* Data(); int Length() { return kMaxIpPacketSize - big_endian_writer_.remaining(); } + base::BigEndianReader* Reader(); private: void AddRtcpHeader(int payload, int format_or_count); @@ -93,6 +95,7 @@ class TestRtcpPacketBuilder { uint8 buffer_[kMaxIpPacketSize]; char* ptr_of_length_; base::BigEndianWriter big_endian_writer_; + base::BigEndianReader big_endian_reader_; DISALLOW_COPY_AND_ASSIGN(TestRtcpPacketBuilder); }; diff --git a/media/cast/net/rtp/frame_buffer.cc b/media/cast/net/rtp/frame_buffer.cc index a419ab6a46..4a911635f9 100644 --- a/media/cast/net/rtp/frame_buffer.cc +++ b/media/cast/net/rtp/frame_buffer.cc @@ -13,6 +13,7 @@ FrameBuffer::FrameBuffer() : frame_id_(0), max_packet_id_(0), num_packets_received_(0), + new_playout_delay_ms_(0), is_key_frame_(false), total_data_size_(0), last_referenced_frame_id_(0), @@ -28,6 +29,7 @@ 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; + new_playout_delay_ms_ = rtp_header.new_playout_delay_ms; if (is_key_frame_) DCHECK_EQ(rtp_header.frame_id, rtp_header.reference_frame_id); last_referenced_frame_id_ = rtp_header.reference_frame_id; @@ -73,6 +75,7 @@ bool FrameBuffer::AssembleEncodedFrame(EncodedFrame* frame) const { frame->frame_id = frame_id_; frame->referenced_frame_id = last_referenced_frame_id_; frame->rtp_timestamp = rtp_timestamp_; + frame->new_playout_delay_ms = new_playout_delay_ms_; // Build the data vector. frame->data.clear(); diff --git a/media/cast/net/rtp/frame_buffer.h b/media/cast/net/rtp/frame_buffer.h index 1309a8785a..8ccc2092c0 100644 --- a/media/cast/net/rtp/frame_buffer.h +++ b/media/cast/net/rtp/frame_buffer.h @@ -39,6 +39,7 @@ class FrameBuffer { uint32 frame_id_; uint16 max_packet_id_; uint16 num_packets_received_; + uint16 new_playout_delay_ms_; bool is_key_frame_; size_t total_data_size_; uint32 last_referenced_frame_id_; diff --git a/media/cast/net/rtp/rtp_defines.h b/media/cast/net/rtp/rtp_defines.h new file mode 100644 index 0000000000..56df66d4d2 --- /dev/null +++ b/media/cast/net/rtp/rtp_defines.h @@ -0,0 +1,20 @@ +// 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. + +namespace media { +namespace cast { + +static const uint16 kRtpHeaderLength = 12; +static const uint16 kCastHeaderLength = 7; +static const uint8 kRtpExtensionBitMask = 0x10; +static const uint8 kCastKeyFrameBitMask = 0x80; +static const uint8 kCastReferenceFrameIdBitMask = 0x40; +static const uint8 kRtpMarkerBitMask = 0x80; +static const uint8 kCastExtensionCountmask = 0x3f; + +// Cast RTP extensions. +static const uint8 kCastRtpExtensionAdaptiveLatency = 1; + +} // namespace cast +} // namespace media diff --git a/media/cast/net/rtp/rtp_packetizer.cc b/media/cast/net/rtp/rtp_packetizer.cc index dcfcc8bba4..c2f5a17bd5 100644 --- a/media/cast/net/rtp/rtp_packetizer.cc +++ b/media/cast/net/rtp/rtp_packetizer.cc @@ -7,16 +7,11 @@ #include "base/big_endian.h" #include "base/logging.h" #include "media/cast/net/pacing/paced_sender.h" +#include "media/cast/net/rtp/rtp_defines.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; -static const uint8 kRtpMarkerBitMask = 0x80; - RtpPacketizerConfig::RtpPacketizerConfig() : payload_type(-1), max_payload_length(kMaxIpPacketSize - 28), // Default is IP-v4/UDP. @@ -47,7 +42,7 @@ uint16 RtpPacketizer::NextSequenceNumber() { } void RtpPacketizer::SendFrameAsPackets(const EncodedFrame& frame) { - uint16 rtp_header_length = kCommonRtpHeaderLength + kCastRtpHeaderLength; + uint16 rtp_header_length = kRtpHeaderLength + kCastHeaderLength; uint16 max_length = config_.max_payload_length - rtp_header_length - 1; rtp_timestamp_ = frame.rtp_timestamp; @@ -73,9 +68,15 @@ void RtpPacketizer::SendFrameAsPackets(const EncodedFrame& frame) { // 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); + uint8 num_extensions = 0; + if (frame.new_playout_delay_ms) + num_extensions++; + uint8 byte0 = kCastReferenceFrameIdBitMask; + if (frame.dependency == EncodedFrame::KEY) + byte0 |= kCastKeyFrameBitMask; + DCHECK_LE(num_extensions, kCastExtensionCountmask); + byte0 |= num_extensions; + packet->data.push_back(byte0); packet->data.push_back(static_cast<uint8>(frame.frame_id)); size_t start_size = packet->data.size(); packet->data.resize(start_size + 4); @@ -84,6 +85,14 @@ void RtpPacketizer::SendFrameAsPackets(const EncodedFrame& frame) { 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)); + if (frame.new_playout_delay_ms) { + packet->data.push_back(kCastRtpExtensionAdaptiveLatency << 2); + packet->data.push_back(2); // 2 bytes + packet->data.push_back( + static_cast<uint8>(frame.new_playout_delay_ms >> 8)); + packet->data.push_back( + static_cast<uint8>(frame.new_playout_delay_ms)); + } // Copy payload data. packet->data.insert(packet->data.end(), diff --git a/media/cast/net/rtp/rtp_packetizer_unittest.cc b/media/cast/net/rtp/rtp_packetizer_unittest.cc index 0d93d91927..5e0d388cab 100644 --- a/media/cast/net/rtp/rtp_packetizer_unittest.cc +++ b/media/cast/net/rtp/rtp_packetizer_unittest.cc @@ -72,6 +72,10 @@ class TestRtpPacketTransport : public PacketSender { return true; } + virtual int64 GetBytesSent() OVERRIDE { + return 0; + } + size_t number_of_packets_received() const { return packets_sent_; } void set_expected_number_of_packets(size_t expected_number_of_packets) { diff --git a/media/cast/net/rtp/rtp_parser.cc b/media/cast/net/rtp/rtp_parser.cc index bed1d7c8d8..1d68cd15a1 100644 --- a/media/cast/net/rtp/rtp_parser.cc +++ b/media/cast/net/rtp/rtp_parser.cc @@ -7,17 +7,11 @@ #include "base/big_endian.h" #include "base/logging.h" #include "media/cast/cast_defines.h" +#include "media/cast/net/rtp/rtp_defines.h" namespace media { namespace cast { -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(uint32 expected_sender_ssrc, uint8 expected_payload_type) : expected_sender_ssrc_(expected_sender_ssrc), expected_payload_type_(expected_payload_type) {} @@ -92,6 +86,22 @@ bool RtpParser::ParsePacket(const uint8* packet, return false; } + for (int i = 0; i < (bits & kCastExtensionCountmask); i++) { + uint16 type_and_size; + if (!reader.ReadU16(&type_and_size)) + return false; + base::StringPiece tmp; + if (!reader.ReadPiece(&tmp, type_and_size & 0x3ff)) + return false; + base::BigEndianReader chunk(tmp.data(), tmp.size()); + switch (type_and_size >> 10) { + case kCastRtpExtensionAdaptiveLatency: + if (!chunk.ReadU16(&header->new_playout_delay_ms)) + return false; + + } + } + // Only the lower 8 bits of the |frame_id| were serialized, so do some magic // to restore the upper 24 bits. // diff --git a/media/cast/net/rtp/rtp_receiver_defines.cc b/media/cast/net/rtp/rtp_receiver_defines.cc index 9b20b5f904..325b3058f6 100644 --- a/media/cast/net/rtp/rtp_receiver_defines.cc +++ b/media/cast/net/rtp/rtp_receiver_defines.cc @@ -17,7 +17,8 @@ RtpCastHeader::RtpCastHeader() frame_id(0), packet_id(0), max_packet_id(0), - reference_frame_id(0) {} + reference_frame_id(0), + new_playout_delay_ms(0) {} RtpPayloadFeedback::~RtpPayloadFeedback() {} diff --git a/media/cast/net/rtp/rtp_receiver_defines.h b/media/cast/net/rtp/rtp_receiver_defines.h index 86fbd2296f..23a1849403 100644 --- a/media/cast/net/rtp/rtp_receiver_defines.h +++ b/media/cast/net/rtp/rtp_receiver_defines.h @@ -28,6 +28,8 @@ struct RtpCastHeader { uint16 packet_id; uint16 max_packet_id; uint32 reference_frame_id; + + uint16 new_playout_delay_ms; }; class RtpPayloadFeedback { diff --git a/media/cast/net/rtp/rtp_sender.cc b/media/cast/net/rtp/rtp_sender.cc index 0f88c444ec..bf1200fd33 100644 --- a/media/cast/net/rtp/rtp_sender.cc +++ b/media/cast/net/rtp/rtp_sender.cc @@ -58,8 +58,7 @@ void RtpSender::SendFrame(const EncodedFrame& frame) { void RtpSender::ResendPackets( const MissingFramesAndPacketsMap& missing_frames_and_packets, - bool cancel_rtx_if_not_in_list, - base::TimeDelta dedupe_window) { + bool cancel_rtx_if_not_in_list, const DedupInfo& dedup_info) { DCHECK(storage_); // Iterate over all frames in the list. for (MissingFramesAndPacketsMap::const_iterator it = @@ -113,10 +112,40 @@ void RtpSender::ResendPackets( transport_->CancelSendingPacket(it->first); } } - transport_->ResendPackets(packets_to_resend, dedupe_window); + transport_->ResendPackets(packets_to_resend, dedup_info); } } +void RtpSender::CancelSendingFrames(const std::vector<uint32>& frame_ids) { + for (std::vector<uint32>::const_iterator i = frame_ids.begin(); + i != frame_ids.end(); ++i) { + const SendPacketVector* stored_packets = storage_->GetFrame8(*i & 0xFF); + if (!stored_packets) + continue; + for (SendPacketVector::const_iterator j = stored_packets->begin(); + j != stored_packets->end(); ++j) { + transport_->CancelSendingPacket(j->first); + } + } +} + +void RtpSender::ResendFrameForKickstart(uint32 frame_id, + base::TimeDelta dedupe_window) { + // Send the last packet of the 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(frame_id, missing)); + + // Sending this extra packet is to kick-start the session. There is + // no need to optimize re-transmission for this case. + DedupInfo dedup_info; + dedup_info.resend_interval = dedupe_window; + ResendPackets(missing_frames_and_packets, false, dedup_info); +} + 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. @@ -127,5 +156,13 @@ void RtpSender::UpdateSequenceNumber(Packet* packet) { big_endian_writer.WriteU16(packetizer_->NextSequenceNumber()); } +int64 RtpSender::GetLastByteSentForFrame(uint32 frame_id) { + const SendPacketVector* stored_packets = storage_->GetFrame8(frame_id & 0xFF); + if (!stored_packets) + return 0; + PacketKey last_packet_key = stored_packets->rbegin()->first; + return transport_->GetLastByteSentForPacket(last_packet_key); +} + } // namespace cast } // namespace media diff --git a/media/cast/net/rtp/rtp_sender.h b/media/cast/net/rtp/rtp_sender.h index 4dd7966a68..2875114aa3 100644 --- a/media/cast/net/rtp/rtp_sender.h +++ b/media/cast/net/rtp/rtp_sender.h @@ -46,7 +46,17 @@ class RtpSender { void ResendPackets(const MissingFramesAndPacketsMap& missing_packets, bool cancel_rtx_if_not_in_list, - base::TimeDelta dedupe_window); + const DedupInfo& dedup_info); + + // Returns the total number of bytes sent to the socket when the specified + // frame was just sent. + // Returns 0 if the frame cannot be found or the frame was only sent + // partially. + int64 GetLastByteSentForFrame(uint32 frame_id); + + void CancelSendingFrames(const std::vector<uint32>& frame_ids); + + void ResendFrameForKickstart(uint32 frame_id, base::TimeDelta dedupe_window); size_t send_packet_count() const { return packetizer_ ? packetizer_->send_packet_count() : 0; diff --git a/media/cast/net/udp_transport.cc b/media/cast/net/udp_transport.cc index 00bd822a4f..d6ca677680 100644 --- a/media/cast/net/udp_transport.cc +++ b/media/cast/net/udp_transport.cc @@ -55,6 +55,7 @@ UdpTransport::UdpTransport( client_connected_(false), next_dscp_value_(net::DSCP_NO_CHANGE), status_callback_(status_callback), + bytes_sent_(0), weak_factory_(this) { DCHECK(!IsEmpty(local_end_point) || !IsEmpty(remote_end_point)); } @@ -160,6 +161,9 @@ void UdpTransport::ReceiveNextPacket(int length_or_status) { bool UdpTransport::SendPacket(PacketRef packet, const base::Closure& cb) { DCHECK(io_thread_proxy_->RunsTasksOnCurrentThread()); + // Increase byte count no matter the packet was sent or dropped. + bytes_sent_ += packet->data.size(); + DCHECK(!send_pending_); if (send_pending_) { VLOG(1) << "Cannot send because of pending IO."; @@ -214,6 +218,10 @@ bool UdpTransport::SendPacket(PacketRef packet, const base::Closure& cb) { return true; } +int64 UdpTransport::GetBytesSent() { + return bytes_sent_; +} + void UdpTransport::OnSent(const scoped_refptr<net::IOBuffer>& buf, PacketRef packet, const base::Closure& cb, diff --git a/media/cast/net/udp_transport.h b/media/cast/net/udp_transport.h index 951e9c12d3..d88f2f3734 100644 --- a/media/cast/net/udp_transport.h +++ b/media/cast/net/udp_transport.h @@ -53,6 +53,7 @@ class UdpTransport : public PacketSender { // PacketSender implementations. virtual bool SendPacket(PacketRef packet, const base::Closure& cb) OVERRIDE; + virtual int64 GetBytesSent() OVERRIDE; private: // Requests and processes packets from |udp_socket_|. This method is called @@ -82,6 +83,7 @@ class UdpTransport : public PacketSender { net::IPEndPoint recv_addr_; PacketReceiverCallback packet_receiver_; const CastTransportStatusCallback status_callback_; + int bytes_sent_; // NOTE: Weak pointers must be invalidated before all other member variables. base::WeakPtrFactory<UdpTransport> weak_factory_; diff --git a/media/cast/receiver/cast_receiver_impl.cc b/media/cast/receiver/cast_receiver_impl.cc index 91821bbb73..36669b9e62 100644 --- a/media/cast/receiver/cast_receiver_impl.cc +++ b/media/cast/receiver/cast_receiver_impl.cc @@ -10,7 +10,6 @@ #include "base/debug/trace_event.h" #include "base/logging.h" #include "base/message_loop/message_loop.h" -#include "media/cast/net/rtcp/rtcp_receiver.h" #include "media/cast/receiver/audio_decoder.h" #include "media/cast/receiver/video_decoder.h" @@ -52,8 +51,8 @@ void CastReceiverImpl::DispatchReceivedPacket(scoped_ptr<Packet> packet) { const size_t length = packet->size(); uint32 ssrc_of_sender; - if (RtcpReceiver::IsRtcpPacket(data, length)) { - ssrc_of_sender = RtcpReceiver::GetSsrcOfSender(data, length); + 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; diff --git a/media/cast/receiver/frame_receiver.cc b/media/cast/receiver/frame_receiver.cc index 2670372e78..0e794cdb27 100644 --- a/media/cast/receiver/frame_receiver.cc +++ b/media/cast/receiver/frame_receiver.cc @@ -11,7 +11,6 @@ #include "base/logging.h" #include "base/message_loop/message_loop.h" #include "media/cast/cast_environment.h" -#include "media/cast/net/rtcp/rtcp_receiver.h" namespace { const int kMinSchedulingDelayMs = 1; @@ -74,7 +73,7 @@ void FrameReceiver::RequestEncodedFrame( bool FrameReceiver::ProcessPacket(scoped_ptr<Packet> packet) { DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); - if (RtcpReceiver::IsRtcpPacket(&packet->front(), packet->size())) { + if (Rtcp::IsRtcpPacket(&packet->front(), packet->size())) { rtcp_.IncomingRtcpPacket(&packet->front(), packet->size()); } else { RtpCastHeader rtp_header; @@ -203,8 +202,7 @@ void FrameReceiver::EmitAvailableEncodedFrames() { } const base::TimeTicks now = cast_environment_->Clock()->NowTicks(); - const base::TimeTicks playout_time = - GetPlayoutTime(encoded_frame->rtp_timestamp); + const base::TimeTicks playout_time = GetPlayoutTime(*encoded_frame); // If we have multiple decodable frames, and the current frame is // too old, then skip it and decode the next frame instead. @@ -252,6 +250,10 @@ void FrameReceiver::EmitAvailableEncodedFrames() { // At this point, we have a decrypted EncodedFrame ready to be emitted. encoded_frame->reference_time = playout_time; framer_.ReleaseFrame(encoded_frame->frame_id); + if (encoded_frame->new_playout_delay_ms) { + target_playout_delay_ = base::TimeDelta::FromMilliseconds( + encoded_frame->new_playout_delay_ms); + } cast_environment_->PostTask(CastEnvironment::MAIN, FROM_HERE, base::Bind(frame_request_queue_.front(), @@ -267,13 +269,18 @@ void FrameReceiver::EmitAvailableEncodedFramesAfterWaiting() { EmitAvailableEncodedFrames(); } -base::TimeTicks FrameReceiver::GetPlayoutTime(uint32 rtp_timestamp) const { +base::TimeTicks FrameReceiver::GetPlayoutTime(const EncodedFrame& frame) const { + base::TimeDelta target_playout_delay = target_playout_delay_; + if (frame.new_playout_delay_ms) { + target_playout_delay = base::TimeDelta::FromMilliseconds( + frame.new_playout_delay_ms); + } return lip_sync_reference_time_ + lip_sync_drift_.Current() + RtpDeltaToTimeDelta( - static_cast<int32>(rtp_timestamp - lip_sync_rtp_timestamp_), + static_cast<int32>(frame.rtp_timestamp - lip_sync_rtp_timestamp_), rtp_timebase_) + - target_playout_delay_; + target_playout_delay; } void FrameReceiver::ScheduleNextCastMessage() { diff --git a/media/cast/receiver/frame_receiver.h b/media/cast/receiver/frame_receiver.h index 2ddeeb980c..695c8d0a9b 100644 --- a/media/cast/receiver/frame_receiver.h +++ b/media/cast/receiver/frame_receiver.h @@ -92,7 +92,7 @@ class FrameReceiver : public RtpPayloadFeedback, // 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; + base::TimeTicks GetPlayoutTime(const EncodedFrame& frame) const; // Schedule timing for the next cast message. void ScheduleNextCastMessage(); @@ -130,7 +130,7 @@ class FrameReceiver : public RtpPayloadFeedback, // transmit/retransmit, receive, decode, and render; given its run-time // environment (sender/receiver hardware performance, network conditions, // etc.). - const base::TimeDelta target_playout_delay_; + 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 diff --git a/media/cast/sender/audio_sender.cc b/media/cast/sender/audio_sender.cc index 704621da38..4bf93b30cd 100644 --- a/media/cast/sender/audio_sender.cc +++ b/media/cast/sender/audio_sender.cc @@ -23,16 +23,6 @@ const int kMinSchedulingDelayMs = 1; // well. const int kAudioFrameRate = 100; -// 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 + 2 * target_delay * kAudioFrameRate / base::TimeDelta::FromSeconds(1); - return std::min(kMaxUnackedFrames, frames); -} } // namespace AudioSender::AudioSender(scoped_refptr<CastEnvironment> cast_environment, @@ -43,9 +33,9 @@ AudioSender::AudioSender(scoped_refptr<CastEnvironment> cast_environment, transport_sender, base::TimeDelta::FromMilliseconds(audio_config.rtcp_interval), audio_config.frequency, - audio_config.ssrc), - target_playout_delay_(audio_config.target_playout_delay), - max_unacked_frames_(GetMaxUnackedFrames(target_playout_delay_)), + audio_config.ssrc, + kAudioFrameRate * 2.0, // We lie to increase max outstanding frames. + audio_config.target_playout_delay), configured_encoder_bitrate_(audio_config.bitrate), num_aggressive_rtcp_reports_sent_(0), last_sent_frame_id_(0), @@ -152,6 +142,10 @@ void AudioSender::SendEncodedAudioFrame( SendRtcpReport(is_last_aggressive_report); } + if (send_target_playout_delay_) { + encoded_frame->new_playout_delay_ms = + target_playout_delay_.InMilliseconds(); + } transport_sender_->InsertCodedAudioFrame(*encoded_frame); } @@ -221,10 +215,6 @@ void AudioSender::OnReceivedCastFeedback(const RtcpCastMessage& cast_feedback) { // Only count duplicated ACKs if there is no NACK request in between. // This is to avoid aggresive resend. duplicate_ack_counter_ = 0; - - // A NACK is also used to cancel pending re-transmissions. - transport_sender_->ResendPackets( - true, cast_feedback.missing_frames_and_packets, false, min_rtt_); } const base::TimeTicks now = cast_environment_->Clock()->NowTicks(); @@ -244,14 +234,12 @@ void AudioSender::OnReceivedCastFeedback(const RtcpCastMessage& cast_feedback) { << " 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; + std::vector<uint32> cancel_sending_frames; while (latest_acked_frame_id_ != cast_feedback.ack_frame_id) { latest_acked_frame_id_++; - missing_frames_and_packets[latest_acked_frame_id_] = missing; + cancel_sending_frames.push_back(latest_acked_frame_id_); } - transport_sender_->ResendPackets( - true, missing_frames_and_packets, true, base::TimeDelta()); + transport_sender_->CancelSendingFrames(ssrc_, cancel_sending_frames); latest_acked_frame_id_ = cast_feedback.ack_frame_id; } } @@ -274,20 +262,8 @@ void AudioSender::ResendForKickstart() { 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(); - - // 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_); + transport_sender_->ResendFrameForKickstart(ssrc_, last_sent_frame_id_); } } // namespace cast diff --git a/media/cast/sender/audio_sender.h b/media/cast/sender/audio_sender.h index 02582aebf3..e07b892a73 100644 --- a/media/cast/sender/audio_sender.h +++ b/media/cast/sender/audio_sender.h @@ -73,18 +73,6 @@ class AudioSender : public FrameSender, // Called by the |audio_encoder_| with the next EncodedFrame to send. void SendEncodedAudioFrame(scoped_ptr<EncodedFrame> audio_frame); - // 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_; - - // 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_; diff --git a/media/cast/sender/audio_sender_unittest.cc b/media/cast/sender/audio_sender_unittest.cc index 287630db1a..6c99af9d3c 100644 --- a/media/cast/sender/audio_sender_unittest.cc +++ b/media/cast/sender/audio_sender_unittest.cc @@ -13,7 +13,6 @@ #include "media/cast/cast_environment.h" #include "media/cast/net/cast_transport_config.h" #include "media/cast/net/cast_transport_sender_impl.h" -#include "media/cast/net/rtcp/rtcp_receiver.h" #include "media/cast/sender/audio_sender.h" #include "media/cast/test/fake_single_thread_task_runner.h" #include "media/cast/test/utility/audio_utility.h" @@ -28,7 +27,7 @@ class TestPacketSender : public PacketSender { virtual bool SendPacket(PacketRef packet, const base::Closure& cb) OVERRIDE { - if (RtcpReceiver::IsRtcpPacket(&packet->data[0], packet->data.size())) { + 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 @@ -42,6 +41,10 @@ class TestPacketSender : public PacketSender { return true; } + virtual int64 GetBytesSent() OVERRIDE { + return 0; + } + int number_of_rtp_packets() const { return number_of_rtp_packets_; } int number_of_rtcp_packets() const { return number_of_rtcp_packets_; } diff --git a/media/cast/sender/congestion_control.cc b/media/cast/sender/congestion_control.cc index 70fcfe9dc9..9efe50adbf 100644 --- a/media/cast/sender/congestion_control.cc +++ b/media/cast/sender/congestion_control.cc @@ -113,9 +113,16 @@ 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_); + frame_stats = GetFrameStats(last_acked_frame_ + 1); DCHECK(frame_stats); + if (frame_stats->sent_time.is_null()) { + // Can't ack a frame that hasn't been sent yet. + return; + } + last_acked_frame_++; + if (when < frame_stats->sent_time) + when = frame_stats->sent_time; + frame_stats->ack_time = when; acked_bits_in_history_ += frame_stats->frame_size; dead_time_in_history_ += DeadTime(*last_frame_stats, *frame_stats); diff --git a/media/cast/sender/frame_sender.cc b/media/cast/sender/frame_sender.cc index e5bc95cd50..b531a069fe 100644 --- a/media/cast/sender/frame_sender.cc +++ b/media/cast/sender/frame_sender.cc @@ -14,14 +14,19 @@ FrameSender::FrameSender(scoped_refptr<CastEnvironment> cast_environment, CastTransportSender* const transport_sender, base::TimeDelta rtcp_interval, int frequency, - uint32 ssrc) + uint32 ssrc, + double max_frame_rate, + base::TimeDelta playout_delay) : cast_environment_(cast_environment), transport_sender_(transport_sender), + ssrc_(ssrc), rtp_timestamp_helper_(frequency), rtt_available_(false), rtcp_interval_(rtcp_interval), - ssrc_(ssrc), + max_frame_rate_(max_frame_rate), weak_factory_(this) { + SetTargetPlayoutDelay(playout_delay); + send_target_playout_delay_ = false; } FrameSender::~FrameSender() { @@ -68,5 +73,16 @@ void FrameSender::OnReceivedRtt(base::TimeDelta rtt, max_rtt_ = max_rtt; } +void FrameSender::SetTargetPlayoutDelay( + base::TimeDelta new_target_playout_delay) { + target_playout_delay_ = new_target_playout_delay; + max_unacked_frames_ = + std::min(kMaxUnackedFrames, + 1 + static_cast<int>(target_playout_delay_ * + max_frame_rate_ / + base::TimeDelta::FromSeconds(1))); + send_target_playout_delay_ = true; +} + } // namespace cast } // namespace media diff --git a/media/cast/sender/frame_sender.h b/media/cast/sender/frame_sender.h index 70eccba9ba..5da3927961 100644 --- a/media/cast/sender/frame_sender.h +++ b/media/cast/sender/frame_sender.h @@ -26,9 +26,19 @@ class FrameSender { CastTransportSender* const transport_sender, base::TimeDelta rtcp_interval, int frequency, - uint32 ssrc); + uint32 ssrc, + double max_frame_rate, + base::TimeDelta playout_delay); virtual ~FrameSender(); + // Calling this function is only valid if the receiver supports the + // "extra_playout_delay", rtp extension. + void SetTargetPlayoutDelay(base::TimeDelta new_target_playout_delay); + + base::TimeDelta GetTargetPlayoutDelay() const { + return target_playout_delay_; + } + protected: // Schedule and execute periodic sending of RTCP report. void ScheduleNextRtcpReport(); @@ -50,6 +60,8 @@ class FrameSender { // network layer. CastTransportSender* const transport_sender_; + const uint32 ssrc_; + // Records lip-sync (i.e., mapping of RTP <--> NTP timestamps), and // extrapolates this mapping to any other point in time. RtpTimestampHelper rtp_timestamp_helper_; @@ -61,10 +73,28 @@ class FrameSender { base::TimeDelta min_rtt_; base::TimeDelta max_rtt_; - private: + protected: const base::TimeDelta rtcp_interval_; - const uint32 ssrc_; + // 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.). + base::TimeDelta target_playout_delay_; + + // If true, we transmit the target playout delay to the receiver. + bool send_target_playout_delay_; + + // Max encoded frames generated per second. + double max_frame_rate_; + + // Maximum number of outstanding frames before the encoding and sending of + // new frames shall halt. + int max_unacked_frames_; + + private: // NOTE: Weak pointers must be invalidated before all other member variables. base::WeakPtrFactory<FrameSender> weak_factory_; diff --git a/media/cast/sender/video_sender.cc b/media/cast/sender/video_sender.cc index b9d359bbde..f0e3a1440c 100644 --- a/media/cast/sender/video_sender.cc +++ b/media/cast/sender/video_sender.cc @@ -47,13 +47,9 @@ VideoSender::VideoSender( transport_sender, base::TimeDelta::FromMilliseconds(video_config.rtcp_interval), kVideoFrequency, - video_config.ssrc), - target_playout_delay_(video_config.target_playout_delay), - max_unacked_frames_( - std::min(kMaxUnackedFrames, - 1 + static_cast<int>(target_playout_delay_ * - video_config.max_frame_rate / - base::TimeDelta::FromSeconds(1)))), + video_config.ssrc, + video_config.max_frame_rate, + video_config.target_playout_delay), fixed_bitrate_(GetFixedBitrate(video_config)), num_aggressive_rtcp_reports_sent_(0), frames_in_encoder_(0), @@ -220,6 +216,10 @@ void VideoSender::SendEncodedVideoFrame( congestion_control_.SendFrameToTransport( frame_id, encoded_frame->data.size() * 8, last_send_time_); + if (send_target_playout_delay_) { + encoded_frame->new_playout_delay_ms = + target_playout_delay_.InMilliseconds(); + } transport_sender_->InsertCodedVideoFrame(*encoded_frame); } @@ -308,10 +308,6 @@ void VideoSender::OnReceivedCastFeedback(const RtcpCastMessage& cast_feedback) { // Only count duplicated ACKs if there is no NACK request in between. // This is to avoid aggresive resend. duplicate_ack_counter_ = 0; - - // A NACK is also used to cancel pending re-transmissions. - transport_sender_->ResendPackets( - false, cast_feedback.missing_frames_and_packets, true, rtt); } base::TimeTicks now = cast_environment_->Clock()->NowTicks(); @@ -332,14 +328,12 @@ void VideoSender::OnReceivedCastFeedback(const RtcpCastMessage& cast_feedback) { << " 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; + std::vector<uint32> cancel_sending_frames; while (latest_acked_frame_id_ != cast_feedback.ack_frame_id) { latest_acked_frame_id_++; - missing_frames_and_packets[latest_acked_frame_id_] = missing; + cancel_sending_frames.push_back(latest_acked_frame_id_); } - transport_sender_->ResendPackets( - false, missing_frames_and_packets, true, rtt); + transport_sender_->CancelSendingFrames(ssrc_, cancel_sending_frames); latest_acked_frame_id_ = cast_feedback.ack_frame_id; } } @@ -363,20 +357,8 @@ void VideoSender::ResendForKickstart() { 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(); - - // 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_); + transport_sender_->ResendFrameForKickstart(ssrc_, last_sent_frame_id_); } } // namespace cast diff --git a/media/cast/sender/video_sender.h b/media/cast/sender/video_sender.h index bf96e7bb92..ebada4d26a 100644 --- a/media/cast/sender/video_sender.h +++ b/media/cast/sender/video_sender.h @@ -79,19 +79,6 @@ class VideoSender : public FrameSender, // Called by the |video_encoder_| with the next EncodeFrame to send. void SendEncodedVideoFrame(int requested_bitrate_before_encode, scoped_ptr<EncodedFrame> encoded_frame); - - // 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_; - - // Maximum number of outstanding frames before the encoding and sending of - // new frames shall halt. - const int max_unacked_frames_; - // If this value is non zero then a fixed value is used for bitrate. // If external video encoder is used then bitrate will be fixed to // (min_bitrate + max_bitrate) / 2. diff --git a/media/cast/sender/video_sender_unittest.cc b/media/cast/sender/video_sender_unittest.cc index 06e9dc0678..b9620fcdb2 100644 --- a/media/cast/sender/video_sender_unittest.cc +++ b/media/cast/sender/video_sender_unittest.cc @@ -15,7 +15,6 @@ #include "media/cast/net/cast_transport_config.h" #include "media/cast/net/cast_transport_sender_impl.h" #include "media/cast/net/pacing/paced_sender.h" -#include "media/cast/net/rtcp/rtcp_receiver.h" #include "media/cast/sender/video_sender.h" #include "media/cast/test/fake_single_thread_task_runner.h" #include "media/cast/test/fake_video_encode_accelerator.h" @@ -67,7 +66,7 @@ class TestPacketSender : public PacketSender { callback_ = cb; return false; } - if (RtcpReceiver::IsRtcpPacket(&packet->data[0], packet->data.size())) { + 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 @@ -81,6 +80,10 @@ class TestPacketSender : public PacketSender { return true; } + virtual int64 GetBytesSent() OVERRIDE { + return 0; + } + int number_of_rtp_packets() const { return number_of_rtp_packets_; } int number_of_rtcp_packets() const { return number_of_rtcp_packets_; } @@ -503,32 +506,5 @@ TEST_F(VideoSenderTest, AcksCancelRetransmits) { 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 diff --git a/media/cast/test/cast_benchmarks.cc b/media/cast/test/cast_benchmarks.cc index 57806b35da..07b4de5382 100644 --- a/media/cast/test/cast_benchmarks.cc +++ b/media/cast/test/cast_benchmarks.cc @@ -143,14 +143,15 @@ class CastTransportSenderWrapper : public CastTransportSender { current_time_as_rtp_timestamp); } - // Retransmission request. - virtual void ResendPackets( - bool is_audio, - const MissingFramesAndPacketsMap& missing_packets, - bool cancel_rtx_if_not_in_list, - base::TimeDelta dedupe_window) OVERRIDE { - transport_->ResendPackets( - is_audio, missing_packets, cancel_rtx_if_not_in_list, dedupe_window); + virtual void CancelSendingFrames( + uint32 ssrc, + const std::vector<uint32>& frame_ids) OVERRIDE { + transport_->CancelSendingFrames(ssrc, frame_ids); + } + + virtual void ResendFrameForKickstart(uint32 ssrc, + uint32 frame_id) OVERRIDE { + transport_->ResendFrameForKickstart(ssrc, frame_id); } virtual PacketReceiverCallback PacketReceiverForTesting() OVERRIDE { diff --git a/media/cast/test/end2end_unittest.cc b/media/cast/test/end2end_unittest.cc index bfd9faba89..bb8b62d791 100644 --- a/media/cast/test/end2end_unittest.cc +++ b/media/cast/test/end2end_unittest.cc @@ -189,7 +189,8 @@ class LoopBackTransport : public PacketSender { explicit LoopBackTransport(scoped_refptr<CastEnvironment> cast_environment) : send_packets_(true), drop_packets_belonging_to_odd_frames_(false), - cast_environment_(cast_environment) {} + cast_environment_(cast_environment), + bytes_sent_(0) {} void SetPacketReceiver( const PacketReceiverCallback& packet_receiver, @@ -211,6 +212,7 @@ class LoopBackTransport : public PacketSender { if (!send_packets_) return false; + bytes_sent_ += packet->data.size(); if (drop_packets_belonging_to_odd_frames_) { uint32 frame_id = packet->data[13]; if (frame_id % 2 == 1) @@ -222,6 +224,10 @@ class LoopBackTransport : public PacketSender { return true; } + virtual int64 GetBytesSent() OVERRIDE { + return bytes_sent_; + } + void SetSendPackets(bool send_packets) { send_packets_ = send_packets; } void DropAllPacketsBelongingToOddFrames() { @@ -239,6 +245,7 @@ class LoopBackTransport : public PacketSender { bool drop_packets_belonging_to_odd_frames_; scoped_refptr<CastEnvironment> cast_environment_; scoped_ptr<test::PacketPipe> packet_pipe_; + int64 bytes_sent_; }; // Class that verifies the audio frames coming out of the receiver. @@ -1454,6 +1461,71 @@ TEST_F(End2EndTest, EvilNetwork) { EXPECT_LT((video_ticks_.back().second - test_end).InMilliseconds(), 1000); } +TEST_F(End2EndTest, OldPacketNetwork) { + Configure(CODEC_VIDEO_FAKE, CODEC_AUDIO_PCM16, 32000, 1); + sender_to_receiver_.SetPacketPipe(test::NewRandomDrop(0.01)); + scoped_ptr<test::PacketPipe> echo_chamber( + test::NewDuplicateAndDelay(1, 10 * kFrameTimerMs)); + echo_chamber->AppendToPipe( + test::NewDuplicateAndDelay(1, 20 * kFrameTimerMs)); + echo_chamber->AppendToPipe( + test::NewDuplicateAndDelay(1, 40 * kFrameTimerMs)); + echo_chamber->AppendToPipe( + test::NewDuplicateAndDelay(1, 80 * kFrameTimerMs)); + echo_chamber->AppendToPipe( + test::NewDuplicateAndDelay(1, 160 * kFrameTimerMs)); + + receiver_to_sender_.SetPacketPipe(echo_chamber.Pass()); + Create(); + StartBasicPlayer(); + + SetExpectedVideoPlayoutSmoothness( + base::TimeDelta::FromMilliseconds(kFrameTimerMs) * 90 / 100, + base::TimeDelta::FromMilliseconds(kFrameTimerMs) * 110 / 100, + base::TimeDelta::FromMilliseconds(kFrameTimerMs) / 10); + + int frames_counter = 0; + for (; frames_counter < 10000; ++frames_counter) { + SendFakeVideoFrame(testing_clock_sender_->NowTicks()); + RunTasks(kFrameTimerMs); + } + RunTasks(100 * kFrameTimerMs + 1); // Empty the pipeline. + + EXPECT_EQ(10000ul, video_ticks_.size()); +} + +TEST_F(End2EndTest, TestSetPlayoutDelay) { + Configure(CODEC_VIDEO_FAKE, CODEC_AUDIO_PCM16, 32000, 1); + Create(); + StartBasicPlayer(); + const int kNewDelay = 600; + + int frames_counter = 0; + for (; frames_counter < 200; ++frames_counter) { + SendFakeVideoFrame(testing_clock_sender_->NowTicks()); + RunTasks(kFrameTimerMs); + } + cast_sender_->SetTargetPlayoutDelay( + base::TimeDelta::FromMilliseconds(kNewDelay)); + for (; frames_counter < 400; ++frames_counter) { + SendFakeVideoFrame(testing_clock_sender_->NowTicks()); + RunTasks(kFrameTimerMs); + } + RunTasks(100 * kFrameTimerMs + 1); // Empty the pipeline. + size_t jump = 0; + for (size_t i = 1; i < video_ticks_.size(); i++) { + int64 delta = (video_ticks_[i].second - + video_ticks_[i-1].second).InMilliseconds(); + if (delta > 100) { + EXPECT_EQ(delta, kNewDelay - kTargetPlayoutDelayMs + kFrameTimerMs); + EXPECT_EQ(0u, jump); + jump = i; + } + } + EXPECT_GT(jump, 199u); + EXPECT_LT(jump, 220u); +} + // TODO(pwestin): Add repeatable packet loss test. // TODO(pwestin): Add test for misaligned send get calls. // TODO(pwestin): Add more tests that does not resample. diff --git a/media/cast/test/loopback_transport.cc b/media/cast/test/loopback_transport.cc index 3b72a7ef5a..bc19f016c7 100644 --- a/media/cast/test/loopback_transport.cc +++ b/media/cast/test/loopback_transport.cc @@ -37,7 +37,8 @@ class LoopBackPacketPipe : public test::PacketPipe { LoopBackTransport::LoopBackTransport( scoped_refptr<CastEnvironment> cast_environment) - : cast_environment_(cast_environment) { + : cast_environment_(cast_environment), + bytes_sent_(0) { } LoopBackTransport::~LoopBackTransport() { @@ -48,9 +49,14 @@ bool LoopBackTransport::SendPacket(PacketRef packet, DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); scoped_ptr<Packet> packet_copy(new Packet(packet->data)); packet_pipe_->Send(packet_copy.Pass()); + bytes_sent_ += packet->data.size(); return true; } +int64 LoopBackTransport::GetBytesSent() { + return bytes_sent_; +} + void LoopBackTransport::Initialize( scoped_ptr<test::PacketPipe> pipe, const PacketReceiverCallback& packet_receiver, diff --git a/media/cast/test/loopback_transport.h b/media/cast/test/loopback_transport.h index 8942caa4b9..cf29fbb789 100644 --- a/media/cast/test/loopback_transport.h +++ b/media/cast/test/loopback_transport.h @@ -32,6 +32,8 @@ class LoopBackTransport : public PacketSender { virtual bool SendPacket(PacketRef packet, const base::Closure& cb) OVERRIDE; + virtual int64 GetBytesSent() OVERRIDE; + // Initiailize this loopback transport. // Establish a flow of packets from |pipe| to |packet_receiver|. // The data flow looks like: @@ -45,6 +47,7 @@ class LoopBackTransport : public PacketSender { private: const scoped_refptr<CastEnvironment> cast_environment_; scoped_ptr<test::PacketPipe> packet_pipe_; + int64 bytes_sent_; DISALLOW_COPY_AND_ASSIGN(LoopBackTransport); }; diff --git a/media/cast/test/simulator.cc b/media/cast/test/simulator.cc index b3360c8722..39ff725a86 100644 --- a/media/cast/test/simulator.cc +++ b/media/cast/test/simulator.cc @@ -65,7 +65,7 @@ using media::cast::proto::NetworkSimulationModelType; namespace media { namespace cast { namespace { -const int kTargetDelay = 300; +const int kTargetDelay = 400; const char kSourcePath[] = "source"; const char kModelPath[] = "model"; const char kOutputPath[] = "output"; @@ -230,9 +230,9 @@ void RunSimulation(const base::FilePath& source_path, // Video sender config. VideoSenderConfig video_sender_config = GetDefaultVideoSenderConfig(); - video_sender_config.max_bitrate = 4000000; + video_sender_config.max_bitrate = 2500000; video_sender_config.min_bitrate = 2000000; - video_sender_config.start_bitrate = 4000000; + video_sender_config.start_bitrate = 2000000; video_sender_config.target_playout_delay = base::TimeDelta::FromMilliseconds(kTargetDelay); diff --git a/media/cast/test/utility/udp_proxy.cc b/media/cast/test/utility/udp_proxy.cc index e71678c482..4dbac83fa5 100644 --- a/media/cast/test/utility/udp_proxy.cc +++ b/media/cast/test/utility/udp_proxy.cc @@ -186,6 +186,29 @@ scoped_ptr<PacketPipe> NewRandomUnsortedDelay(double random_delay) { return scoped_ptr<PacketPipe>(new RandomUnsortedDelay(random_delay)).Pass(); } +class DuplicateAndDelay : public RandomUnsortedDelay { + public: + DuplicateAndDelay(double delay_min, + double random_delay) : + RandomUnsortedDelay(random_delay), + delay_min_(delay_min) { + } + virtual void Send(scoped_ptr<Packet> packet) OVERRIDE { + pipe_->Send(scoped_ptr<Packet>(new Packet(*packet.get()))); + RandomUnsortedDelay::Send(packet.Pass()); + } + virtual double GetDelay() OVERRIDE { + return RandomUnsortedDelay::GetDelay() + delay_min_; + } + private: + double delay_min_; +}; + +scoped_ptr<PacketPipe> NewDuplicateAndDelay(double delay_min, + double random_delay) { + return scoped_ptr<PacketPipe>( + new DuplicateAndDelay(delay_min, random_delay)).Pass(); +} class RandomSortedDelay : public PacketPipe { public: diff --git a/media/cast/test/utility/udp_proxy.h b/media/cast/test/utility/udp_proxy.h index 6c72fb6576..967288f78a 100644 --- a/media/cast/test/utility/udp_proxy.h +++ b/media/cast/test/utility/udp_proxy.h @@ -140,6 +140,13 @@ scoped_ptr<PacketPipe> NewConstantDelay(double delay_seconds); // This PacketPipe can reorder packets. scoped_ptr<PacketPipe> NewRandomUnsortedDelay(double delay); +// Duplicates every packet, one is transmitted immediately, +// one is transmitted after a random delay between |delay_min| +// and |delay_min + random_delay|. +// This PacketPipe will reorder packets. +scoped_ptr<PacketPipe> NewDuplicateAndDelay(double delay_min, + double random_delay); + // This PacketPipe inserts a random delay between each packet. // This PacketPipe cannot re-order packets. The delay between each // packet is asically |min_delay| + random( |random_delay| ) diff --git a/media/cdm/aes_decryptor.cc b/media/cdm/aes_decryptor.cc index daafb1e88d..b83c9b185d 100644 --- a/media/cdm/aes_decryptor.cc +++ b/media/cdm/aes_decryptor.cc @@ -245,11 +245,11 @@ void AesDecryptor::CreateSession(const std::string& init_data_type, // For now, the AesDecryptor does not care about |init_data_type| or // |session_type|; just resolve the promise and then fire a message event - // with the |init_data| as the request. + // using the |init_data| as the key ID in the license request. // TODO(jrummell): Validate |init_data_type| and |session_type|. std::vector<uint8> message; if (init_data && init_data_length) - message.assign(init_data, init_data + init_data_length); + CreateLicenseRequest(init_data, init_data_length, session_type, &message); promise->resolve(web_session_id); @@ -280,7 +280,8 @@ void AesDecryptor::UpdateSession(const std::string& web_session_id, response_length); KeyIdAndKeyPairs keys; - if (!ExtractKeysFromJWKSet(key_string, &keys)) { + SessionType session_type = MediaKeys::TEMPORARY_SESSION; + if (!ExtractKeysFromJWKSet(key_string, &keys, &session_type)) { promise->reject( INVALID_ACCESS_ERROR, 0, "response is not a valid JSON Web Key Set."); return; diff --git a/media/cdm/aes_decryptor_unittest.cc b/media/cdm/aes_decryptor_unittest.cc index 91cd6ac365..fe84c07521 100644 --- a/media/cdm/aes_decryptor_unittest.cc +++ b/media/cdm/aes_decryptor_unittest.cc @@ -7,6 +7,8 @@ #include "base/basictypes.h" #include "base/bind.h" +#include "base/json/json_reader.h" +#include "base/values.h" #include "media/base/cdm_promise.h" #include "media/base/decoder_buffer.h" #include "media/base/decrypt_config.h" @@ -24,6 +26,11 @@ using ::testing::StrNe; MATCHER(IsEmpty, "") { return arg.empty(); } MATCHER(IsNotEmpty, "") { return !arg.empty(); } +MATCHER(IsJSONDictionary, "") { + std::string result(arg.begin(), arg.end()); + scoped_ptr<base::Value> root(base::JSONReader().ReadToValue(result)); + return (root.get() && root->GetType() == base::Value::TYPE_DICTIONARY); +} class GURL; @@ -51,7 +58,8 @@ const char kKeyAsJWK[] = " \"kid\": \"AAECAw\"," " \"k\": \"BAUGBwgJCgsMDQ4PEBESEw\"" " }" - " ]" + " ]," + " \"type\": \"temporary\"" "}"; // Same kid as kKeyAsJWK, key to decrypt kEncryptedData2 @@ -287,7 +295,8 @@ class AesDecryptorTest : public testing::Test { std::string CreateSession(const std::vector<uint8>& key_id) { DCHECK(!key_id.empty()); EXPECT_CALL(*this, - OnSessionMessage(IsNotEmpty(), key_id, GURL::EmptyGURL())); + OnSessionMessage( + IsNotEmpty(), IsJSONDictionary(), GURL::EmptyGURL())); decryptor_.CreateSession(std::string(), &key_id[0], key_id.size(), diff --git a/media/cdm/json_web_key.cc b/media/cdm/json_web_key.cc index 4b9d8221f7..03a8a1a705 100644 --- a/media/cdm/json_web_key.cc +++ b/media/cdm/json_web_key.cc @@ -7,6 +7,7 @@ #include "base/base64.h" #include "base/json/json_reader.h" #include "base/json/json_string_value_serializer.h" +#include "base/json/string_escape.h" #include "base/logging.h" #include "base/memory/scoped_ptr.h" #include "base/strings/string_util.h" @@ -19,7 +20,11 @@ const char kKeyTypeTag[] = "kty"; const char kSymmetricKeyValue[] = "oct"; const char kKeyTag[] = "k"; const char kKeyIdTag[] = "kid"; +const char kKeyIdsTag[] = "kids"; const char kBase64Padding = '='; +const char kTypeTag[] = "type"; +const char kPersistentType[] = "persistent"; +const char kTemporaryType[] = "temporary"; // Encodes |input| into a base64 string without padding. static std::string EncodeBase64(const uint8* input, int input_length) { @@ -120,7 +125,9 @@ static bool ConvertJwkToKeyPair(const base::DictionaryValue& jwk, return true; } -bool ExtractKeysFromJWKSet(const std::string& jwk_set, KeyIdAndKeyPairs* keys) { +bool ExtractKeysFromJWKSet(const std::string& jwk_set, + KeyIdAndKeyPairs* keys, + MediaKeys::SessionType* session_type) { if (!base::IsStringASCII(jwk_set)) return false; @@ -156,9 +163,101 @@ bool ExtractKeysFromJWKSet(const std::string& jwk_set, KeyIdAndKeyPairs* keys) { local_keys.push_back(key_pair); } - // Successfully processed all JWKs in the set. + // Successfully processed all JWKs in the set. Now check if "type" is + // specified. + base::Value* value = NULL; + std::string type_id; + if (!dictionary->Get(kTypeTag, &value)) { + // Not specified, so use the default type. + *session_type = MediaKeys::TEMPORARY_SESSION; + } else if (!value->GetAsString(&type_id)) { + DVLOG(1) << "Invalid '" << kTypeTag << "' value"; + return false; + } else if (type_id == kPersistentType) { + *session_type = MediaKeys::PERSISTENT_SESSION; + } else if (type_id == kTemporaryType) { + *session_type = MediaKeys::TEMPORARY_SESSION; + } else { + DVLOG(1) << "Invalid '" << kTypeTag << "' value: " << type_id; + return false; + } + + // All done. keys->swap(local_keys); return true; } +void CreateLicenseRequest(const uint8* key_id, + int key_id_length, + MediaKeys::SessionType session_type, + std::vector<uint8>* license) { + // Create the license request. + scoped_ptr<base::DictionaryValue> request(new base::DictionaryValue()); + scoped_ptr<base::ListValue> list(new base::ListValue()); + list->AppendString(EncodeBase64(key_id, key_id_length)); + request->Set(kKeyIdsTag, list.release()); + + switch (session_type) { + case MediaKeys::TEMPORARY_SESSION: + request->SetString(kTypeTag, kTemporaryType); + break; + case MediaKeys::PERSISTENT_SESSION: + request->SetString(kTypeTag, kPersistentType); + break; + } + + // Serialize the license request as a string. + std::string json; + JSONStringValueSerializer serializer(&json); + serializer.Serialize(*request); + + // Convert the serialized license request into std::vector and return it. + std::vector<uint8> result(json.begin(), json.end()); + license->swap(result); +} + +bool ExtractFirstKeyIdFromLicenseRequest(const std::vector<uint8>& license, + std::vector<uint8>* first_key) { + const std::string license_as_str( + reinterpret_cast<const char*>(!license.empty() ? &license[0] : NULL), + license.size()); + if (!base::IsStringASCII(license_as_str)) + return false; + + scoped_ptr<base::Value> root(base::JSONReader().ReadToValue(license_as_str)); + if (!root.get() || root->GetType() != base::Value::TYPE_DICTIONARY) + return false; + + // Locate the set from the dictionary. + base::DictionaryValue* dictionary = + static_cast<base::DictionaryValue*>(root.get()); + base::ListValue* list_val = NULL; + if (!dictionary->GetList(kKeyIdsTag, &list_val)) { + DVLOG(1) << "Missing '" << kKeyIdsTag << "' parameter or not a list"; + return false; + } + + // Get the first key. + if (list_val->GetSize() < 1) { + DVLOG(1) << "Empty '" << kKeyIdsTag << "' list"; + return false; + } + + std::string encoded_key; + if (!list_val->GetString(0, &encoded_key)) { + DVLOG(1) << "First entry in '" << kKeyIdsTag << "' not a string"; + return false; + } + + std::string decoded_string = DecodeBase64(encoded_key); + if (decoded_string.empty()) { + DVLOG(1) << "Invalid '" << kKeyIdsTag << "' value: " << encoded_key; + return false; + } + + std::vector<uint8> result(decoded_string.begin(), decoded_string.end()); + first_key->swap(result); + return true; +} + } // namespace media diff --git a/media/cdm/json_web_key.h b/media/cdm/json_web_key.h index cb483aeb8b..af028f2fe2 100644 --- a/media/cdm/json_web_key.h +++ b/media/cdm/json_web_key.h @@ -11,21 +11,42 @@ #include "base/basictypes.h" #include "media/base/media_export.h" +#include "media/base/media_keys.h" namespace media { +// The ClearKey license request format (ref: +// https://dvcs.w3.org/hg/html-media/raw-file/default/encrypted-media/encrypted-media.html#clear-key) +// is a JSON object containing the following members: +// "kids" : An array of key IDs. Each element of the array is the base64url +// encoding of the octet sequence containing the key ID value. +// "type" : The requested SessionType. +// An example: +// { "kids":["67ef0gd8pvfd0","77ef0gd8pvfd0"], "type":"temporary" } + +// The ClearKey license format is a JSON Web Key (JWK) Set containing +// representation of the symmetric key to be used for decryption. +// For each JWK in the set, the parameter values are as follows: +// "kty" (key type) : "oct" (octet sequence) +// "alg" (algorithm) : "A128KW" (AES key wrap using a 128-bit key) +// "k" (key value) : The base64url encoding of the octet sequence +// containing the symmetric key value. +// "kid" (key ID) : The base64url encoding of the octet sequence +// containing the key ID value. +// The JSON object may have an optional "type" member value, which may be +// any of the SessionType values. If not specified, the default value of +// "temporary" is used. // A JSON Web Key Set looks like the following in JSON: -// { "keys": [ JWK1, JWK2, ... ] } +// { "keys": [ JWK1, JWK2, ... ], "type":"temporary" } // A symmetric keys JWK looks like the following in JSON: // { "kty":"oct", +// "alg":"A128KW", // "kid":"AQIDBAUGBwgJCgsMDQ4PEA", // "k":"FBUWFxgZGhscHR4fICEiIw" } + // There may be other properties specified, but they are ignored. // Ref: http://tools.ietf.org/html/draft-ietf-jose-json-web-key and: // http://tools.ietf.org/html/draft-jones-jose-json-private-and-symmetric-key -// -// For EME WD, both 'kid' and 'k' are base64 encoded strings, without trailing -// padding. // Vector of [key_id, key_value] pairs. Values are raw binary data, stored in // strings for convenience. @@ -37,10 +58,27 @@ MEDIA_EXPORT std::string GenerateJWKSet(const uint8* key, int key_length, const uint8* key_id, int key_id_length); // Extracts the JSON Web Keys from a JSON Web Key Set. If |input| looks like -// a valid JWK Set, then true is returned and |keys| is updated to contain -// the list of keys found. Otherwise return false. +// a valid JWK Set, then true is returned and |keys| and |session_type| are +// updated to contain the values found. Otherwise return false. MEDIA_EXPORT bool ExtractKeysFromJWKSet(const std::string& jwk_set, - KeyIdAndKeyPairs* keys); + KeyIdAndKeyPairs* keys, + MediaKeys::SessionType* session_type); + +// Create a license request message for the |key_id| and |session_type| +// specified. Currently ClearKey generates a message for each key individually, +// so no need to take a list of |key_id|'s. |license| is updated to contain the +// resulting JSON string. +MEDIA_EXPORT void CreateLicenseRequest(const uint8* key_id, + int key_id_length, + MediaKeys::SessionType session_type, + std::vector<uint8>* license); + +// Extract the first key from the license request message. Returns true if +// |license| is a valid license request and contains at least one key, +// otherwise false and |first_key| is not touched. +MEDIA_EXPORT bool ExtractFirstKeyIdFromLicenseRequest( + const std::vector<uint8>& license, + std::vector<uint8>* first_key); } // namespace media diff --git a/media/cdm/json_web_key_unittest.cc b/media/cdm/json_web_key_unittest.cc index 1018d173c7..49ea32bd45 100644 --- a/media/cdm/json_web_key_unittest.cc +++ b/media/cdm/json_web_key_unittest.cc @@ -19,9 +19,50 @@ class JSONWebKeyTest : public testing::Test { size_t expected_number_of_keys) { DCHECK(!jwk.empty()); KeyIdAndKeyPairs keys; - EXPECT_EQ(expected_result, ExtractKeysFromJWKSet(jwk, &keys)); + MediaKeys::SessionType session_type; + EXPECT_EQ(expected_result, + ExtractKeysFromJWKSet(jwk, &keys, &session_type)); EXPECT_EQ(expected_number_of_keys, keys.size()); } + + void ExtractSessionTypeAndExpect(const std::string& jwk, + bool expected_result, + MediaKeys::SessionType expected_type) { + DCHECK(!jwk.empty()); + KeyIdAndKeyPairs keys; + MediaKeys::SessionType session_type; + EXPECT_EQ(expected_result, + ExtractKeysFromJWKSet(jwk, &keys, &session_type)); + if (expected_result) { + // Only check if successful. + EXPECT_EQ(expected_type, session_type); + } + } + + void CreateLicenseAndExpect(const uint8* key_id, + int key_id_length, + MediaKeys::SessionType session_type, + const std::string& expected_result) { + std::vector<uint8> result; + CreateLicenseRequest(key_id, key_id_length, session_type, &result); + std::string s(result.begin(), result.end()); + EXPECT_EQ(expected_result, s); + } + + void ExtractKeyFromLicenseAndExpect(const std::string& license, + bool expected_result, + const uint8* expected_key, + int expected_key_length) { + std::vector<uint8> license_vector(license.begin(), license.end()); + std::vector<uint8> key; + EXPECT_EQ(expected_result, + ExtractFirstKeyIdFromLicenseRequest(license_vector, &key)); + if (expected_result) { + std::vector<uint8> key_result(expected_key, + expected_key + expected_key_length); + EXPECT_EQ(key_result, key); + } + } }; TEST_F(JSONWebKeyTest, GenerateJWKSet) { @@ -182,5 +223,99 @@ TEST_F(JSONWebKeyTest, ExtractJWKKeys) { ExtractJWKKeysAndExpect(kJwksDuplicateKids, true, 2); } +TEST_F(JSONWebKeyTest, SessionType) { + ExtractSessionTypeAndExpect( + "{\"keys\":[{\"k\":\"AQI\",\"kid\":\"AQI\",\"kty\":\"oct\"}]}", + true, + MediaKeys::TEMPORARY_SESSION); + ExtractSessionTypeAndExpect( + "{\"keys\":[{\"k\":\"AQI\",\"kid\":\"AQI\",\"kty\":\"oct\"}],\"type\":" + "\"temporary\"}", + true, + MediaKeys::TEMPORARY_SESSION); + ExtractSessionTypeAndExpect( + "{\"keys\":[{\"k\":\"AQI\",\"kid\":\"AQI\",\"kty\":\"oct\"}],\"type\":" + "\"persistent\"}", + true, + MediaKeys::PERSISTENT_SESSION); + ExtractSessionTypeAndExpect( + "{\"keys\":[{\"k\":\"AQI\",\"kid\":\"AQI\",\"kty\":\"oct\"}],\"type\":" + "\"unknown\"}", + false, + MediaKeys::TEMPORARY_SESSION); + ExtractSessionTypeAndExpect( + "{\"keys\":[{\"k\":\"AQI\",\"kid\":\"AQI\",\"kty\":\"oct\"}],\"type\":3}", + false, + MediaKeys::TEMPORARY_SESSION); +} + +TEST_F(JSONWebKeyTest, CreateLicense) { + const uint8 data1[] = { 0x01, 0x02 }; + const uint8 data2[] = { 0x01, 0x02, 0x03, 0x04 }; + const uint8 data3[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10 }; + + CreateLicenseAndExpect(data1, + arraysize(data1), + MediaKeys::TEMPORARY_SESSION, + "{\"kids\":[\"AQI\"],\"type\":\"temporary\"}"); + CreateLicenseAndExpect(data1, + arraysize(data1), + MediaKeys::PERSISTENT_SESSION, + "{\"kids\":[\"AQI\"],\"type\":\"persistent\"}"); + CreateLicenseAndExpect(data2, + arraysize(data2), + MediaKeys::TEMPORARY_SESSION, + "{\"kids\":[\"AQIDBA\"],\"type\":\"temporary\"}"); + CreateLicenseAndExpect( + data3, + arraysize(data3), + MediaKeys::PERSISTENT_SESSION, + "{\"kids\":[\"AQIDBAUGBwgJCgsMDQ4PEA\"],\"type\":\"persistent\"}"); +} + +TEST_F(JSONWebKeyTest, ExtractLicense) { + const uint8 data1[] = { 0x01, 0x02 }; + const uint8 data2[] = { 0x01, 0x02, 0x03, 0x04 }; + const uint8 data3[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10 }; + + ExtractKeyFromLicenseAndExpect( + "{\"kids\":[\"AQI\"],\"type\":\"temporary\"}", + true, + data1, + arraysize(data1)); + ExtractKeyFromLicenseAndExpect( + "{\"kids\":[\"AQIDBA\"],\"type\":\"temporary\"}", + true, + data2, + arraysize(data2)); + ExtractKeyFromLicenseAndExpect( + "{\"kids\":[\"AQIDBAUGBwgJCgsMDQ4PEA\"],\"type\":\"persistent\"}", + true, + data3, + arraysize(data3)); + + // Try some incorrect JSON. + ExtractKeyFromLicenseAndExpect("", false, NULL, 0); + ExtractKeyFromLicenseAndExpect("!@#$%^&*()", false, NULL, 0); + + // Valid JSON, but not a dictionary. + ExtractKeyFromLicenseAndExpect("6", false, NULL, 0); + ExtractKeyFromLicenseAndExpect("[\"AQI\"]", false, NULL, 0); + + // Dictionary, but missing expected tag. + ExtractKeyFromLicenseAndExpect("{\"kid\":[\"AQI\"]}", false, NULL, 0); + + // Correct tag, but empty list. + ExtractKeyFromLicenseAndExpect("{\"kids\":[]}", false, NULL, 0); + + // Correct tag, but list doesn't contain a string. + ExtractKeyFromLicenseAndExpect("{\"kids\":[[\"AQI\"]]}", false, NULL, 0); + + // Correct tag, but invalid base64 encoding. + ExtractKeyFromLicenseAndExpect("{\"kids\":[\"!@#$%^&*()\"]}", false, NULL, 0); +} + } // namespace media diff --git a/media/cdm/ppapi/cdm_wrapper.h b/media/cdm/ppapi/cdm_wrapper.h index 708cb5713d..72afa90a02 100644 --- a/media/cdm/ppapi/cdm_wrapper.h +++ b/media/cdm/ppapi/cdm_wrapper.h @@ -130,8 +130,8 @@ class CdmWrapper { // Prior to CDM_6, |init_data_type| was a content type. This helper convererts // an |init_data_type| to a content type. // TODO(sandersd): Remove once Host_4 and Host_5 interfaces are removed. - virtual const char* ConvertInitDataTypeToContentType( - const char* init_data_type) const = 0; + virtual std::string ConvertInitDataTypeToContentType( + const std::string& init_data_type) const = 0; protected: CdmWrapper() {} @@ -368,11 +368,11 @@ class CdmWrapperImpl : public CdmWrapper { v1->timestamp = v2.timestamp; } - virtual const char* ConvertInitDataTypeToContentType( - const char* init_data_type) const { - if (!strcmp(init_data_type, "cenc")) + virtual std::string ConvertInitDataTypeToContentType( + const std::string& init_data_type) const { + if (init_data_type == "cenc") return "video/mp4"; - if (!strcmp(init_data_type, "webm")) + if (init_data_type == "webm") return "video/webm"; return init_data_type; } @@ -413,9 +413,11 @@ void CdmWrapperImpl<cdm::ContentDecryptionModule_4>::CreateSession( cdm::SessionType session_type) { uint32_t session_id = CreateSessionId(); RegisterPromise(session_id, promise_id); + std::string converted_init_data_type = ConvertInitDataTypeToContentType( + std::string(init_data_type, init_data_type_size)); cdm_->CreateSession(session_id, - ConvertInitDataTypeToContentType(init_data_type), - init_data_type_size, + converted_init_data_type.data(), + converted_init_data_type.length(), init_data, init_data_size); } @@ -518,14 +520,16 @@ void CdmWrapperImpl<cdm::ContentDecryptionModule_5>::CreateSession( const uint8_t* init_data, uint32_t init_data_size, cdm::SessionType session_type) { + std::string converted_init_data_type = ConvertInitDataTypeToContentType( + std::string(init_data_type, init_data_type_size)); // TODO(jrummell): Remove this code once |session_type| is passed through // Pepper. When removing, add the header back in for CDM4. PP_DCHECK(session_type == cdm::kTemporary); const char kPersistentSessionHeader[] = "PERSISTENT|"; if (HasHeader(init_data, init_data_size, kPersistentSessionHeader)) { cdm_->CreateSession(promise_id, - ConvertInitDataTypeToContentType(init_data_type), - init_data_type_size, + converted_init_data_type.data(), + converted_init_data_type.length(), init_data + strlen(kPersistentSessionHeader), init_data_size - strlen(kPersistentSessionHeader), cdm::kPersistent); @@ -533,8 +537,8 @@ void CdmWrapperImpl<cdm::ContentDecryptionModule_5>::CreateSession( } cdm_->CreateSession(promise_id, - ConvertInitDataTypeToContentType(init_data_type), - init_data_type_size, + converted_init_data_type.data(), + converted_init_data_type.length(), init_data, init_data_size, session_type); diff --git a/media/filters/decoder_stream.cc b/media/filters/decoder_stream.cc index 4517290141..0e11c4ecbc 100644 --- a/media/filters/decoder_stream.cc +++ b/media/filters/decoder_stream.cc @@ -227,16 +227,17 @@ void DecoderStream<StreamType>::OnDecoderSelected( if (!selected_decoder) { state_ = STATE_UNINITIALIZED; - StreamTraits::FinishInitialization( - base::ResetAndReturn(&init_cb_), selected_decoder.get(), stream_); + base::ResetAndReturn(&init_cb_).Run(false); return; } state_ = STATE_NORMAL; decoder_ = selected_decoder.Pass(); decrypting_demuxer_stream_ = decrypting_demuxer_stream.Pass(); - StreamTraits::FinishInitialization( - base::ResetAndReturn(&init_cb_), decoder_.get(), stream_); + + if (StreamTraits::NeedsBitstreamConversion(decoder_.get())) + stream_->EnableBitstreamConverter(); + base::ResetAndReturn(&init_cb_).Run(true); } template <DemuxerStream::Type StreamType> diff --git a/media/filters/decoder_stream.h b/media/filters/decoder_stream.h index c077a1830e..d6ee126634 100644 --- a/media/filters/decoder_stream.h +++ b/media/filters/decoder_stream.h @@ -192,10 +192,6 @@ class MEDIA_EXPORT DecoderStream { // NOTE: Weak pointers must be invalidated before all other member variables. base::WeakPtrFactory<DecoderStream<StreamType> > weak_factory_; - - // This is required so the VideoFrameStream can access private members in - // FinishInitialization() and ReportStatistics(). - DISALLOW_IMPLICIT_CONSTRUCTORS(DecoderStream); }; template <> diff --git a/media/filters/decoder_stream_traits.cc b/media/filters/decoder_stream_traits.cc index c86862f7fa..824b912825 100644 --- a/media/filters/decoder_stream_traits.cc +++ b/media/filters/decoder_stream_traits.cc @@ -27,19 +27,6 @@ void DecoderStreamTraits<DemuxerStream::AUDIO>::Initialize( decoder->Initialize(config, status_cb, output_cb); } -bool DecoderStreamTraits<DemuxerStream::AUDIO>::FinishInitialization( - const StreamInitCB& init_cb, - DecoderType* decoder, - DemuxerStream* stream) { - DCHECK(stream); - if (!decoder) { - init_cb.Run(false); - return false; - } - init_cb.Run(true); - return true; -} - void DecoderStreamTraits<DemuxerStream::AUDIO>::ReportStatistics( const StatisticsCB& statistics_cb, int bytes_decoded) { @@ -72,19 +59,9 @@ void DecoderStreamTraits<DemuxerStream::VIDEO>::Initialize( decoder->Initialize(config, low_delay, status_cb, output_cb); } -bool DecoderStreamTraits<DemuxerStream::VIDEO>::FinishInitialization( - const StreamInitCB& init_cb, - DecoderType* decoder, - DemuxerStream* stream) { - DCHECK(stream); - if (!decoder) { - init_cb.Run(false); - return false; - } - if (decoder->NeedsBitstreamConversion()) - stream->EnableBitstreamConverter(); - init_cb.Run(true); - return true; +bool DecoderStreamTraits<DemuxerStream::VIDEO>::NeedsBitstreamConversion( + DecoderType* decoder) { + return decoder->NeedsBitstreamConversion(); } void DecoderStreamTraits<DemuxerStream::VIDEO>::ReportStatistics( diff --git a/media/filters/decoder_stream_traits.h b/media/filters/decoder_stream_traits.h index 59e534d2e6..c995962b44 100644 --- a/media/filters/decoder_stream_traits.h +++ b/media/filters/decoder_stream_traits.h @@ -36,9 +36,7 @@ struct DecoderStreamTraits<DemuxerStream::AUDIO> { bool low_delay, const PipelineStatusCB& status_cb, const OutputCB& output_cb); - static bool FinishInitialization(const StreamInitCB& init_cb, - DecoderType* decoder, - DemuxerStream* stream); + static bool NeedsBitstreamConversion(DecoderType* decoder) { return false; } static void ReportStatistics(const StatisticsCB& statistics_cb, int bytes_decoded); static DecoderConfigType GetDecoderConfig(DemuxerStream& stream); @@ -60,9 +58,7 @@ struct DecoderStreamTraits<DemuxerStream::VIDEO> { bool low_delay, const PipelineStatusCB& status_cb, const OutputCB& output_cb); - static bool FinishInitialization(const StreamInitCB& init_cb, - DecoderType* decoder, - DemuxerStream* stream); + static bool NeedsBitstreamConversion(DecoderType* decoder); static void ReportStatistics(const StatisticsCB& statistics_cb, int bytes_decoded); static DecoderConfigType GetDecoderConfig(DemuxerStream& stream); diff --git a/media/filters/gpu_video_decoder.cc b/media/filters/gpu_video_decoder.cc index 16a33eae54..3954e09406 100644 --- a/media/filters/gpu_video_decoder.cc +++ b/media/filters/gpu_video_decoder.cc @@ -422,8 +422,22 @@ void GpuVideoDecoder::PictureReady(const media::Picture& picture) { } const PictureBuffer& pb = it->second; + // Validate picture rectangle from GPU. This is for sanity/security check + // even the rectangle is not used in this class. + if (picture.visible_rect().IsEmpty() || + !gfx::Rect(pb.size()).Contains(picture.visible_rect())) { + NOTREACHED() << "Invalid picture size from VDA: " + << picture.visible_rect().ToString() << " should fit in " + << pb.size().ToString(); + NotifyError(media::VideoDecodeAccelerator::PLATFORM_FAILURE); + return; + } + // Update frame's timestamp. base::TimeDelta timestamp; + // Some of the VDAs don't support and thus don't provide us with visible + // size in picture.size, passing coded size instead, so always drop it and + // use config information instead. gfx::Rect visible_rect; gfx::Size natural_size; GetBufferData(picture.bitstream_buffer_id(), ×tamp, &visible_rect, diff --git a/media/filters/skcanvas_video_renderer.cc b/media/filters/skcanvas_video_renderer.cc index fe1bfde044..003c1951a9 100644 --- a/media/filters/skcanvas_video_renderer.cc +++ b/media/filters/skcanvas_video_renderer.cc @@ -190,6 +190,7 @@ void SkCanvasVideoRenderer::Paint(media::VideoFrame* video_frame, SkCanvas* canvas, const gfx::RectF& dest_rect, uint8 alpha, + SkXfermode::Mode mode, VideoRotation video_rotation) { if (alpha == 0) { return; @@ -233,13 +234,21 @@ void SkCanvasVideoRenderer::Paint(media::VideoFrame* video_frame, last_frame_timestamp_ = video_frame->timestamp(); } - // Use SRC mode so we completely overwrite the buffer (in case we have alpha) - // this means we don't need the extra cost of clearing the buffer first. - paint.setXfermode(SkXfermode::Create(SkXfermode::kSrc_Mode)); + paint.setXfermodeMode(mode); // Paint using |last_frame_|. paint.setFilterLevel(SkPaint::kLow_FilterLevel); canvas->drawBitmapRect(last_frame_, NULL, dest, &paint); } +void SkCanvasVideoRenderer::Copy(media::VideoFrame* video_frame, + SkCanvas* canvas) { + Paint(video_frame, + canvas, + video_frame->visible_rect(), + 0xff, + SkXfermode::kSrc_Mode, + media::VIDEO_ROTATION_0); +} + } // namespace media diff --git a/media/filters/skcanvas_video_renderer.h b/media/filters/skcanvas_video_renderer.h index 0e5c520339..3d1f4da56f 100644 --- a/media/filters/skcanvas_video_renderer.h +++ b/media/filters/skcanvas_video_renderer.h @@ -9,6 +9,7 @@ #include "media/base/media_export.h" #include "media/base/video_rotation.h" #include "third_party/skia/include/core/SkBitmap.h" +#include "third_party/skia/include/core/SkXfermode.h" #include "ui/gfx/rect.h" class SkCanvas; @@ -32,8 +33,12 @@ class MEDIA_EXPORT SkCanvasVideoRenderer { SkCanvas* canvas, const gfx::RectF& dest_rect, uint8 alpha, + SkXfermode::Mode mode, VideoRotation video_rotation); + // Copy |video_frame| on |canvas|. + void Copy(media::VideoFrame* video_frame, SkCanvas* canvas); + private: // An RGB bitmap and corresponding timestamp of the previously converted // video frame data. diff --git a/media/filters/skcanvas_video_renderer_unittest.cc b/media/filters/skcanvas_video_renderer_unittest.cc index 2a8b174837..358ce0ab1e 100644 --- a/media/filters/skcanvas_video_renderer_unittest.cc +++ b/media/filters/skcanvas_video_renderer_unittest.cc @@ -56,8 +56,11 @@ class SkCanvasVideoRendererTest : public testing::Test { void PaintRotated(VideoFrame* video_frame, SkCanvas* canvas, Color color, + SkXfermode::Mode mode, VideoRotation video_rotation); + void Copy(VideoFrame* video_frame, SkCanvas* canvas); + // Getters for various frame sizes. VideoFrame* natural_frame() { return natural_frame_.get(); } VideoFrame* larger_frame() { return larger_frame_.get(); } @@ -189,18 +192,25 @@ SkCanvasVideoRendererTest::SkCanvasVideoRendererTest() SkCanvasVideoRendererTest::~SkCanvasVideoRendererTest() {} void SkCanvasVideoRendererTest::PaintWithoutFrame(SkCanvas* canvas) { - renderer_.Paint(NULL, canvas, kNaturalRect, 0xFF, VIDEO_ROTATION_0); + renderer_.Paint(NULL, + canvas, + kNaturalRect, + 0xFF, + SkXfermode::kSrcOver_Mode, + VIDEO_ROTATION_0); } void SkCanvasVideoRendererTest::Paint(VideoFrame* video_frame, SkCanvas* canvas, Color color) { - PaintRotated(video_frame, canvas, color, VIDEO_ROTATION_0); + PaintRotated( + video_frame, canvas, color, SkXfermode::kSrcOver_Mode, VIDEO_ROTATION_0); } void SkCanvasVideoRendererTest::PaintRotated(VideoFrame* video_frame, SkCanvas* canvas, Color color, + SkXfermode::Mode mode, VideoRotation video_rotation) { switch (color) { case kNone: @@ -215,7 +225,13 @@ void SkCanvasVideoRendererTest::PaintRotated(VideoFrame* video_frame, media::FillYUV(video_frame, 29, 255, 107); break; } - renderer_.Paint(video_frame, canvas, kNaturalRect, 0xFF, video_rotation); + renderer_.Paint( + video_frame, canvas, kNaturalRect, 0xFF, mode, video_rotation); +} + +void SkCanvasVideoRendererTest::Copy(VideoFrame* video_frame, + SkCanvas* canvas) { + renderer_.Copy(video_frame, canvas); } TEST_F(SkCanvasVideoRendererTest, NoFrame) { @@ -226,11 +242,31 @@ TEST_F(SkCanvasVideoRendererTest, NoFrame) { } TEST_F(SkCanvasVideoRendererTest, TransparentFrame) { - // Test that we don't blend with existing canvas contents. FillCanvas(target_canvas(), SK_ColorRED); - Paint(VideoFrame::CreateTransparentFrame(gfx::Size(kWidth, kHeight)), - target_canvas(), - kNone); + PaintRotated(VideoFrame::CreateTransparentFrame(gfx::Size(kWidth, kHeight)), + target_canvas(), + kNone, + SkXfermode::kSrcOver_Mode, + VIDEO_ROTATION_0); + EXPECT_EQ(static_cast<SkColor>(SK_ColorRED), GetColor(target_canvas())); +} + +TEST_F(SkCanvasVideoRendererTest, TransparentFrameSrcMode) { + FillCanvas(target_canvas(), SK_ColorRED); + // SRC mode completely overwrites the buffer. + PaintRotated(VideoFrame::CreateTransparentFrame(gfx::Size(kWidth, kHeight)), + target_canvas(), + kNone, + SkXfermode::kSrc_Mode, + VIDEO_ROTATION_0); + EXPECT_EQ(static_cast<SkColor>(SK_ColorTRANSPARENT), + GetColor(target_canvas())); +} + +TEST_F(SkCanvasVideoRendererTest, CopyTransparentFrame) { + FillCanvas(target_canvas(), SK_ColorRED); + Copy(VideoFrame::CreateTransparentFrame(gfx::Size(kWidth, kHeight)), + target_canvas()); EXPECT_EQ(static_cast<SkColor>(SK_ColorTRANSPARENT), GetColor(target_canvas())); } @@ -325,7 +361,11 @@ TEST_F(SkCanvasVideoRendererTest, CroppedFrame_NoScaling) { TEST_F(SkCanvasVideoRendererTest, Video_Rotation_90) { SkCanvas canvas(AllocBitmap(kWidth, kHeight)); const gfx::Rect crop_rect = cropped_frame()->visible_rect(); - PaintRotated(cropped_frame(), &canvas, kNone, VIDEO_ROTATION_90); + PaintRotated(cropped_frame(), + &canvas, + kNone, + SkXfermode::kSrcOver_Mode, + VIDEO_ROTATION_90); // Check the corners. EXPECT_EQ(SK_ColorGREEN, GetColorAt(&canvas, 0, 0)); EXPECT_EQ(SK_ColorBLACK, GetColorAt(&canvas, kWidth - 1, 0)); @@ -336,7 +376,11 @@ TEST_F(SkCanvasVideoRendererTest, Video_Rotation_90) { TEST_F(SkCanvasVideoRendererTest, Video_Rotation_180) { SkCanvas canvas(AllocBitmap(kWidth, kHeight)); const gfx::Rect crop_rect = cropped_frame()->visible_rect(); - PaintRotated(cropped_frame(), &canvas, kNone, VIDEO_ROTATION_180); + PaintRotated(cropped_frame(), + &canvas, + kNone, + SkXfermode::kSrcOver_Mode, + VIDEO_ROTATION_180); // Check the corners. EXPECT_EQ(SK_ColorBLUE, GetColorAt(&canvas, 0, 0)); EXPECT_EQ(SK_ColorGREEN, GetColorAt(&canvas, kWidth - 1, 0)); @@ -347,7 +391,11 @@ TEST_F(SkCanvasVideoRendererTest, Video_Rotation_180) { TEST_F(SkCanvasVideoRendererTest, Video_Rotation_270) { SkCanvas canvas(AllocBitmap(kWidth, kHeight)); const gfx::Rect crop_rect = cropped_frame()->visible_rect(); - PaintRotated(cropped_frame(), &canvas, kNone, VIDEO_ROTATION_270); + PaintRotated(cropped_frame(), + &canvas, + kNone, + SkXfermode::kSrcOver_Mode, + VIDEO_ROTATION_270); // Check the corners. EXPECT_EQ(SK_ColorRED, GetColorAt(&canvas, 0, 0)); EXPECT_EQ(SK_ColorBLUE, GetColorAt(&canvas, kWidth - 1, 0)); diff --git a/media/formats/mp2t/es_parser.h b/media/formats/mp2t/es_parser.h index 96785eafb7..a1959c20a3 100644 --- a/media/formats/mp2t/es_parser.h +++ b/media/formats/mp2t/es_parser.h @@ -9,6 +9,7 @@ #include "base/callback.h" #include "base/memory/ref_counted.h" #include "base/time/time.h" +#include "media/base/media_export.h" #include "media/base/stream_parser_buffer.h" namespace media { @@ -17,7 +18,7 @@ class StreamParserBuffer; namespace mp2t { -class EsParser { +class MEDIA_EXPORT EsParser { public: typedef base::Callback<void(scoped_refptr<StreamParserBuffer>)> EmitBufferCB; diff --git a/media/formats/mp2t/es_parser_h264.h b/media/formats/mp2t/es_parser_h264.h index 82499f5ec5..df09c88689 100644 --- a/media/formats/mp2t/es_parser_h264.h +++ b/media/formats/mp2t/es_parser_h264.h @@ -32,7 +32,7 @@ namespace mp2t { // Mpeg2 TS spec: "2.14 Carriage of Rec. ITU-T H.264 | ISO/IEC 14496-10 video" // "Each AVC access unit shall contain an access unit delimiter NAL Unit;" // -class MEDIA_EXPORT EsParserH264 : NON_EXPORTED_BASE(public EsParser) { +class MEDIA_EXPORT EsParserH264 : public EsParser { public: typedef base::Callback<void(const VideoDecoderConfig&)> NewVideoConfigCB; diff --git a/media/test/data/eme_player_js/clearkey_player.js b/media/test/data/eme_player_js/clearkey_player.js index 6de8408ce5..92580f3049 100644 --- a/media/test/data/eme_player_js/clearkey_player.js +++ b/media/test/data/eme_player_js/clearkey_player.js @@ -20,7 +20,7 @@ ClearKeyPlayer.prototype.registerEventListeners = function() { ClearKeyPlayer.prototype.onMessage = function(message) { Utils.timeLog('MediaKeySession onMessage', message); var initData = - Utils.getInitDataFromMessage(message, this.testConfig.mediaType); + Utils.getInitDataFromMessage(message, this.testConfig.mediaType, true); var key = Utils.getDefaultKey(this.testConfig.forceInvalidResponse); var jwkSet = Utils.createJWKData(initData, key); if (PROMISES_SUPPORTED) { diff --git a/media/test/data/eme_player_js/prefixed_clearkey_player.js b/media/test/data/eme_player_js/prefixed_clearkey_player.js index cbca7d3305..910a126bf7 100644 --- a/media/test/data/eme_player_js/prefixed_clearkey_player.js +++ b/media/test/data/eme_player_js/prefixed_clearkey_player.js @@ -19,7 +19,7 @@ PrefixedClearKeyPlayer.prototype.registerEventListeners = function() { PrefixedClearKeyPlayer.prototype.onWebkitKeyMessage = function(message) { var initData = - Utils.getInitDataFromMessage(message, this.testConfig.mediaType); + Utils.getInitDataFromMessage(message, this.testConfig.mediaType, false); var key = Utils.getDefaultKey(this.testConfig.forceInvalidResponse); Utils.timeLog('Adding key to sessionID: ' + message.sessionId); message.target.webkitAddKey(this.testConfig.keySystem, key, initData, diff --git a/media/test/data/eme_player_js/utils.js b/media/test/data/eme_player_js/utils.js index 491bac7892..ca62cd1f7b 100644 --- a/media/test/data/eme_player_js/utils.js +++ b/media/test/data/eme_player_js/utils.js @@ -79,6 +79,28 @@ Utils.createJWKData = function(keyId, key) { return Utils.convertToUint8Array(createJWKSet(createJWK(keyId, key))); }; +Utils.extractFirstLicenseKey = function(message) { + // Decodes data (Uint8Array) from base64 string. + // TODO(jrummell): Update once the EME spec is updated to say base64url + // encoding. + function base64Decode(data) { + return atob(data); + } + + function convertToString(data) { + return String.fromCharCode.apply(null, Utils.convertToUint8Array(data)); + } + + try { + var json = JSON.parse(convertToString(message)); + // Decode the first element of 'kids', return it as an Uint8Array. + return Utils.convertToUint8Array(base64Decode(json.kids[0])); + } catch (error) { + // Not valid JSON, so return message untouched as Uint8Array. + return Utils.convertToUint8Array(message); + } +} + Utils.documentLog = function(log, success, time) { if (!docLogs) return; @@ -154,13 +176,17 @@ Utils.getHexString = function(uintArray) { return hex_str; }; -Utils.getInitDataFromMessage = function(message, mediaType) { - var initData = Utils.convertToUint8Array(message.message); +Utils.getInitDataFromMessage = function(message, mediaType, decodeJSONMessage) { + var initData; if (mediaType.indexOf('mp4') != -1) { // Temporary hack for Clear Key in v0.1. // If content uses mp4, then message.message is PSSH data. Instead of // parsing that data we hard code the initData. initData = Utils.convertToUint8Array(KEY_ID); + } else if (decodeJSONMessage) { + initData = Utils.extractFirstLicenseKey(message.message); + } else { + initData = Utils.convertToUint8Array(message.message); } return initData; }; diff --git a/media/video/capture/mac/platform_video_capturing_mac.h b/media/video/capture/mac/platform_video_capturing_mac.h index 29bc49715c..33ad7b6e54 100644 --- a/media/video/capture/mac/platform_video_capturing_mac.h +++ b/media/video/capture/mac/platform_video_capturing_mac.h @@ -21,7 +21,7 @@ class VideoCaptureDeviceMac; // implementation. - (id)initWithFrameReceiver:(media::VideoCaptureDeviceMac*)frameReceiver; -// Set the frame receiver. This method executes the registration in mutual +// Sets the frame receiver. This method executes the registration in mutual // exclusion. // TODO(mcasas): This method and stopCapture() are always called in sequence and // this one is only used to clear the frameReceiver, investigate if both can be @@ -31,8 +31,9 @@ class VideoCaptureDeviceMac; // Sets which capture device to use by name passed as deviceId argument. The // device names are usually obtained via VideoCaptureDevice::GetDeviceNames() // method. This method will also configure all device properties except those in -// setCaptureHeight:widht:frameRate. If |deviceId| is nil, all potential -// configuration is torn down. Returns YES on sucess, NO otherwise. +// setCaptureHeight:width:frameRate. If |deviceId| is nil, capture is stopped +// and all potential configuration is torn down. Returns YES on sucess, NO +// otherwise. - (BOOL)setCaptureDevice:(NSString*)deviceId; // Configures the capture properties. @@ -40,7 +41,7 @@ class VideoCaptureDeviceMac; width:(int)width frameRate:(float)frameRate; -// Start video capturing, register observers. Returns YES on sucess, NO +// Starts video capturing, registers observers. Returns YES on sucess, NO // otherwise. - (BOOL)startCapture; diff --git a/media/video/capture/mac/video_capture_device_mac.h b/media/video/capture/mac/video_capture_device_mac.h index 60da1396d5..7bda9e31e0 100644 --- a/media/video/capture/mac/video_capture_device_mac.h +++ b/media/video/capture/mac/video_capture_device_mac.h @@ -73,11 +73,14 @@ class VideoCaptureDeviceMac : public VideoCaptureDevice { int aspect_numerator, int aspect_denominator); + // Forwarder to VideoCaptureDevice::Client::OnError(). void ReceiveError(const std::string& reason); + // Forwarder to VideoCaptureDevice::Client::OnLog(). + void LogMessage(const std::string& message); + private: void SetErrorState(const std::string& reason); - void LogMessage(const std::string& message); bool UpdateCaptureResolution(); // Flag indicating the internal state. diff --git a/media/video/capture/mac/video_capture_device_mac.mm b/media/video/capture/mac/video_capture_device_mac.mm index 2c1943b0b9..66019b7f64 100644 --- a/media/video/capture/mac/video_capture_device_mac.mm +++ b/media/video/capture/mac/video_capture_device_mac.mm @@ -440,7 +440,6 @@ void VideoCaptureDeviceMac::AllocateAndStart( void VideoCaptureDeviceMac::StopAndDeAllocate() { DCHECK(task_runner_->BelongsToCurrentThread()); DCHECK(state_ == kCapturing || state_ == kError) << state_; - [capture_device_ stopCapture]; [capture_device_ setCaptureDevice:nil]; [capture_device_ setFrameReceiver:nil]; diff --git a/media/video/capture/mac/video_capture_device_qtkit_mac.mm b/media/video/capture/mac/video_capture_device_qtkit_mac.mm index 7f4a853c63..dec7415874 100644 --- a/media/video/capture/mac/video_capture_device_qtkit_mac.mm +++ b/media/video/capture/mac/video_capture_device_qtkit_mac.mm @@ -140,32 +140,14 @@ } else { // Remove the previously set capture device. if (!captureDeviceInput_) { - [self sendErrorString:[NSString + // Being here means stopping a device that never started OK in the first + // place, log it. + [self sendLogString:[NSString stringWithUTF8String:"No video capture device set, on removal."]]; return YES; } - if ([[captureSession_ inputs] count] > 0) { - // The device is still running. - [self stopCapture]; - } - if ([[captureSession_ outputs] count] > 0) { - // Only one output is set for |captureSession_|. - DCHECK_EQ([[captureSession_ outputs] count], 1u); - id output = [[captureSession_ outputs] objectAtIndex:0]; - [output setDelegate:nil]; - - // TODO(shess): QTKit achieves thread safety by posting messages to the - // main thread. As part of -addOutput:, it posts a message to the main - // thread which in turn posts a notification which will run in a future - // spin after the original method returns. -removeOutput: can post a - // main-thread message in between while holding a lock which the - // notification handler will need. Posting either -addOutput: or - // -removeOutput: to the main thread should fix it, remove is likely - // safer. http://crbug.com/152757 - [captureSession_ performSelectorOnMainThread:@selector(removeOutput:) - withObject:output - waitUntilDone:YES]; - } + // Tear down input and output, stop the capture and deregister observers. + [self stopCapture]; [captureSession_ release]; captureSession_ = nil; [captureDeviceInput_ release]; @@ -240,12 +222,30 @@ } - (void)stopCapture { - if ([[captureSession_ inputs] count] == 1) { + // QTKit achieves thread safety and asynchronous execution by posting messages + // to the main thread, e.g. -addOutput:. Both -removeOutput: and -removeInput: + // post a message to the main thread while holding a lock that the + // notification handler might need. To avoid a deadlock, we perform those + // tasks in the main thread. See bugs http://crbug.com/152757 and + // http://crbug.com/399792. + [self performSelectorOnMainThread:@selector(stopCaptureOnUIThread:) + withObject:nil + waitUntilDone:YES]; + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + +- (void)stopCaptureOnUIThread:(id)dummy { + if ([[captureSession_ inputs] count] > 0) { [captureSession_ removeInput:captureDeviceInput_]; + DCHECK_EQ([[captureSession_ inputs] count], 1u); [captureSession_ stopRunning]; } - - [[NSNotificationCenter defaultCenter] removeObserver:self]; + if ([[captureSession_ outputs] count] > 0) { + DCHECK_EQ([[captureSession_ outputs] count], 1u); + id output = [[captureSession_ outputs] objectAtIndex:0]; + [output setDelegate:nil]; + [captureSession_ removeOutput:output]; + } } // |captureOutput| is called by the capture device to deliver a new frame. @@ -345,4 +345,12 @@ [lock_ unlock]; } +- (void)sendLogString:(NSString*)message { + DVLOG(1) << [message UTF8String]; + [lock_ lock]; + if (frameReceiver_) + frameReceiver_->LogMessage([message UTF8String]); + [lock_ unlock]; +} + @end diff --git a/media/video/capture/win/video_capture_device_win.cc b/media/video/capture/win/video_capture_device_win.cc index 29ccf5afcd..a7d1f104cd 100644 --- a/media/video/capture/win/video_capture_device_win.cc +++ b/media/video/capture/win/video_capture_device_win.cc @@ -312,9 +312,14 @@ void VideoCaptureDeviceWin::AllocateAndStart( ScopedMediaType media_type; // Get the windows capability from the capture device. + // GetStreamCaps can return S_FALSE which we consider an error. Therefore the + // FAILED macro can't be used. hr = stream_config->GetStreamCaps( found_capability.stream_index, media_type.Receive(), caps.get()); - if (SUCCEEDED(hr)) { + if (hr != S_OK) { + SetErrorState("Failed to get capture device capabilities"); + return; + } else { if (media_type->formattype == FORMAT_VideoInfo) { VIDEOINFOHEADER* h = reinterpret_cast<VIDEOINFOHEADER*>(media_type->pbFormat); @@ -325,11 +330,13 @@ void VideoCaptureDeviceWin::AllocateAndStart( sink_filter_->SetRequestedMediaFormat(format); // Order the capture device to use this format. hr = stream_config->SetFormat(media_type.get()); + if (FAILED(hr)) { + // TODO(grunell): Log the error. http://crbug.com/405016. + SetErrorState("Failed to set capture device output format"); + return; + } } - if (FAILED(hr)) - SetErrorState("Failed to set capture device output format"); - if (format.pixel_format == PIXEL_FORMAT_MJPEG && !mjpg_filter_.get()) { // Create MJPG filter if we need it. hr = mjpg_filter_.CreateInstance(CLSID_MjpegDec, NULL, CLSCTX_INPROC); diff --git a/media/video/picture.cc b/media/video/picture.cc index 7b32563824..f0510131ea 100644 --- a/media/video/picture.cc +++ b/media/video/picture.cc @@ -22,9 +22,12 @@ PictureBuffer::PictureBuffer(int32 id, texture_mailbox_(texture_mailbox) { } -Picture::Picture(int32 picture_buffer_id, int32 bitstream_buffer_id) +Picture::Picture(int32 picture_buffer_id, + int32 bitstream_buffer_id, + const gfx::Rect& visible_rect) : picture_buffer_id_(picture_buffer_id), - bitstream_buffer_id_(bitstream_buffer_id) { + bitstream_buffer_id_(bitstream_buffer_id), + visible_rect_(visible_rect) { } } // namespace media diff --git a/media/video/picture.h b/media/video/picture.h index d5be2276f2..844e629ef3 100644 --- a/media/video/picture.h +++ b/media/video/picture.h @@ -8,6 +8,7 @@ #include "base/basictypes.h" #include "gpu/command_buffer/common/mailbox.h" #include "media/base/media_export.h" +#include "ui/gfx/geometry/rect.h" #include "ui/gfx/size.h" namespace media { @@ -54,7 +55,9 @@ class MEDIA_EXPORT PictureBuffer { // This is the media-namespace equivalent of PP_Picture_Dev. class MEDIA_EXPORT Picture { public: - Picture(int32 picture_buffer_id, int32 bitstream_buffer_id); + Picture(int32 picture_buffer_id, + int32 bitstream_buffer_id, + const gfx::Rect& visible_rect); // Returns the id of the picture buffer where this picture is contained. int32 picture_buffer_id() const { @@ -70,9 +73,15 @@ class MEDIA_EXPORT Picture { bitstream_buffer_id_ = bitstream_buffer_id; } + // Returns the visible rectangle of the picture. Its size may be smaller + // than the size of the PictureBuffer, as it is the only visible part of the + // Picture contained in the PictureBuffer. + gfx::Rect visible_rect() const { return visible_rect_; } + private: int32 picture_buffer_id_; int32 bitstream_buffer_id_; + gfx::Rect visible_rect_; }; } // namespace media |