summaryrefslogtreecommitdiffstats
path: root/media
diff options
context:
space:
mode:
authorpwestin@google.com <pwestin@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2013-09-11 19:56:02 +0000
committerpwestin@google.com <pwestin@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2013-09-11 19:56:02 +0000
commit3626ff30a79a6d6a3e73c3331eedb819c5bf3aa2 (patch)
treeb3abca2cfaa2a0535ebf34b7e613c4b4fcbb1e91 /media
parente69bccc43f4fed65875b09cb4c0262d7782edde8 (diff)
downloadchromium_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.cc172
-rw-r--r--media/cast/audio_sender/audio_encoder.h63
-rw-r--r--media/cast/audio_sender/audio_encoder_unittest.cc73
-rw-r--r--media/cast/audio_sender/audio_sender.cc166
-rw-r--r--media/cast/audio_sender/audio_sender.gypi30
-rw-r--r--media/cast/audio_sender/audio_sender.h99
-rw-r--r--media/cast/audio_sender/audio_sender_unittest.cc96
-rw-r--r--media/cast/cast.gyp2
-rw-r--r--media/cast/cast_defines.h10
-rw-r--r--media/cast/cast_sender.gyp4
-rw-r--r--media/cast/rtcp/rtcp_defines.h5
-rw-r--r--media/cast/video_sender/video_sender_unittest.cc1
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 {