summaryrefslogtreecommitdiff
path: root/media/cast/audio_sender
diff options
context:
space:
mode:
authorTorne (Richard Coles) <torne@google.com>2013-11-28 11:55:43 +0000
committerTorne (Richard Coles) <torne@google.com>2013-11-28 11:55:43 +0000
commitf2477e01787aa58f445919b809d89e252beef54f (patch)
tree2db962b4af39f0db3a5f83b314373d0530c484b8 /media/cast/audio_sender
parent7daea1dd5ff7e419322de831b642d81af3247912 (diff)
downloadchromium_org-f2477e01787aa58f445919b809d89e252beef54f.tar.gz
Merge from Chromium at DEPS revision 237746
This commit was generated by merge_to_master.py. Change-Id: I8997af4cddfeb09a7c26f7e8e672c712cab461ea
Diffstat (limited to 'media/cast/audio_sender')
-rw-r--r--media/cast/audio_sender/audio_encoder.cc367
-rw-r--r--media/cast/audio_sender/audio_encoder.h57
-rw-r--r--media/cast/audio_sender/audio_encoder_unittest.cc214
-rw-r--r--media/cast/audio_sender/audio_sender.cc133
-rw-r--r--media/cast/audio_sender/audio_sender.gypi6
-rw-r--r--media/cast/audio_sender/audio_sender.h34
-rw-r--r--media/cast/audio_sender/audio_sender_unittest.cc66
7 files changed, 623 insertions, 254 deletions
diff --git a/media/cast/audio_sender/audio_encoder.cc b/media/cast/audio_sender/audio_encoder.cc
index 3cfca0dfc8..a82d1de39a 100644
--- a/media/cast/audio_sender/audio_encoder.cc
+++ b/media/cast/audio_sender/audio_encoder.cc
@@ -4,171 +4,288 @@
#include "media/cast/audio_sender/audio_encoder.h"
+#include <algorithm>
+
#include "base/bind.h"
+#include "base/bind_helpers.h"
#include "base/logging.h"
#include "base/message_loop/message_loop.h"
+#include "base/sys_byteorder.h"
+#include "base/time/time.h"
+#include "media/base/audio_bus.h"
#include "media/cast/cast_defines.h"
#include "media/cast/cast_environment.h"
-#include "third_party/webrtc/modules/audio_coding/main/interface/audio_coding_module.h"
-#include "third_party/webrtc/modules/interface/module_common_types.h"
+#include "third_party/opus/src/include/opus.h"
namespace media {
namespace cast {
-// 48KHz, 2 channels and 100 ms.
-static const int kMaxNumberOfSamples = 48 * 2 * 100;
+void LogAudioEncodedEvent(CastEnvironment* const cast_environment,
+ const base::TimeTicks& recorded_time) {
+ // TODO(mikhal): Resolve timestamp calculation for audio.
+ cast_environment->Logging()->InsertFrameEvent(kAudioFrameEncoded,
+ GetVideoRtpTimestamp(recorded_time), kFrameIdUnknown);
+}
-// This class is only called from the cast audio encoder thread.
-class WebrtEncodedDataCallback : public webrtc::AudioPacketizationCallback {
+// Base class that handles the common problem of feeding one or more AudioBus'
+// data into a 10 ms buffer and then, once the buffer is full, encoding the
+// signal and emitting an EncodedAudioFrame via the FrameEncodedCallback.
+//
+// Subclasses complete the implementation by handling the actual encoding
+// details.
+class AudioEncoder::ImplBase {
public:
- WebrtEncodedDataCallback(scoped_refptr<CastEnvironment> cast_environment,
- AudioCodec codec,
- int frequency)
- : codec_(codec),
- frequency_(frequency),
- cast_environment_(cast_environment),
- 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*/) OVERRIDE {
- 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);
+ ImplBase(CastEnvironment* cast_environment,
+ AudioCodec codec, int num_channels, int sampling_rate,
+ const FrameEncodedCallback& callback)
+ : cast_environment_(cast_environment),
+ codec_(codec), num_channels_(num_channels),
+ samples_per_10ms_(sampling_rate / 100),
+ callback_(callback),
+ buffer_fill_end_(0),
+ frame_id_(0) {
+ CHECK_GT(num_channels_, 0);
+ CHECK_GT(samples_per_10ms_, 0);
+ CHECK_EQ(sampling_rate % 100, 0);
+ CHECK_LE(samples_per_10ms_ * num_channels_,
+ EncodedAudioFrame::kMaxNumberOfSamples);
+ }
- cast_environment_->PostTask(CastEnvironment::MAIN, FROM_HERE,
- base::Bind(*frame_encoded_callback_, base::Passed(&audio_frame),
- recorded_time_));
- return 0;
+ virtual ~ImplBase() {}
+
+ void EncodeAudio(const AudioBus* audio_bus,
+ const base::TimeTicks& recorded_time,
+ const base::Closure& done_callback) {
+ int src_pos = 0;
+ while (src_pos < audio_bus->frames()) {
+ const int num_samples_to_xfer =
+ std::min(samples_per_10ms_ - buffer_fill_end_,
+ audio_bus->frames() - src_pos);
+ DCHECK_EQ(audio_bus->channels(), num_channels_);
+ TransferSamplesIntoBuffer(
+ audio_bus, src_pos, buffer_fill_end_, num_samples_to_xfer);
+ src_pos += num_samples_to_xfer;
+ buffer_fill_end_ += num_samples_to_xfer;
+
+ if (src_pos == audio_bus->frames()) {
+ cast_environment_->PostTask(CastEnvironment::MAIN, FROM_HERE,
+ done_callback);
+ // Note: |audio_bus| is now invalid..
+ }
+
+ if (buffer_fill_end_ == samples_per_10ms_) {
+ scoped_ptr<EncodedAudioFrame> audio_frame(new EncodedAudioFrame());
+ audio_frame->codec = codec_;
+ audio_frame->frame_id = frame_id_++;
+ audio_frame->samples = samples_per_10ms_;
+ if (EncodeFromFilledBuffer(&audio_frame->data)) {
+ // Compute an offset to determine the recorded time for the first
+ // audio sample in the buffer.
+ const base::TimeDelta buffer_time_offset =
+ (buffer_fill_end_ - src_pos) *
+ base::TimeDelta::FromMilliseconds(10) / samples_per_10ms_;
+ // TODO(miu): Consider batching EncodedAudioFrames so we only post a
+ // at most one task for each call to this method.
+ cast_environment_->PostTask(
+ CastEnvironment::MAIN, FROM_HERE,
+ base::Bind(callback_, base::Passed(&audio_frame),
+ recorded_time - buffer_time_offset));
+ }
+ buffer_fill_end_ = 0;
+ }
+ }
}
- void SetEncodedCallbackInfo(
- const base::TimeTicks& recorded_time,
- const AudioEncoder::FrameEncodedCallback* frame_encoded_callback) {
- recorded_time_ = recorded_time;
- frame_encoded_callback_ = frame_encoded_callback;
+ protected:
+ virtual void TransferSamplesIntoBuffer(const AudioBus* audio_bus,
+ int source_offset,
+ int buffer_fill_offset,
+ int num_samples) = 0;
+ virtual bool EncodeFromFilledBuffer(std::string* out) = 0;
+
+ CastEnvironment* const cast_environment_;
+ const AudioCodec codec_;
+ const int num_channels_;
+ const int samples_per_10ms_;
+ const FrameEncodedCallback callback_;
+
+ private:
+ // In the case where a call to EncodeAudio() cannot completely fill the
+ // buffer, this points to the position at which to populate data in a later
+ // call.
+ int buffer_fill_end_;
+
+ // A counter used to label EncodedAudioFrames.
+ uint32 frame_id_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ImplBase);
+};
+
+class AudioEncoder::OpusImpl : public AudioEncoder::ImplBase {
+ public:
+ OpusImpl(CastEnvironment* cast_environment,
+ int num_channels, int sampling_rate, int bitrate,
+ const FrameEncodedCallback& callback)
+ : ImplBase(cast_environment, kOpus, num_channels, sampling_rate,
+ callback),
+ encoder_memory_(new uint8[opus_encoder_get_size(num_channels)]),
+ opus_encoder_(reinterpret_cast<OpusEncoder*>(encoder_memory_.get())),
+ buffer_(new float[num_channels * samples_per_10ms_]) {
+ CHECK_EQ(opus_encoder_init(opus_encoder_, sampling_rate, num_channels,
+ OPUS_APPLICATION_AUDIO),
+ OPUS_OK);
+ if (bitrate <= 0) {
+ // Note: As of 2013-10-31, the encoder in "auto bitrate" mode would use a
+ // variable bitrate up to 102kbps for 2-channel, 48 kHz audio and a 10 ms
+ // frame size. The opus library authors may, of course, adjust this in
+ // later versions.
+ bitrate = OPUS_AUTO;
+ }
+ CHECK_EQ(opus_encoder_ctl(opus_encoder_, OPUS_SET_BITRATE(bitrate)),
+ OPUS_OK);
}
+ virtual ~OpusImpl() {}
+
private:
- const AudioCodec codec_;
- const int frequency_;
- scoped_refptr<CastEnvironment> cast_environment_;
- uint32 last_timestamp_;
- base::TimeTicks recorded_time_;
- const AudioEncoder::FrameEncodedCallback* frame_encoded_callback_;
+ virtual void TransferSamplesIntoBuffer(const AudioBus* audio_bus,
+ int source_offset,
+ int buffer_fill_offset,
+ int num_samples) OVERRIDE {
+ // Opus requires channel-interleaved samples in a single array.
+ for (int ch = 0; ch < audio_bus->channels(); ++ch) {
+ const float* src = audio_bus->channel(ch) + source_offset;
+ const float* const src_end = src + num_samples;
+ float* dest = buffer_.get() + buffer_fill_offset * num_channels_ + ch;
+ for (; src < src_end; ++src, dest += num_channels_)
+ *dest = *src;
+ }
+ }
+
+ virtual bool EncodeFromFilledBuffer(std::string* out) OVERRIDE {
+ out->resize(kOpusMaxPayloadSize);
+ const opus_int32 result = opus_encode_float(
+ opus_encoder_, buffer_.get(), samples_per_10ms_,
+ reinterpret_cast<uint8*>(&out->at(0)), kOpusMaxPayloadSize);
+ if (result > 1) {
+ out->resize(result);
+ return true;
+ } else if (result < 0) {
+ LOG(ERROR) << "Error code from opus_encode_float(): " << result;
+ return false;
+ } else {
+ // Do nothing: The documentation says that a return value of zero or
+ // one byte means the packet does not need to be transmitted.
+ return false;
+ }
+ }
+
+ const scoped_ptr<uint8[]> encoder_memory_;
+ OpusEncoder* const opus_encoder_;
+ const scoped_ptr<float[]> buffer_;
+
+ // This is the recommended value, according to documentation in
+ // third_party/opus/src/include/opus.h, so that the Opus encoder does not
+ // degrade the audio due to memory constraints.
+ //
+ // Note: Whereas other RTP implementations do not, the cast library is
+ // perfectly capable of transporting larger than MTU-sized audio frames.
+ static const int kOpusMaxPayloadSize = 4000;
+
+ DISALLOW_COPY_AND_ASSIGN(OpusImpl);
};
-AudioEncoder::AudioEncoder(scoped_refptr<CastEnvironment> cast_environment,
- const AudioSenderConfig& audio_config)
- : cast_environment_(cast_environment),
- audio_encoder_(webrtc::AudioCodingModule::Create(0)),
- webrtc_encoder_callback_(
- new WebrtEncodedDataCallback(cast_environment, 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";
+class AudioEncoder::Pcm16Impl : public AudioEncoder::ImplBase {
+ public:
+ Pcm16Impl(CastEnvironment* cast_environment,
+ int num_channels, int sampling_rate,
+ const FrameEncodedCallback& callback)
+ : ImplBase(cast_environment, kPcm16, num_channels, sampling_rate,
+ callback),
+ buffer_(new int16[num_channels * samples_per_10ms_]) {}
+
+ virtual ~Pcm16Impl() {}
+
+ private:
+ virtual void TransferSamplesIntoBuffer(const AudioBus* audio_bus,
+ int source_offset,
+ int buffer_fill_offset,
+ int num_samples) OVERRIDE {
+ audio_bus->ToInterleavedPartial(
+ source_offset, num_samples, sizeof(int16),
+ buffer_.get() + buffer_fill_offset * num_channels_);
}
- if (audio_encoder_->RegisterTransportCallback(
- webrtc_encoder_callback_.get()) != 0) {
- DCHECK(false) << "Invalid webrtc return value";
+
+ virtual bool EncodeFromFilledBuffer(std::string* out) OVERRIDE {
+ // Output 16-bit PCM integers in big-endian byte order.
+ out->resize(num_channels_ * samples_per_10ms_ * sizeof(int16));
+ const int16* src = buffer_.get();
+ const int16* const src_end = src + num_channels_ * samples_per_10ms_;
+ uint16* dest = reinterpret_cast<uint16*>(&out->at(0));
+ for (; src < src_end; ++src, ++dest)
+ *dest = base::HostToNet16(*src);
+ return true;
}
- webrtc::CodecInst send_codec;
- send_codec.pltype = audio_config.rtp_payload_type;
- send_codec.plfreq = audio_config.frequency;
- send_codec.channels = audio_config.channels;
+
+ private:
+ const scoped_ptr<int16[]> buffer_;
+
+ DISALLOW_COPY_AND_ASSIGN(Pcm16Impl);
+};
+
+AudioEncoder::AudioEncoder(
+ const scoped_refptr<CastEnvironment>& cast_environment,
+ const AudioSenderConfig& audio_config,
+ const FrameEncodedCallback& frame_encoded_callback)
+ : cast_environment_(cast_environment) {
+ // Note: It doesn't matter which thread constructs AudioEncoder, just so long
+ // as all calls to InsertAudio() are by the same thread.
+ insert_thread_checker_.DetachFromThread();
switch (audio_config.codec) {
case kOpus:
- 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
+ impl_.reset(new OpusImpl(
+ cast_environment, audio_config.channels, audio_config.frequency,
+ audio_config.bitrate, frame_encoded_callback));
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;
+ impl_.reset(new Pcm16Impl(
+ cast_environment, audio_config.channels, audio_config.frequency,
+ frame_encoded_callback));
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";
+ NOTREACHED() << "Unsupported or unspecified codec for audio encoder";
+ break;
}
}
AudioEncoder::~AudioEncoder() {}
-// Called from main cast thread.
-void AudioEncoder::InsertRawAudioFrame(
- const PcmAudioFrame* audio_frame,
+void AudioEncoder::InsertAudio(
+ const AudioBus* audio_bus,
const base::TimeTicks& recorded_time,
- const FrameEncodedCallback& frame_encoded_callback,
- const base::Closure release_callback) {
+ const base::Closure& done_callback) {
+ DCHECK(insert_thread_checker_.CalledOnValidThread());
+ if (!impl_) {
+ NOTREACHED();
+ cast_environment_->PostTask(CastEnvironment::MAIN, FROM_HERE,
+ done_callback);
+ return;
+ }
cast_environment_->PostTask(CastEnvironment::AUDIO_ENCODER, FROM_HERE,
- base::Bind(&AudioEncoder::EncodeAudioFrameThread, this, audio_frame,
- recorded_time, frame_encoded_callback, release_callback));
+ base::Bind(&AudioEncoder::EncodeAudio, this, audio_bus, recorded_time,
+ done_callback));
}
-// Called from cast audio encoder thread.
-void AudioEncoder::EncodeAudioFrameThread(
- const PcmAudioFrame* audio_frame,
+void AudioEncoder::EncodeAudio(
+ const AudioBus* audio_bus,
const base::TimeTicks& recorded_time,
- const FrameEncodedCallback& frame_encoded_callback,
- const base::Closure release_callback) {
+ const base::Closure& done_callback) {
DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::AUDIO_ENCODER));
- size_t samples_per_10ms = audio_frame->frequency / 100;
- size_t 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 (size_t 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));
-
- // The webrtc API is int and we have a size_t; the cast should never be an
- // issue since the normal values are in the 480 range.
- DCHECK_GE(1000u, samples_per_10ms);
- webrtc_audio_frame.samples_per_channel_ =
- static_cast<int>(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_ += static_cast<uint32>(samples_per_10ms);
- }
- // We are done with the audio frame release it.
+ impl_->EncodeAudio(audio_bus, recorded_time, done_callback);
cast_environment_->PostTask(CastEnvironment::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 (size_t i = 0; i < number_of_10ms_blocks; ++i) {
- audio_encoder_->Process();
- }
+ base::Bind(LogAudioEncodedEvent, cast_environment_, recorded_time));
}
} // namespace cast
diff --git a/media/cast/audio_sender/audio_encoder.h b/media/cast/audio_sender/audio_encoder.h
index 8a4acc4f91..4a22d1983b 100644
--- a/media/cast/audio_sender/audio_encoder.h
+++ b/media/cast/audio_sender/audio_encoder.h
@@ -7,37 +7,37 @@
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h"
+#include "base/threading/thread_checker.h"
#include "media/cast/cast_config.h"
#include "media/cast/cast_environment.h"
-#include "media/cast/rtp_sender/rtp_sender.h"
-namespace webrtc {
-class AudioCodingModule;
+namespace base {
+class TimeTicks;
}
namespace media {
-namespace cast {
+class AudioBus;
+}
-class WebrtEncodedDataCallback;
+namespace media {
+namespace cast {
-// 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<CastEnvironment> cast_environment,
- const AudioSenderConfig& audio_config);
+ AudioEncoder(const scoped_refptr<CastEnvironment>& cast_environment,
+ const AudioSenderConfig& audio_config,
+ const FrameEncodedCallback& frame_encoded_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 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);
+ // The |audio_bus| must be valid until the |done_callback| is called.
+ // The callback is called from the main cast thread as soon as the encoder is
+ // done with |audio_bus|; it does not mean that the encoded data has been
+ // sent out.
+ void InsertAudio(const AudioBus* audio_bus,
+ const base::TimeTicks& recorded_time,
+ const base::Closure& done_callback);
protected:
virtual ~AudioEncoder();
@@ -45,16 +45,21 @@ class AudioEncoder : public base::RefCountedThreadSafe<AudioEncoder> {
private:
friend class base::RefCountedThreadSafe<AudioEncoder>;
- void EncodeAudioFrameThread(
- const PcmAudioFrame* audio_frame,
- const base::TimeTicks& recorded_time,
- const FrameEncodedCallback& frame_encoded_callback,
- const base::Closure release_callback);
+ class ImplBase;
+ class OpusImpl;
+ class Pcm16Impl;
+
+ // Invokes |impl_|'s encode method on the AUDIO_ENCODER thread while holding
+ // a ref-count on AudioEncoder.
+ void EncodeAudio(const AudioBus* audio_bus,
+ const base::TimeTicks& recorded_time,
+ const base::Closure& done_callback);
+
+ const scoped_refptr<CastEnvironment> cast_environment_;
+ scoped_ptr<ImplBase> impl_;
- scoped_refptr<CastEnvironment> cast_environment_;
- scoped_ptr<webrtc::AudioCodingModule> audio_encoder_;
- scoped_ptr<WebrtEncodedDataCallback> webrtc_encoder_callback_;
- uint32 timestamp_;
+ // Used to ensure only one thread invokes InsertAudio().
+ base::ThreadChecker insert_thread_checker_;
DISALLOW_COPY_AND_ASSIGN(AudioEncoder);
};
diff --git a/media/cast/audio_sender/audio_encoder_unittest.cc b/media/cast/audio_sender/audio_encoder_unittest.cc
index b33424a606..d721f71ef2 100644
--- a/media/cast/audio_sender/audio_encoder_unittest.cc
+++ b/media/cast/audio_sender/audio_encoder_unittest.cc
@@ -2,11 +2,18 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+#include <sstream>
+#include <string>
+
#include "base/bind.h"
+#include "base/bind_helpers.h"
#include "base/memory/scoped_ptr.h"
+#include "media/base/audio_bus.h"
+#include "media/base/media.h"
#include "media/cast/audio_sender/audio_encoder.h"
#include "media/cast/cast_config.h"
#include "media/cast/cast_environment.h"
+#include "media/cast/test/audio_utility.h"
#include "media/cast/test/fake_task_runner.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -15,17 +22,74 @@ namespace cast {
static const int64 kStartMillisecond = GG_INT64_C(12345678900000);
-static void RelaseFrame(const PcmAudioFrame* frame) {
- delete frame;
-}
+namespace {
-static void FrameEncoded(scoped_ptr<EncodedAudioFrame> encoded_frame,
- const base::TimeTicks& recorded_time) {
-}
+class TestEncodedAudioFrameReceiver {
+ public:
+ explicit TestEncodedAudioFrameReceiver(AudioCodec codec) :
+ codec_(codec), frames_received_(0) {}
+ virtual ~TestEncodedAudioFrameReceiver() {}
+
+ int frames_received() const {
+ return frames_received_;
+ }
+
+ void SetRecordedTimeLowerBound(const base::TimeTicks& t) {
+ lower_bound_ = t;
+ }
+
+ void SetRecordedTimeUpperBound(const base::TimeTicks& t) {
+ upper_bound_ = t;
+ }
-class AudioEncoderTest : public ::testing::Test {
- protected:
+ void FrameEncoded(scoped_ptr<EncodedAudioFrame> encoded_frame,
+ const base::TimeTicks& recorded_time) {
+ EXPECT_EQ(codec_, encoded_frame->codec);
+ EXPECT_EQ(static_cast<uint8>(frames_received_ & 0xff),
+ encoded_frame->frame_id);
+ EXPECT_LT(0, encoded_frame->samples);
+ EXPECT_TRUE(!encoded_frame->data.empty());
+
+ EXPECT_LE(lower_bound_, recorded_time);
+ lower_bound_ = recorded_time;
+ EXPECT_GT(upper_bound_, recorded_time);
+
+ ++frames_received_;
+ }
+
+ private:
+ const AudioCodec codec_;
+ int frames_received_;
+ base::TimeTicks lower_bound_;
+ base::TimeTicks upper_bound_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestEncodedAudioFrameReceiver);
+};
+
+struct TestScenario {
+ const int64* durations_in_ms;
+ size_t num_durations;
+
+ TestScenario(const int64* d, size_t n)
+ : durations_in_ms(d), num_durations(n) {}
+
+ std::string ToString() const {
+ std::ostringstream out;
+ for (size_t i = 0; i < num_durations; ++i) {
+ if (i > 0)
+ out << ", ";
+ out << durations_in_ms[i];
+ }
+ return out.str();
+ }
+};
+
+} // namespace
+
+class AudioEncoderTest : public ::testing::TestWithParam<TestScenario> {
+ public:
AudioEncoderTest() {
+ InitializeMediaLibraryForTesting();
testing_clock_.Advance(
base::TimeDelta::FromMilliseconds(kStartMillisecond));
}
@@ -33,38 +97,138 @@ class AudioEncoderTest : public ::testing::Test {
virtual void SetUp() {
task_runner_ = new test::FakeTaskRunner(&testing_clock_);
cast_environment_ = new CastEnvironment(&testing_clock_, task_runner_,
- task_runner_, task_runner_, task_runner_, task_runner_);
+ task_runner_, task_runner_, task_runner_, task_runner_,
+ GetDefaultCastLoggingConfig());
+ }
+
+ virtual ~AudioEncoderTest() {}
+
+ void RunTestForCodec(AudioCodec codec) {
+ const TestScenario& scenario = GetParam();
+ SCOPED_TRACE(::testing::Message()
+ << "Durations: " << scenario.ToString());
+
+ CreateObjectsForCodec(codec);
+
+ receiver_->SetRecordedTimeLowerBound(testing_clock_.NowTicks());
+ for (size_t i = 0; i < scenario.num_durations; ++i) {
+ const base::TimeDelta duration =
+ base::TimeDelta::FromMilliseconds(scenario.durations_in_ms[i]);
+ receiver_->SetRecordedTimeUpperBound(
+ testing_clock_.NowTicks() + duration);
+
+ const scoped_ptr<AudioBus> bus(
+ audio_bus_factory_->NextAudioBus(duration));
+
+ const int last_count = release_callback_count_;
+ audio_encoder_->InsertAudio(
+ bus.get(), testing_clock_.NowTicks(),
+ base::Bind(&AudioEncoderTest::IncrementReleaseCallbackCounter,
+ base::Unretained(this)));
+ task_runner_->RunTasks();
+ EXPECT_EQ(1, release_callback_count_ - last_count)
+ << "Release callback was not invoked once.";
+
+ testing_clock_.Advance(duration);
+ }
+
+ DVLOG(1) << "Received " << receiver_->frames_received()
+ << " frames for this test run: " << scenario.ToString();
+ }
+
+ private:
+ void CreateObjectsForCodec(AudioCodec codec) {
AudioSenderConfig audio_config;
- audio_config.codec = kOpus;
+ audio_config.codec = codec;
audio_config.use_external_encoder = false;
- audio_config.frequency = 48000;
+ audio_config.frequency = kDefaultAudioSamplingRate;
audio_config.channels = 2;
- audio_config.bitrate = 64000;
+ audio_config.bitrate = kDefaultAudioEncoderBitrate;
audio_config.rtp_payload_type = 127;
- audio_encoder_ = new AudioEncoder(cast_environment_, audio_config);
+ audio_bus_factory_.reset(new TestAudioBusFactory(
+ audio_config.channels, audio_config.frequency,
+ TestAudioBusFactory::kMiddleANoteFreq, 0.5f));
+
+ receiver_.reset(new TestEncodedAudioFrameReceiver(codec));
+
+ audio_encoder_ = new AudioEncoder(
+ cast_environment_, audio_config,
+ base::Bind(&TestEncodedAudioFrameReceiver::FrameEncoded,
+ base::Unretained(receiver_.get())));
+ release_callback_count_ = 0;
}
- virtual ~AudioEncoderTest() {}
+ void IncrementReleaseCallbackCounter() {
+ ++release_callback_count_;
+ }
base::SimpleTestTickClock testing_clock_;
scoped_refptr<test::FakeTaskRunner> task_runner_;
+ scoped_ptr<TestAudioBusFactory> audio_bus_factory_;
+ scoped_ptr<TestEncodedAudioFrameReceiver> receiver_;
scoped_refptr<AudioEncoder> audio_encoder_;
scoped_refptr<CastEnvironment> cast_environment_;
+ int release_callback_count_;
+
+ DISALLOW_COPY_AND_ASSIGN(AudioEncoderTest);
};
-TEST_F(AudioEncoderTest, Encode20ms) {
- 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));
- task_runner_->RunTasks();
+TEST_P(AudioEncoderTest, EncodeOpus) {
+ RunTestForCodec(kOpus);
}
+TEST_P(AudioEncoderTest, EncodePcm16) {
+ RunTestForCodec(kPcm16);
+}
+
+static const int64 kOneCall_3Millis[] = { 3 };
+static const int64 kOneCall_10Millis[] = { 10 };
+static const int64 kOneCall_13Millis[] = { 13 };
+static const int64 kOneCall_20Millis[] = { 20 };
+
+static const int64 kTwoCalls_3Millis[] = { 3, 3 };
+static const int64 kTwoCalls_10Millis[] = { 10, 10 };
+static const int64 kTwoCalls_Mixed1[] = { 3, 10 };
+static const int64 kTwoCalls_Mixed2[] = { 10, 3 };
+static const int64 kTwoCalls_Mixed3[] = { 3, 17 };
+static const int64 kTwoCalls_Mixed4[] = { 17, 3 };
+
+static const int64 kManyCalls_3Millis[] =
+ { 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 };
+static const int64 kManyCalls_10Millis[] =
+ { 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10 };
+static const int64 kManyCalls_Mixed1[] =
+ { 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10 };
+static const int64 kManyCalls_Mixed2[] =
+ { 10, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 3 };
+static const int64 kManyCalls_Mixed3[] =
+ { 3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8, 9, 7, 9, 3, 2, 3, 8, 4, 6, 2, 6, 4 };
+static const int64 kManyCalls_Mixed4[] =
+ { 31, 4, 15, 9, 26, 53, 5, 8, 9, 7, 9, 32, 38, 4, 62, 64, 3 };
+static const int64 kManyCalls_Mixed5[] =
+ { 3, 14, 15, 9, 26, 53, 58, 9, 7, 9, 3, 23, 8, 4, 6, 2, 6, 43 };
+
+INSTANTIATE_TEST_CASE_P(
+ AudioEncoderTestScenarios, AudioEncoderTest,
+ ::testing::Values(
+ TestScenario(kOneCall_3Millis, arraysize(kOneCall_3Millis)),
+ TestScenario(kOneCall_10Millis, arraysize(kOneCall_10Millis)),
+ TestScenario(kOneCall_13Millis, arraysize(kOneCall_13Millis)),
+ TestScenario(kOneCall_20Millis, arraysize(kOneCall_20Millis)),
+ TestScenario(kTwoCalls_3Millis, arraysize(kTwoCalls_3Millis)),
+ TestScenario(kTwoCalls_10Millis, arraysize(kTwoCalls_10Millis)),
+ TestScenario(kTwoCalls_Mixed1, arraysize(kTwoCalls_Mixed1)),
+ TestScenario(kTwoCalls_Mixed2, arraysize(kTwoCalls_Mixed2)),
+ TestScenario(kTwoCalls_Mixed3, arraysize(kTwoCalls_Mixed3)),
+ TestScenario(kTwoCalls_Mixed4, arraysize(kTwoCalls_Mixed4)),
+ TestScenario(kManyCalls_3Millis, arraysize(kManyCalls_3Millis)),
+ TestScenario(kManyCalls_10Millis, arraysize(kManyCalls_10Millis)),
+ TestScenario(kManyCalls_Mixed1, arraysize(kManyCalls_Mixed1)),
+ TestScenario(kManyCalls_Mixed2, arraysize(kManyCalls_Mixed2)),
+ TestScenario(kManyCalls_Mixed3, arraysize(kManyCalls_Mixed3)),
+ TestScenario(kManyCalls_Mixed4, arraysize(kManyCalls_Mixed4)),
+ TestScenario(kManyCalls_Mixed5, arraysize(kManyCalls_Mixed5))));
+
} // namespace cast
} // namespace media
diff --git a/media/cast/audio_sender/audio_sender.cc b/media/cast/audio_sender/audio_sender.cc
index 560ebd990c..00f4313e1e 100644
--- a/media/cast/audio_sender/audio_sender.cc
+++ b/media/cast/audio_sender/audio_sender.cc
@@ -7,7 +7,10 @@
#include "base/bind.h"
#include "base/logging.h"
#include "base/message_loop/message_loop.h"
+#include "crypto/encryptor.h"
+#include "crypto/symmetric_key.h"
#include "media/cast/audio_sender/audio_encoder.h"
+#include "media/cast/cast_environment.h"
#include "media/cast/rtcp/rtcp.h"
#include "media/cast/rtp_sender/rtp_sender.h"
@@ -22,28 +25,6 @@ class LocalRtcpAudioSenderFeedback : public RtcpSenderFeedback {
: audio_sender_(audio_sender) {
}
- virtual void OnReceivedReportBlock(
- const RtcpReportBlock& report_block) OVERRIDE {
- }
-
- 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()) {
@@ -75,72 +56,137 @@ class LocalRtpSenderStatistics : public RtpSenderStatistics {
AudioSender::AudioSender(scoped_refptr<CastEnvironment> cast_environment,
const AudioSenderConfig& audio_config,
PacedPacketSender* const paced_packet_sender)
- : incoming_feedback_ssrc_(audio_config.incoming_feedback_ssrc),
- cast_environment_(cast_environment),
- rtp_sender_(cast_environment->Clock(), &audio_config, NULL,
+ : cast_environment_(cast_environment),
+ rtp_sender_(cast_environment, &audio_config, NULL,
paced_packet_sender),
rtcp_feedback_(new LocalRtcpAudioSenderFeedback(this)),
rtp_audio_sender_statistics_(
new LocalRtpSenderStatistics(&rtp_sender_)),
- rtcp_(cast_environment->Clock(),
+ rtcp_(cast_environment,
rtcp_feedback_.get(),
paced_packet_sender,
rtp_audio_sender_statistics_.get(),
NULL,
audio_config.rtcp_mode,
base::TimeDelta::FromMilliseconds(audio_config.rtcp_interval),
- true,
audio_config.sender_ssrc,
+ audio_config.incoming_feedback_ssrc,
audio_config.rtcp_c_name),
+ initialized_(false),
weak_factory_(this) {
- rtcp_.SetRemoteSSRC(audio_config.incoming_feedback_ssrc);
-
+ if (audio_config.aes_iv_mask.size() == kAesKeySize &&
+ audio_config.aes_key.size() == kAesKeySize) {
+ iv_mask_ = audio_config.aes_iv_mask;
+ crypto::SymmetricKey* key = crypto::SymmetricKey::Import(
+ crypto::SymmetricKey::AES, audio_config.aes_key);
+ encryptor_.reset(new crypto::Encryptor());
+ encryptor_->Init(key, crypto::Encryptor::CTR, std::string());
+ } else if (audio_config.aes_iv_mask.size() != 0 ||
+ audio_config.aes_key.size() != 0) {
+ DCHECK(false) << "Invalid crypto configuration";
+ }
if (!audio_config.use_external_encoder) {
- audio_encoder_ = new AudioEncoder(cast_environment, audio_config);
+ audio_encoder_ = new AudioEncoder(
+ cast_environment, audio_config,
+ base::Bind(&AudioSender::SendEncodedAudioFrame,
+ weak_factory_.GetWeakPtr()));
}
- 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";
+void AudioSender::InitializeTimers() {
+ DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN));
+ if (!initialized_) {
+ initialized_ = true;
+ ScheduleNextRtcpReport();
+ }
+}
- audio_encoder_->InsertRawAudioFrame(audio_frame, recorded_time,
- base::Bind(&AudioSender::SendEncodedAudioFrame,
- weak_factory_.GetWeakPtr()),
- callback);
+void AudioSender::InsertAudio(const AudioBus* audio_bus,
+ const base::TimeTicks& recorded_time,
+ const base::Closure& done_callback) {
+ DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN));
+ DCHECK(audio_encoder_.get()) << "Invalid internal state";
+ // TODO(mikhal): Resolve calculation of the audio rtp_timestamp for logging.
+ // This is a tmp solution to allow the code to build.
+ cast_environment_->Logging()->InsertFrameEvent(kAudioFrameReceived,
+ GetVideoRtpTimestamp(recorded_time), kFrameIdUnknown);
+ audio_encoder_->InsertAudio(audio_bus, recorded_time, done_callback);
}
void AudioSender::InsertCodedAudioFrame(const EncodedAudioFrame* audio_frame,
const base::TimeTicks& recorded_time,
const base::Closure callback) {
+ DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN));
DCHECK(audio_encoder_.get() == NULL) << "Invalid internal state";
- rtp_sender_.IncomingEncodedAudioFrame(audio_frame, recorded_time);
+
+ cast_environment_->Logging()->InsertFrameEvent(kAudioFrameReceived,
+ GetVideoRtpTimestamp(recorded_time), kFrameIdUnknown);
+
+ if (encryptor_) {
+ EncodedAudioFrame encrypted_frame;
+ if (!EncryptAudioFrame(*audio_frame, &encrypted_frame)) {
+ // Logging already done.
+ return;
+ }
+ rtp_sender_.IncomingEncodedAudioFrame(&encrypted_frame, recorded_time);
+ } else {
+ 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);
+ DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN));
+ InitializeTimers();
+ if (encryptor_) {
+ EncodedAudioFrame encrypted_frame;
+ if (!EncryptAudioFrame(*audio_frame.get(), &encrypted_frame)) {
+ // Logging already done.
+ return;
+ }
+ rtp_sender_.IncomingEncodedAudioFrame(&encrypted_frame, recorded_time);
+ } else {
+ rtp_sender_.IncomingEncodedAudioFrame(audio_frame.get(), recorded_time);
+ }
+}
+
+bool AudioSender::EncryptAudioFrame(const EncodedAudioFrame& audio_frame,
+ EncodedAudioFrame* encrypted_frame) {
+ DCHECK(encryptor_) << "Invalid state";
+
+ if (!encryptor_->SetCounter(GetAesNonce(audio_frame.frame_id, iv_mask_))) {
+ NOTREACHED() << "Failed to set counter";
+ return false;
+ }
+ if (!encryptor_->Encrypt(audio_frame.data, &encrypted_frame->data)) {
+ NOTREACHED() << "Encrypt error";
+ return false;
+ }
+ encrypted_frame->codec = audio_frame.codec;
+ encrypted_frame->frame_id = audio_frame.frame_id;
+ encrypted_frame->samples = audio_frame.samples;
+ return true;
}
void AudioSender::ResendPackets(
const MissingFramesAndPacketsMap& missing_frames_and_packets) {
+ DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN));
rtp_sender_.ResendPackets(missing_frames_and_packets);
}
void AudioSender::IncomingRtcpPacket(const uint8* packet, size_t length,
const base::Closure callback) {
+ DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN));
rtcp_.IncomingRtcpPacket(packet, length);
cast_environment_->PostTask(CastEnvironment::MAIN, FROM_HERE, callback);
}
void AudioSender::ScheduleNextRtcpReport() {
+ DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN));
base::TimeDelta time_to_next =
rtcp_.TimeToSendNextRtcpReport() - cast_environment_->Clock()->NowTicks();
@@ -153,7 +199,8 @@ void AudioSender::ScheduleNextRtcpReport() {
}
void AudioSender::SendRtcpReport() {
- rtcp_.SendRtcpReport(incoming_feedback_ssrc_);
+ DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN));
+ rtcp_.SendRtcpFromRtpSender(NULL); // TODO(pwestin): add logging.
ScheduleNextRtcpReport();
}
diff --git a/media/cast/audio_sender/audio_sender.gypi b/media/cast/audio_sender/audio_sender.gypi
index 3e2a56345b..32a316ac68 100644
--- a/media/cast/audio_sender/audio_sender.gypi
+++ b/media/cast/audio_sender/audio_sender.gypi
@@ -10,7 +10,6 @@
'include_dirs': [
'<(DEPTH)/',
'<(DEPTH)/third_party/',
- '<(DEPTH)/third_party/webrtc',
],
'sources': [
'audio_encoder.h',
@@ -19,9 +18,12 @@
'audio_sender.cc',
], # source
'dependencies': [
+ '<(DEPTH)/crypto/crypto.gyp:crypto',
+ '<(DEPTH)/media/media.gyp:media',
+ '<(DEPTH)/media/media.gyp:shared_memory_support',
'<(DEPTH)/media/cast/rtcp/rtcp.gyp:cast_rtcp',
'<(DEPTH)/media/cast/rtp_sender/rtp_sender.gyp:*',
- '<(DEPTH)/third_party/webrtc/webrtc.gyp:webrtc',
+ '<(DEPTH)/third_party/opus/opus.gyp:opus',
],
},
],
diff --git a/media/cast/audio_sender/audio_sender.h b/media/cast/audio_sender/audio_sender.h
index ca1fffb37f..e4a078b4d3 100644
--- a/media/cast/audio_sender/audio_sender.h
+++ b/media/cast/audio_sender/audio_sender.h
@@ -17,6 +17,14 @@
#include "media/cast/rtcp/rtcp.h"
#include "media/cast/rtp_sender/rtp_sender.h"
+namespace crypto {
+ class Encryptor;
+}
+
+namespace media {
+class AudioBus;
+}
+
namespace media {
namespace cast {
@@ -36,13 +44,13 @@ class AudioSender : public base::NonThreadSafe,
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_bus| must be valid until the |done_callback| is called.
+ // The callback is called from the main cast thread as soon as the encoder is
+ // done with |audio_bus|; it does not mean that the encoded data has been
+ // sent out.
+ void InsertAudio(const AudioBus* audio_bus,
+ const base::TimeTicks& recorded_time,
+ const base::Closure& done_callback);
// The audio_frame must be valid until the closure callback is called.
// The closure callback is called from the main cast thread as soon as
@@ -66,18 +74,27 @@ class AudioSender : public base::NonThreadSafe,
void ResendPackets(
const MissingFramesAndPacketsMap& missing_frames_and_packets);
+ // Caller must allocate the destination |encrypted_frame|. The data member
+ // will be resized to hold the encrypted size.
+ bool EncryptAudioFrame(const EncodedAudioFrame& audio_frame,
+ EncodedAudioFrame* encrypted_frame);
+
void ScheduleNextRtcpReport();
void SendRtcpReport();
+ void InitializeTimers();
+
base::WeakPtrFactory<AudioSender> weak_factory_;
- const uint32 incoming_feedback_ssrc_;
scoped_refptr<CastEnvironment> cast_environment_;
scoped_refptr<AudioEncoder> audio_encoder_;
RtpSender rtp_sender_;
scoped_ptr<LocalRtpSenderStatistics> rtp_audio_sender_statistics_;
scoped_ptr<LocalRtcpAudioSenderFeedback> rtcp_feedback_;
Rtcp rtcp_;
+ bool initialized_;
+ scoped_ptr<crypto::Encryptor> encryptor_;
+ std::string iv_mask_;
DISALLOW_COPY_AND_ASSIGN(AudioSender);
};
@@ -86,4 +103,3 @@ class AudioSender : public base::NonThreadSafe,
} // 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
index 9ab6bb549b..5b632bf025 100644
--- a/media/cast/audio_sender/audio_sender_unittest.cc
+++ b/media/cast/audio_sender/audio_sender_unittest.cc
@@ -3,12 +3,15 @@
// found in the LICENSE file.
#include "base/bind.h"
+#include "base/bind_helpers.h"
#include "base/memory/scoped_ptr.h"
#include "base/test/simple_test_tick_clock.h"
+#include "media/base/media.h"
#include "media/cast/audio_sender/audio_sender.h"
#include "media/cast/cast_config.h"
#include "media/cast/cast_environment.h"
#include "media/cast/pacing/mock_paced_packet_sender.h"
+#include "media/cast/test/audio_utility.h"
#include "media/cast/test/fake_task_runner.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -18,14 +21,12 @@ namespace cast {
static const int64 kStartMillisecond = GG_INT64_C(12345678900000);
using testing::_;
-
-static void RelaseFrame(const PcmAudioFrame* frame) {
- delete frame;
-}
+using testing::AtLeast;
class AudioSenderTest : public ::testing::Test {
protected:
AudioSenderTest() {
+ InitializeMediaLibraryForTesting();
testing_clock_.Advance(
base::TimeDelta::FromMilliseconds(kStartMillisecond));
}
@@ -33,17 +34,17 @@ class AudioSenderTest : public ::testing::Test {
virtual void SetUp() {
task_runner_ = new test::FakeTaskRunner(&testing_clock_);
cast_environment_ = new CastEnvironment(&testing_clock_, task_runner_,
- task_runner_, task_runner_, task_runner_, task_runner_);
- 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;
+ task_runner_, task_runner_, task_runner_, task_runner_,
+ GetDefaultCastLoggingConfig());
+ audio_config_.codec = kOpus;
+ audio_config_.use_external_encoder = false;
+ audio_config_.frequency = kDefaultAudioSamplingRate;
+ audio_config_.channels = 2;
+ audio_config_.bitrate = kDefaultAudioEncoderBitrate;
+ audio_config_.rtp_payload_type = 127;
audio_sender_.reset(
- new AudioSender(cast_environment_, audio_config, &mock_transport_));
+ new AudioSender(cast_environment_, audio_config_, &mock_transport_));
}
virtual ~AudioSenderTest() {}
@@ -53,26 +54,43 @@ class AudioSenderTest : public ::testing::Test {
scoped_refptr<test::FakeTaskRunner> task_runner_;
scoped_ptr<AudioSender> audio_sender_;
scoped_refptr<CastEnvironment> cast_environment_;
+ AudioSenderConfig audio_config_;
};
TEST_F(AudioSenderTest, Encode20ms) {
- EXPECT_CALL(mock_transport_, SendPackets(_)).Times(1);
-
- 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));
-
+ EXPECT_CALL(mock_transport_, SendPackets(_)).Times(AtLeast(1));
+
+ const base::TimeDelta kDuration = base::TimeDelta::FromMilliseconds(20);
+ scoped_ptr<AudioBus> bus(TestAudioBusFactory(
+ audio_config_.channels, audio_config_.frequency,
+ TestAudioBusFactory::kMiddleANoteFreq, 0.5f).NextAudioBus(kDuration));
+
+ base::TimeTicks recorded_time = base::TimeTicks::Now();
+ audio_sender_->InsertAudio(
+ bus.get(), recorded_time,
+ base::Bind(base::IgnoreResult(&scoped_ptr<AudioBus>::release),
+ base::Unretained(&bus)));
task_runner_->RunTasks();
+
+ EXPECT_TRUE(!bus) << "AudioBus wasn't released after use.";
}
TEST_F(AudioSenderTest, RtcpTimer) {
+ EXPECT_CALL(mock_transport_, SendPackets(_)).Times(AtLeast(1));
EXPECT_CALL(mock_transport_, SendRtcpPacket(_)).Times(1);
+ const base::TimeDelta kDuration = base::TimeDelta::FromMilliseconds(20);
+ scoped_ptr<AudioBus> bus(TestAudioBusFactory(
+ audio_config_.channels, audio_config_.frequency,
+ TestAudioBusFactory::kMiddleANoteFreq, 0.5f).NextAudioBus(kDuration));
+
+ base::TimeTicks recorded_time = base::TimeTicks::Now();
+ audio_sender_->InsertAudio(
+ bus.get(), recorded_time,
+ base::Bind(base::IgnoreResult(&scoped_ptr<AudioBus>::release),
+ base::Unretained(&bus)));
+ task_runner_->RunTasks();
+
// Make sure that we send at least one RTCP packet.
base::TimeDelta max_rtcp_timeout =
base::TimeDelta::FromMilliseconds(1 + kDefaultRtcpIntervalMs * 3 / 2);