diff options
author | pwestin@google.com <pwestin@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-09-11 19:56:02 +0000 |
---|---|---|
committer | pwestin@google.com <pwestin@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-09-11 19:56:02 +0000 |
commit | 3626ff30a79a6d6a3e73c3331eedb819c5bf3aa2 (patch) | |
tree | b3abca2cfaa2a0535ebf34b7e613c4b4fcbb1e91 /media | |
parent | e69bccc43f4fed65875b09cb4c0262d7782edde8 (diff) | |
download | chromium_src-3626ff30a79a6d6a3e73c3331eedb819c5bf3aa2.zip chromium_src-3626ff30a79a6d6a3e73c3331eedb819c5bf3aa2.tar.gz chromium_src-3626ff30a79a6d6a3e73c3331eedb819c5bf3aa2.tar.bz2 |
Adding audio sender to cast.
Note: missing gyp to webrtc to link.
BUG=
Review URL: https://chromiumcodereview.appspot.com/23752003
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@222608 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media')
-rw-r--r-- | media/cast/audio_sender/audio_encoder.cc | 172 | ||||
-rw-r--r-- | media/cast/audio_sender/audio_encoder.h | 63 | ||||
-rw-r--r-- | media/cast/audio_sender/audio_encoder_unittest.cc | 73 | ||||
-rw-r--r-- | media/cast/audio_sender/audio_sender.cc | 166 | ||||
-rw-r--r-- | media/cast/audio_sender/audio_sender.gypi | 30 | ||||
-rw-r--r-- | media/cast/audio_sender/audio_sender.h | 99 | ||||
-rw-r--r-- | media/cast/audio_sender/audio_sender_unittest.cc | 96 | ||||
-rw-r--r-- | media/cast/cast.gyp | 2 | ||||
-rw-r--r-- | media/cast/cast_defines.h | 10 | ||||
-rw-r--r-- | media/cast/cast_sender.gyp | 4 | ||||
-rw-r--r-- | media/cast/rtcp/rtcp_defines.h | 5 | ||||
-rw-r--r-- | media/cast/video_sender/video_sender_unittest.cc | 1 |
12 files changed, 714 insertions, 7 deletions
diff --git a/media/cast/audio_sender/audio_encoder.cc b/media/cast/audio_sender/audio_encoder.cc new file mode 100644 index 0000000..175f82b --- /dev/null +++ b/media/cast/audio_sender/audio_encoder.cc @@ -0,0 +1,172 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/cast/audio_sender/audio_encoder.h" + +#include "base/bind.h" +#include "base/logging.h" +#include "base/message_loop/message_loop.h" +#include "media/cast/cast_defines.h" +#include "media/cast/cast_thread.h" +#include "third_party/webrtc/modules/audio_coding/main/interface/audio_coding_module.h" +#include "third_party/webrtc/modules/interface/module_common_types.h" + +namespace media { +namespace cast { + +// 48KHz, 2 channels and 100 ms. +static const int kMaxNumberOfSamples = 48 * 2 * 100; + +// This class is only called from the cast audio encoder thread. +class WebrtEncodedDataCallback : public webrtc::AudioPacketizationCallback { + public: + WebrtEncodedDataCallback(scoped_refptr<CastThread> cast_thread, + AudioCodec codec, + int frequency) + : codec_(codec), + frequency_(frequency), + cast_thread_(cast_thread), + last_timestamp_(0) {} + + virtual int32 SendData( + webrtc::FrameType /*frame_type*/, + uint8 /*payload_type*/, + uint32 timestamp, + const uint8* payload_data, + uint16 payload_size, + const webrtc::RTPFragmentationHeader* /*fragmentation*/) { + scoped_ptr<EncodedAudioFrame> audio_frame(new EncodedAudioFrame()); + audio_frame->codec = codec_; + audio_frame->samples = timestamp - last_timestamp_; + DCHECK(audio_frame->samples <= kMaxNumberOfSamples); + last_timestamp_ = timestamp; + audio_frame->data.insert(audio_frame->data.begin(), + payload_data, + payload_data + payload_size); + + cast_thread_->PostTask(CastThread::MAIN, FROM_HERE, + base::Bind(*frame_encoded_callback_, base::Passed(&audio_frame), + recorded_time_)); + return 0; + } + + void SetEncodedCallbackInfo( + const base::TimeTicks& recorded_time, + const AudioEncoder::FrameEncodedCallback* frame_encoded_callback) { + recorded_time_ = recorded_time; + frame_encoded_callback_ = frame_encoded_callback; + } + + private: + const AudioCodec codec_; + const int frequency_; + scoped_refptr<CastThread> cast_thread_; + uint32 last_timestamp_; + base::TimeTicks recorded_time_; + const AudioEncoder::FrameEncodedCallback* frame_encoded_callback_; +}; + +AudioEncoder::AudioEncoder(scoped_refptr<CastThread> cast_thread, + const AudioSenderConfig& audio_config) + : cast_thread_(cast_thread), + audio_encoder_(webrtc::AudioCodingModule::Create(0)), + webrtc_encoder_callback_( + new WebrtEncodedDataCallback(cast_thread, audio_config.codec, + audio_config.frequency)), + timestamp_(0) { // Must start at 0; used above. + + if (audio_encoder_->InitializeSender() != 0) { + DCHECK(false) << "Invalid webrtc return value"; + } + if (audio_encoder_->RegisterTransportCallback( + webrtc_encoder_callback_.get()) != 0) { + DCHECK(false) << "Invalid webrtc return value"; + } + webrtc::CodecInst send_codec; + send_codec.pltype = audio_config.rtp_payload_type; + send_codec.plfreq = audio_config.frequency; + send_codec.channels = audio_config.channels; + + switch (audio_config.codec) { + case kOpus: + strncpy(send_codec.plname, "opus", sizeof(send_codec.plname)); + send_codec.pacsize = audio_config.frequency / 50; // 20 ms + send_codec.rate = audio_config.bitrate; // 64000 + break; + case kPcm16: + strncpy(send_codec.plname, "L16", sizeof(send_codec.plname)); + send_codec.pacsize = audio_config.frequency / 100; // 10 ms + // TODO(pwestin) bug in webrtc; it should take audio_config.channels into + // account. + send_codec.rate = 8 * 2 * audio_config.frequency; + break; + default: + DCHECK(false) << "Codec must be specified for audio encoder"; + return; + } + if (audio_encoder_->RegisterSendCodec(send_codec) != 0) { + DCHECK(false) << "Invalid webrtc return value; failed to register codec"; + } +} + +AudioEncoder::~AudioEncoder() { + webrtc::AudioCodingModule::Destroy(audio_encoder_); +} + +// Called from main cast thread. +void AudioEncoder::InsertRawAudioFrame( + const PcmAudioFrame* audio_frame, + const base::TimeTicks& recorded_time, + const FrameEncodedCallback& frame_encoded_callback, + const base::Closure release_callback) { + cast_thread_->PostTask(CastThread::AUDIO_ENCODER, FROM_HERE, + base::Bind(&AudioEncoder::EncodeAudioFrameThread, this, audio_frame, + recorded_time, frame_encoded_callback, release_callback)); +} + +// Called from cast audio encoder thread. +void AudioEncoder::EncodeAudioFrameThread( + const PcmAudioFrame* audio_frame, + const base::TimeTicks& recorded_time, + const FrameEncodedCallback& frame_encoded_callback, + const base::Closure release_callback) { + int samples_per_10ms = audio_frame->frequency / 100; + int number_of_10ms_blocks = audio_frame->samples.size() / + (samples_per_10ms * audio_frame->channels); + DCHECK(webrtc::AudioFrame::kMaxDataSizeSamples > samples_per_10ms) + << "webrtc sanity check failed"; + + for (int i = 0; i < number_of_10ms_blocks; ++i) { + webrtc::AudioFrame webrtc_audio_frame; + webrtc_audio_frame.timestamp_ = timestamp_; + + // Due to the webrtc::AudioFrame declaration we need to copy our data into + // the webrtc structure. + memcpy(&webrtc_audio_frame.data_[0], + &audio_frame->samples[i * samples_per_10ms * audio_frame->channels], + samples_per_10ms * audio_frame->channels * sizeof(int16)); + webrtc_audio_frame.samples_per_channel_ = samples_per_10ms; + webrtc_audio_frame.sample_rate_hz_ = audio_frame->frequency; + webrtc_audio_frame.num_channels_ = audio_frame->channels; + + // webrtc::AudioCodingModule is thread safe. + if (audio_encoder_->Add10MsData(webrtc_audio_frame) != 0) { + DCHECK(false) << "Invalid webrtc return value"; + } + timestamp_ += samples_per_10ms; + } + // We are done with the audio frame release it. + cast_thread_->PostTask(CastThread::MAIN, FROM_HERE, release_callback); + + // Note: + // Not all insert of 10 ms will generate a callback with encoded data. + webrtc_encoder_callback_->SetEncodedCallbackInfo(recorded_time, + &frame_encoded_callback); + for (int i = 0; i < number_of_10ms_blocks; ++i) { + audio_encoder_->Process(); + } +} + +} // namespace media +} // namespace cast diff --git a/media/cast/audio_sender/audio_encoder.h b/media/cast/audio_sender/audio_encoder.h new file mode 100644 index 0000000..8aacb0b --- /dev/null +++ b/media/cast/audio_sender/audio_encoder.h @@ -0,0 +1,63 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_CAST_AUDIO_SENDER_AUDIO_ENCODER_H_ +#define MEDIA_CAST_AUDIO_SENDER_AUDIO_ENCODER_H_ + +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "media/cast/cast_config.h" +#include "media/cast/cast_thread.h" +#include "media/cast/rtp_sender/rtp_sender.h" + +namespace webrtc { +class AudioCodingModule; +} + +namespace media { +namespace cast { + +class WebrtEncodedDataCallback; + +// Thread safe class. +// It should be called from the main cast thread; however that is not required. +class AudioEncoder : public base::RefCountedThreadSafe<AudioEncoder> { + public: + typedef base::Callback<void(scoped_ptr<EncodedAudioFrame>, + const base::TimeTicks&)> FrameEncodedCallback; + + AudioEncoder(scoped_refptr<CastThread> cast_thread, + const AudioSenderConfig& audio_config); + + virtual ~AudioEncoder(); + + // The audio_frame must be valid until the closure callback is called. + // The closure callback is called from the main cast thread as soon as + // the encoder is done with the frame; it does not mean that the encoded frame + // has been sent out. + void InsertRawAudioFrame(const PcmAudioFrame* audio_frame, + const base::TimeTicks& recorded_time, + const FrameEncodedCallback& frame_encoded_callback, + const base::Closure callback); + + private: + void EncodeAudioFrameThread( + const PcmAudioFrame* audio_frame, + const base::TimeTicks& recorded_time, + const FrameEncodedCallback& frame_encoded_callback, + const base::Closure release_callback); + + scoped_refptr<CastThread> cast_thread_; + // Can't use scoped_ptr due to protected constructor within webrtc. + webrtc::AudioCodingModule* audio_encoder_; + scoped_ptr<WebrtEncodedDataCallback> webrtc_encoder_callback_; + uint32 timestamp_; + + DISALLOW_COPY_AND_ASSIGN(AudioEncoder); +}; + +} // namespace cast +} // namespace media + +#endif // MEDIA_CAST_AUDIO_SENDER_AUDIO_ENCODER_H_ diff --git a/media/cast/audio_sender/audio_encoder_unittest.cc b/media/cast/audio_sender/audio_encoder_unittest.cc new file mode 100644 index 0000000..5903ab6 --- /dev/null +++ b/media/cast/audio_sender/audio_encoder_unittest.cc @@ -0,0 +1,73 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/bind.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "media/cast/audio_sender/audio_encoder.h" +#include "media/cast/cast_config.h" +#include "media/cast/cast_thread.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace media { +namespace cast { + +static const int64 kStartMillisecond = 123456789; + +using base::RunLoop; + +static void RelaseFrame(const PcmAudioFrame* frame) { + delete frame; +}; + +static void FrameEncoded(scoped_ptr<EncodedAudioFrame> encoded_frame, + const base::TimeTicks& recorded_time) { +} + +class AudioEncoderTest : public ::testing::Test { + protected: + AudioEncoderTest() {} + + virtual void SetUp() { + cast_thread_ = new CastThread(MessageLoopProxy::current(), + MessageLoopProxy::current(), + MessageLoopProxy::current(), + MessageLoopProxy::current(), + MessageLoopProxy::current()); + AudioSenderConfig audio_config; + audio_config.codec = kOpus; + audio_config.use_external_encoder = false; + audio_config.frequency = 48000; + audio_config.channels = 2; + audio_config.bitrate = 64000; + audio_config.rtp_payload_type = 127; + + audio_encoder_ = new AudioEncoder(cast_thread_, audio_config); + } + + ~AudioEncoderTest() {} + + base::MessageLoop loop_; + scoped_refptr<AudioEncoder> audio_encoder_; + scoped_refptr<CastThread> cast_thread_; +}; + +TEST_F(AudioEncoderTest, Encode20ms) { + RunLoop run_loop; + + PcmAudioFrame* audio_frame = new PcmAudioFrame(); + audio_frame->channels = 2; + audio_frame->frequency = 48000; + audio_frame->samples.insert(audio_frame->samples.begin(), 480 * 2 * 2, 123); + + base::TimeTicks recorded_time; + audio_encoder_->InsertRawAudioFrame(audio_frame, recorded_time, + base::Bind(&FrameEncoded), + base::Bind(&RelaseFrame, audio_frame)); + run_loop.RunUntilIdle(); +} + +} // namespace cast +} // namespace media diff --git a/media/cast/audio_sender/audio_sender.cc b/media/cast/audio_sender/audio_sender.cc new file mode 100644 index 0000000..70149dc --- /dev/null +++ b/media/cast/audio_sender/audio_sender.cc @@ -0,0 +1,166 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/cast/audio_sender/audio_sender.h" + +#include "base/bind.h" +#include "base/logging.h" +#include "base/message_loop/message_loop.h" +#include "media/cast/audio_sender/audio_encoder.h" +#include "media/cast/rtcp/rtcp.h" +#include "media/cast/rtp_sender/rtp_sender.h" + +namespace media { +namespace cast { + +const int64 kMinSchedulingDelayMs = 1; + +class LocalRtcpAudioSenderFeedback : public RtcpSenderFeedback { + public: + explicit LocalRtcpAudioSenderFeedback(AudioSender* audio_sender) + : audio_sender_(audio_sender) { + } + + virtual void OnReceivedSendReportRequest() OVERRIDE { + DCHECK(false) << "Invalid callback"; + } + + virtual void OnReceivedReportBlock( + const RtcpReportBlock& report_block) OVERRIDE { + DCHECK(false) << "Invalid callback"; + } + + virtual void OnReceivedIntraFrameRequest() OVERRIDE { + DCHECK(false) << "Invalid callback"; + } + + + virtual void OnReceivedRpsi(uint8 payload_type, + uint64 picture_id) OVERRIDE { + DCHECK(false) << "Invalid callback"; + } + + virtual void OnReceivedRemb(uint32 bitrate) OVERRIDE { + DCHECK(false) << "Invalid callback"; + } + + virtual void OnReceivedNackRequest( + const std::list<uint16>& nack_sequence_numbers) OVERRIDE { + DCHECK(false) << "Invalid callback"; + } + + virtual void OnReceivedCastFeedback( + const RtcpCastMessage& cast_feedback) OVERRIDE { + if (!cast_feedback.missing_frames_and_packets_.empty()) { + audio_sender_->ResendPackets(cast_feedback.missing_frames_and_packets_); + } + VLOG(1) << "Received audio ACK " + << static_cast<int>(cast_feedback.ack_frame_id_); + } + + private: + AudioSender* audio_sender_; +}; + +class LocalRtpSenderStatistics : public RtpSenderStatistics { + public: + explicit LocalRtpSenderStatistics(RtpSender* rtp_sender) + : rtp_sender_(rtp_sender) { + } + + virtual void GetStatistics(const base::TimeTicks& now, + RtcpSenderInfo* sender_info) OVERRIDE { + rtp_sender_->RtpStatistics(now, sender_info); + } + + private: + RtpSender* rtp_sender_; +}; + +AudioSender::AudioSender(scoped_refptr<CastThread> cast_thread, + const AudioSenderConfig& audio_config, + PacedPacketSender* const paced_packet_sender) + : incoming_feedback_ssrc_(audio_config.incoming_feedback_ssrc), + cast_thread_(cast_thread), + rtp_sender_(&audio_config, NULL, paced_packet_sender), + rtcp_feedback_(new LocalRtcpAudioSenderFeedback(this)), + rtp_audio_sender_statistics_( + new LocalRtpSenderStatistics(&rtp_sender_)), + rtcp_(rtcp_feedback_.get(), + paced_packet_sender, + rtp_audio_sender_statistics_.get(), + NULL, + audio_config.rtcp_mode, + base::TimeDelta::FromMilliseconds(audio_config.rtcp_interval), + true, + audio_config.sender_ssrc, + audio_config.rtcp_c_name), + clock_(&default_tick_clock_), + weak_factory_(this) { + + rtcp_.SetRemoteSSRC(audio_config.incoming_feedback_ssrc); + + if (!audio_config.use_external_encoder) { + audio_encoder_ = new AudioEncoder(cast_thread, audio_config); + } + ScheduleNextRtcpReport(); +} + +AudioSender::~AudioSender() {} + +void AudioSender::InsertRawAudioFrame( + const PcmAudioFrame* audio_frame, + const base::TimeTicks& recorded_time, + const base::Closure callback) { + DCHECK(audio_encoder_.get()) << "Invalid internal state"; + + + audio_encoder_->InsertRawAudioFrame(audio_frame, recorded_time, + base::Bind(&AudioSender::SendEncodedAudioFrame, + weak_factory_.GetWeakPtr()), + callback); +} + +void AudioSender::InsertCodedAudioFrame(const EncodedAudioFrame* audio_frame, + const base::TimeTicks& recorded_time, + const base::Closure callback) { + DCHECK(audio_encoder_.get() == NULL) << "Invalid internal state"; + rtp_sender_.IncomingEncodedAudioFrame(audio_frame, recorded_time); + callback.Run(); +} + +void AudioSender::SendEncodedAudioFrame( + scoped_ptr<EncodedAudioFrame> audio_frame, + const base::TimeTicks& recorded_time) { + rtp_sender_.IncomingEncodedAudioFrame(audio_frame.get(), recorded_time); +} + +void AudioSender::ResendPackets( + const MissingFramesAndPacketsMap& missing_frames_and_packets) { + rtp_sender_.ResendPackets(missing_frames_and_packets); +} + +void AudioSender::IncomingRtcpPacket(const uint8* packet, int length) { + rtcp_.IncomingRtcpPacket(packet, length); +} + +void AudioSender::ScheduleNextRtcpReport() { + base::TimeDelta time_to_next = + rtcp_.TimeToSendNextRtcpReport() - clock_->NowTicks(); + + time_to_next = std::max(time_to_next, + base::TimeDelta::FromMilliseconds(kMinSchedulingDelayMs)); + + cast_thread_->PostDelayedTask(CastThread::MAIN, FROM_HERE, + base::Bind(&AudioSender::SendRtcpReport, weak_factory_.GetWeakPtr()), + time_to_next); +} + +void AudioSender::SendRtcpReport() { + rtcp_.SendRtcpReport(incoming_feedback_ssrc_); + ScheduleNextRtcpReport(); +} + +} // namespace cast +} // namespace media diff --git a/media/cast/audio_sender/audio_sender.gypi b/media/cast/audio_sender/audio_sender.gypi new file mode 100644 index 0000000..3e2a563 --- /dev/null +++ b/media/cast/audio_sender/audio_sender.gypi @@ -0,0 +1,30 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +{ + 'targets': [ + { + 'target_name': 'audio_sender', + 'type': 'static_library', + 'include_dirs': [ + '<(DEPTH)/', + '<(DEPTH)/third_party/', + '<(DEPTH)/third_party/webrtc', + ], + 'sources': [ + 'audio_encoder.h', + 'audio_encoder.cc', + 'audio_sender.h', + 'audio_sender.cc', + ], # source + 'dependencies': [ + '<(DEPTH)/media/cast/rtcp/rtcp.gyp:cast_rtcp', + '<(DEPTH)/media/cast/rtp_sender/rtp_sender.gyp:*', + '<(DEPTH)/third_party/webrtc/webrtc.gyp:webrtc', + ], + }, + ], +} + + diff --git a/media/cast/audio_sender/audio_sender.h b/media/cast/audio_sender/audio_sender.h new file mode 100644 index 0000000..30d48b4 --- /dev/null +++ b/media/cast/audio_sender/audio_sender.h @@ -0,0 +1,99 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_CAST_AUDIO_SENDER_H_ +#define MEDIA_CAST_AUDIO_SENDER_H_ + +#include "base/callback.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/threading/non_thread_safe.h" +#include "base/time/default_tick_clock.h" +#include "base/time/tick_clock.h" +#include "base/time/time.h" +#include "media/cast/cast_config.h" +#include "media/cast/cast_thread.h" +#include "media/cast/rtcp/rtcp.h" +#include "media/cast/rtp_sender/rtp_sender.h" + +namespace media { +namespace cast { + +class AudioEncoder; +class LocalRtcpAudioSenderFeedback; +class LocalRtpSenderStatistics; +class PacedPacketSender; + +// This class is not thread safe. +// It's only called from the main cast thread. +class AudioSender : public base::NonThreadSafe, + public base::SupportsWeakPtr<AudioSender> { + public: + AudioSender(scoped_refptr<CastThread> cast_thread, + const AudioSenderConfig& audio_config, + PacedPacketSender* const paced_packet_sender); + + virtual ~AudioSender(); + + // The audio_frame must be valid until the closure callback is called. + // The closure callback is called from the main cast thread as soon as + // the encoder is done with the frame; it does not mean that the encoded frame + // has been sent out. + void InsertRawAudioFrame(const PcmAudioFrame* audio_frame, + const base::TimeTicks& recorded_time, + const base::Closure callback); + + // The audio_frame must be valid until the closure callback is called. + // The closure callback is called from the main cast thread as soon as + // the cast sender is done with the frame; it does not mean that the encoded + // frame has been sent out. + void InsertCodedAudioFrame(const EncodedAudioFrame* audio_frame, + const base::TimeTicks& recorded_time, + const base::Closure callback); + + // Only called from the main cast thread. + void IncomingRtcpPacket(const uint8* packet, int length); + + // Only used for testing. + void set_clock(base::TickClock* clock) { + clock_ = clock; + rtcp_.set_clock(clock); + rtp_sender_.set_clock(clock); + } + + protected: + void SendEncodedAudioFrame(scoped_ptr<EncodedAudioFrame> audio_frame, + const base::TimeTicks& recorded_time); + + private: + friend class LocalRtcpAudioSenderFeedback; + + void ResendPackets( + const MissingFramesAndPacketsMap& missing_frames_and_packets); + + void ScheduleNextRtcpReport(); + void SendRtcpReport(); + + base::DefaultTickClock default_tick_clock_; + base::TickClock* clock_; + + base::WeakPtrFactory<AudioSender> weak_factory_; + + const uint32 incoming_feedback_ssrc_; + scoped_refptr<CastThread> cast_thread_; + scoped_refptr<AudioEncoder> audio_encoder_; + RtpSender rtp_sender_; + scoped_ptr<LocalRtpSenderStatistics> rtp_audio_sender_statistics_; + scoped_ptr<LocalRtcpAudioSenderFeedback> rtcp_feedback_; + Rtcp rtcp_; + + DISALLOW_COPY_AND_ASSIGN(AudioSender); +}; + +} // namespace cast +} // namespace media + +#endif // MEDIA_CAST_AUDIO_SENDER_H_ + diff --git a/media/cast/audio_sender/audio_sender_unittest.cc b/media/cast/audio_sender/audio_sender_unittest.cc new file mode 100644 index 0000000..c08b3c8 --- /dev/null +++ b/media/cast/audio_sender/audio_sender_unittest.cc @@ -0,0 +1,96 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/bind.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "base/test/simple_test_tick_clock.h" +#include "base/threading/platform_thread.h" +#include "media/cast/audio_sender/audio_sender.h" +#include "media/cast/cast_config.h" +#include "media/cast/cast_thread.h" +#include "media/cast/pacing/mock_paced_packet_sender.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace media { +namespace cast { + +static const int64 kStartMillisecond = 123456789; + +using base::RunLoop; +using testing::_; + +static void RelaseFrame(const PcmAudioFrame* frame) { + delete frame; +}; + +class AudioSenderTest : public ::testing::Test { + protected: + AudioSenderTest() { + testing_clock_.Advance( + base::TimeDelta::FromMilliseconds(kStartMillisecond)); + } + + virtual void SetUp() { + cast_thread_ = new CastThread(MessageLoopProxy::current(), + MessageLoopProxy::current(), + MessageLoopProxy::current(), + MessageLoopProxy::current(), + MessageLoopProxy::current()); + AudioSenderConfig audio_config; + audio_config.codec = kOpus; + audio_config.use_external_encoder = false; + audio_config.frequency = 48000; + audio_config.channels = 2; + audio_config.bitrate = 64000; + audio_config.rtp_payload_type = 127; + + audio_sender_.reset( + new AudioSender(cast_thread_, audio_config, &mock_transport_)); + audio_sender_->set_clock(&testing_clock_); + } + + ~AudioSenderTest() {} + + base::MessageLoop loop_; + MockPacedPacketSender mock_transport_; + base::SimpleTestTickClock testing_clock_; + scoped_ptr<AudioSender> audio_sender_; + scoped_refptr<CastThread> cast_thread_; +}; + +TEST_F(AudioSenderTest, Encode20ms) { + EXPECT_CALL(mock_transport_, SendPacket(_, _)).Times(1); + + RunLoop run_loop; + + PcmAudioFrame* audio_frame = new PcmAudioFrame(); + audio_frame->channels = 2; + audio_frame->frequency = 48000; + audio_frame->samples.insert(audio_frame->samples.begin(), 480 * 2 * 2, 123); + + base::TimeTicks recorded_time; + audio_sender_->InsertRawAudioFrame(audio_frame, recorded_time, + base::Bind(&RelaseFrame, audio_frame)); + run_loop.RunUntilIdle(); +} + +TEST_F(AudioSenderTest, RtcpTimer) { + EXPECT_CALL(mock_transport_, SendRtcpPacket(_)).Times(1); + + RunLoop run_loop; + // Make sure that we send at least one RTCP packet. + base::TimeDelta max_rtcp_timeout = + base::TimeDelta::FromMilliseconds(1 + kDefaultRtcpIntervalMs * 3 / 2); + testing_clock_.Advance(max_rtcp_timeout); + + // TODO(pwestin): haven't found a way to make the post delayed task to go + // faster than a real-time. + base::PlatformThread::Sleep(max_rtcp_timeout); + run_loop.RunUntilIdle(); +} + +} // namespace cast +} // namespace media diff --git a/media/cast/cast.gyp b/media/cast/cast.gyp index ecced89..9b632f4 100644 --- a/media/cast/cast.gyp +++ b/media/cast/cast.gyp @@ -58,6 +58,8 @@ '<(DEPTH)/third_party/webrtc/', ], 'sources': [ + 'audio_sender/audio_encoder_unittest.cc', + 'audio_sender/audio_sender_unittest.cc', 'congestion_control/congestion_control_unittest.cc', 'framer/cast_message_builder_unittest.cc', 'framer/frame_buffer_unittest.cc', diff --git a/media/cast/cast_defines.h b/media/cast/cast_defines.h index 01edca5..1371732 100644 --- a/media/cast/cast_defines.h +++ b/media/cast/cast_defines.h @@ -5,6 +5,9 @@ #ifndef MEDIA_CAST_CAST_DEFINES_H_ #define MEDIA_CAST_CAST_DEFINES_H_ +#include <map> +#include <set> + #include "base/basictypes.h" #include "base/compiler_specific.h" #include "base/time/time.h" @@ -35,6 +38,13 @@ enum DefaultSettings { kDefaultRtpMaxDelayMs = 100, }; +const uint16 kRtcpCastAllPacketsLost = 0xffff; + +// Each uint16 represents one packet id within a cast frame. +typedef std::set<uint16> PacketIdSet; +// Each uint8 represents one cast frame. +typedef std::map<uint8, PacketIdSet> MissingFramesAndPacketsMap; + // TODO(pwestin): Re-factor the functions bellow into a class with static // methods. diff --git a/media/cast/cast_sender.gyp b/media/cast/cast_sender.gyp index 9206dd0..591a3b7 100644 --- a/media/cast/cast_sender.gyp +++ b/media/cast/cast_sender.gyp @@ -4,7 +4,7 @@ { 'includes': [ -# 'audio_sender/audio_sender.gypi', + 'audio_sender/audio_sender.gypi', 'congestion_control/congestion_control.gypi', 'video_sender/video_sender.gypi', ], @@ -18,7 +18,7 @@ # 'cast_sender_impl.h', ], # source 'dependencies': [ -# 'audio_sender', + 'audio_sender', 'congestion_control', 'pacing/paced_sender.gyp:paced_sender', 'rtcp/rtcp.gyp:cast_rtcp', diff --git a/media/cast/rtcp/rtcp_defines.h b/media/cast/rtcp/rtcp_defines.h index 102e321..f0635f8 100644 --- a/media/cast/rtcp/rtcp_defines.h +++ b/media/cast/rtcp/rtcp_defines.h @@ -15,11 +15,6 @@ namespace media { namespace cast { -const uint16 kRtcpCastAllPacketsLost = 0xffff; - -typedef std::set<uint16> PacketIdSet; -typedef std::map<uint8, PacketIdSet> MissingFramesAndPacketsMap; - class RtcpCastMessage { public: explicit RtcpCastMessage(uint32 media_ssrc); diff --git a/media/cast/video_sender/video_sender_unittest.cc b/media/cast/video_sender/video_sender_unittest.cc index 4bfe33e..91e74f1 100644 --- a/media/cast/video_sender/video_sender_unittest.cc +++ b/media/cast/video_sender/video_sender_unittest.cc @@ -17,6 +17,7 @@ #include "media/cast/video_sender/mock_video_encoder_controller.h" #include "media/cast/video_sender/video_sender.h" #include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" namespace media { namespace cast { |