diff options
author | Torne (Richard Coles) <torne@google.com> | 2013-11-28 11:55:43 +0000 |
---|---|---|
committer | Torne (Richard Coles) <torne@google.com> | 2013-11-28 11:55:43 +0000 |
commit | f2477e01787aa58f445919b809d89e252beef54f (patch) | |
tree | 2db962b4af39f0db3a5f83b314373d0530c484b8 /media/cast/audio_sender | |
parent | 7daea1dd5ff7e419322de831b642d81af3247912 (diff) | |
download | chromium_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.cc | 367 | ||||
-rw-r--r-- | media/cast/audio_sender/audio_encoder.h | 57 | ||||
-rw-r--r-- | media/cast/audio_sender/audio_encoder_unittest.cc | 214 | ||||
-rw-r--r-- | media/cast/audio_sender/audio_sender.cc | 133 | ||||
-rw-r--r-- | media/cast/audio_sender/audio_sender.gypi | 6 | ||||
-rw-r--r-- | media/cast/audio_sender/audio_sender.h | 34 | ||||
-rw-r--r-- | media/cast/audio_sender/audio_sender_unittest.cc | 66 |
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); |