Index: trunk/src/media/filters/audio_renderer_impl_unittest.cc |
=================================================================== |
--- trunk/src/media/filters/audio_renderer_impl_unittest.cc (revision 282435) |
+++ trunk/src/media/filters/audio_renderer_impl_unittest.cc (working copy) |
@@ -4,11 +4,17 @@ |
#include "base/bind.h" |
#include "base/callback_helpers.h" |
+#include "base/gtest_prod_util.h" |
+#include "base/memory/scoped_vector.h" |
+#include "base/message_loop/message_loop.h" |
#include "base/run_loop.h" |
+#include "base/stl_util.h" |
#include "base/strings/stringprintf.h" |
+#include "media/base/audio_buffer.h" |
#include "media/base/audio_buffer_converter.h" |
#include "media/base/audio_hardware_config.h" |
#include "media/base/audio_splicer.h" |
+#include "media/base/audio_timestamp_helper.h" |
#include "media/base/fake_audio_renderer_sink.h" |
#include "media/base/gmock_callback_support.h" |
#include "media/base/mock_filters.h" |
@@ -16,8 +22,12 @@ |
#include "media/filters/audio_renderer_impl.h" |
#include "testing/gtest/include/gtest/gtest.h" |
+using ::base::Time; |
+using ::base::TimeTicks; |
using ::base::TimeDelta; |
using ::testing::_; |
+using ::testing::AnyNumber; |
+using ::testing::Invoke; |
using ::testing::Return; |
using ::testing::SaveArg; |
@@ -33,6 +43,11 @@ |
// Use a different output sample rate so the AudioBufferConverter is invoked. |
static int kOutputSamplesPerSecond = 48000; |
+// Constants for distinguishing between muted audio and playing audio when using |
+// ConsumeBufferedData(). Must match the type needed by kSampleFormat. |
+static float kMutedAudio = 0.0f; |
+static float kPlayingAudio = 0.5f; |
+ |
static const int kDataSize = 1024; |
ACTION_P(EnterPendingDecoderInitStateAction, test) { |
@@ -47,8 +62,7 @@ |
needs_stop_(true), |
demuxer_stream_(DemuxerStream::AUDIO), |
decoder_(new MockAudioDecoder()), |
- last_time_update_(kNoTimestamp()), |
- ended_(false) { |
+ last_time_update_(kNoTimestamp()) { |
AudioDecoderConfig audio_config(kCodec, |
kSampleFormat, |
kChannelLayout, |
@@ -84,6 +98,10 @@ |
decoders.Pass(), |
SetDecryptorReadyCB(), |
&hardware_config_)); |
+ |
+ // Stub out time. |
+ renderer_->set_now_cb_for_testing(base::Bind( |
+ &AudioRendererImplTest::GetTime, base::Unretained(this))); |
} |
virtual ~AudioRendererImplTest() { |
@@ -120,8 +138,7 @@ |
base::Unretained(this)), |
base::Bind(&AudioRendererImplTest::OnBufferingStateChange, |
base::Unretained(this)), |
- base::Bind(&AudioRendererImplTest::OnEnded, |
- base::Unretained(this)), |
+ ended_event_.GetClosure(), |
base::Bind(&AudioRendererImplTest::OnError, |
base::Unretained(this))); |
} |
@@ -224,6 +241,11 @@ |
renderer_->StopRendering(); |
} |
+ void WaitForEnded() { |
+ SCOPED_TRACE("WaitForEnded()"); |
+ ended_event_.RunAndWait(); |
+ } |
+ |
bool IsReadPending() const { |
return !decode_cb_.is_null(); |
} |
@@ -253,7 +275,7 @@ |
kChannelLayout, |
kChannelCount, |
kSamplesPerSecond, |
- 1.0f, |
+ kPlayingAudio, |
0.0f, |
size, |
next_timestamp_->GetTimestamp()); |
@@ -291,13 +313,46 @@ |
} |
// Attempts to consume |requested_frames| frames from |renderer_|'s internal |
- // buffer, returning the actual number of frames consumed. |
- int ConsumeBufferedData(int requested_frames) { |
+ // buffer, returning true if all |requested_frames| frames were consumed, |
+ // false if less than |requested_frames| frames were consumed. |
+ // |
+ // |muted| is optional and if passed will get set if the value of |
+ // the consumed data is muted audio. |
+ bool ConsumeBufferedData(int requested_frames, bool* muted) { |
scoped_ptr<AudioBus> bus = |
AudioBus::Create(kChannels, std::max(requested_frames, 1)); |
+ int frames_read; |
+ if (!sink_->Render(bus.get(), 0, &frames_read)) { |
+ if (muted) |
+ *muted = true; |
+ return false; |
+ } |
+ |
+ if (muted) |
+ *muted = frames_read < 1 || bus->channel(0)[0] == kMutedAudio; |
+ return frames_read == requested_frames; |
+ } |
+ |
+ // Attempts to consume all data available from the renderer. Returns the |
+ // number of frames read. Since time is frozen, the audio delay will increase |
+ // as frames come in. |
+ int ConsumeAllBufferedData() { |
int frames_read = 0; |
- EXPECT_TRUE(sink_->Render(bus.get(), 0, &frames_read)); |
- return frames_read; |
+ int total_frames_read = 0; |
+ |
+ scoped_ptr<AudioBus> bus = AudioBus::Create(kChannels, 1024); |
+ |
+ do { |
+ TimeDelta audio_delay = TimeDelta::FromMicroseconds( |
+ total_frames_read * Time::kMicrosecondsPerSecond / |
+ static_cast<float>(hardware_config_.GetOutputConfig().sample_rate())); |
+ |
+ frames_read = renderer_->Render( |
+ bus.get(), audio_delay.InMilliseconds()); |
+ total_frames_read += frames_read; |
+ } while (frames_read > 0); |
+ |
+ return total_frames_read; |
} |
int frames_buffered() { |
@@ -317,6 +372,55 @@ |
return buffer_capacity() - frames_buffered(); |
} |
+ TimeDelta CalculatePlayTime(int frames_filled) { |
+ return TimeDelta::FromMicroseconds( |
+ frames_filled * Time::kMicrosecondsPerSecond / |
+ renderer_->audio_parameters_.sample_rate()); |
+ } |
+ |
+ void EndOfStreamTest(float playback_rate) { |
+ Initialize(); |
+ Preroll(); |
+ StartRendering(); |
+ renderer_->SetPlaybackRate(playback_rate); |
+ |
+ // Drain internal buffer, we should have a pending read. |
+ EXPECT_CALL(*this, OnBufferingStateChange(BUFFERING_HAVE_NOTHING)); |
+ int total_frames = frames_buffered(); |
+ int frames_filled = ConsumeAllBufferedData(); |
+ WaitForPendingRead(); |
+ |
+ // Due to how the cross-fade algorithm works we won't get an exact match |
+ // between the ideal and expected number of frames consumed. In the faster |
+ // than normal playback case, more frames are created than should exist and |
+ // vice versa in the slower than normal playback case. |
+ const float kEpsilon = 0.20 * (total_frames / playback_rate); |
+ EXPECT_NEAR(frames_filled, total_frames / playback_rate, kEpsilon); |
+ |
+ // Figure out how long until the ended event should fire. |
+ TimeDelta audio_play_time = CalculatePlayTime(frames_filled); |
+ DVLOG(1) << "audio_play_time = " << audio_play_time.InSecondsF(); |
+ |
+ // Fulfill the read with an end-of-stream packet. We shouldn't report ended |
+ // nor have a read until we drain the internal buffer. |
+ EXPECT_CALL(*this, OnBufferingStateChange(BUFFERING_HAVE_ENOUGH)); |
+ DeliverEndOfStream(); |
+ |
+ // Advance time half way without an ended expectation. |
+ AdvanceTime(audio_play_time / 2); |
+ ConsumeBufferedData(frames_buffered(), NULL); |
+ |
+ // Advance time by other half and expect the ended event. |
+ AdvanceTime(audio_play_time / 2); |
+ ConsumeBufferedData(frames_buffered(), NULL); |
+ WaitForEnded(); |
+ } |
+ |
+ void AdvanceTime(TimeDelta time) { |
+ base::AutoLock auto_lock(lock_); |
+ time_ += time; |
+ } |
+ |
void force_config_change() { |
renderer_->OnConfigChange(); |
} |
@@ -333,8 +437,6 @@ |
return last_time_update_; |
} |
- bool ended() const { return ended_; } |
- |
// Fixture members. |
base::MessageLoop message_loop_; |
scoped_ptr<AudioRendererImpl> renderer_; |
@@ -346,6 +448,11 @@ |
bool needs_stop_; |
private: |
+ TimeTicks GetTime() { |
+ base::AutoLock auto_lock(lock_); |
+ return time_; |
+ } |
+ |
void DecodeDecoder(const scoped_refptr<DecoderBuffer>& buffer, |
const AudioDecoder::DecodeCB& decode_cb) { |
// We shouldn't ever call Read() after Stop(): |
@@ -391,27 +498,27 @@ |
message_loop_.RunUntilIdle(); |
} |
- void OnEnded() { |
- CHECK(!ended_); |
- ended_ = true; |
- } |
- |
MockDemuxerStream demuxer_stream_; |
MockAudioDecoder* decoder_; |
+ // Used for stubbing out time in the audio callback thread. |
+ base::Lock lock_; |
+ TimeTicks time_; |
+ |
// Used for satisfying reads. |
AudioDecoder::OutputCB output_cb_; |
AudioDecoder::DecodeCB decode_cb_; |
base::Closure reset_cb_; |
scoped_ptr<AudioTimestampHelper> next_timestamp_; |
+ WaitableMessageLoopEvent ended_event_; |
+ |
// Run during DecodeDecoder() to unblock WaitForPendingRead(). |
base::Closure wait_for_pending_decode_cb_; |
base::Closure stop_decoder_cb_; |
PipelineStatusCB init_decoder_cb_; |
base::TimeDelta last_time_update_; |
- bool ended_; |
DISALLOW_COPY_AND_ASSIGN(AudioRendererImplTest); |
}; |
@@ -436,35 +543,20 @@ |
StartRendering(); |
// Drain internal buffer, we should have a pending read. |
- EXPECT_EQ(frames_buffered(), ConsumeBufferedData(frames_buffered())); |
+ EXPECT_TRUE(ConsumeBufferedData(frames_buffered(), NULL)); |
WaitForPendingRead(); |
} |
TEST_F(AudioRendererImplTest, EndOfStream) { |
- Initialize(); |
- Preroll(); |
- StartRendering(); |
+ EndOfStreamTest(1.0); |
+} |
- // Drain internal buffer, we should have a pending read. |
- EXPECT_EQ(frames_buffered(), ConsumeBufferedData(frames_buffered())); |
- WaitForPendingRead(); |
+TEST_F(AudioRendererImplTest, EndOfStream_FasterPlaybackSpeed) { |
+ EndOfStreamTest(2.0); |
+} |
- // Forcefully trigger underflow. |
- EXPECT_EQ(0, ConsumeBufferedData(1)); |
- EXPECT_CALL(*this, OnBufferingStateChange(BUFFERING_HAVE_NOTHING)); |
- |
- // Fulfill the read with an end-of-stream buffer. Doing so should change our |
- // buffering state so playback resumes. |
- EXPECT_CALL(*this, OnBufferingStateChange(BUFFERING_HAVE_ENOUGH)); |
- DeliverEndOfStream(); |
- |
- // Consume all remaining data. We shouldn't have signal ended yet. |
- EXPECT_EQ(frames_buffered(), ConsumeBufferedData(frames_buffered())); |
- EXPECT_FALSE(ended()); |
- |
- // Ended should trigger on next render call. |
- EXPECT_EQ(0, ConsumeBufferedData(1)); |
- EXPECT_TRUE(ended()); |
+TEST_F(AudioRendererImplTest, EndOfStream_SlowerPlaybackSpeed) { |
+ EndOfStreamTest(0.5); |
} |
TEST_F(AudioRendererImplTest, Underflow) { |
@@ -473,24 +565,27 @@ |
StartRendering(); |
// Drain internal buffer, we should have a pending read. |
- EXPECT_EQ(frames_buffered(), ConsumeBufferedData(frames_buffered())); |
+ EXPECT_TRUE(ConsumeBufferedData(frames_buffered(), NULL)); |
WaitForPendingRead(); |
// Verify the next FillBuffer() call triggers a buffering state change |
// update. |
EXPECT_CALL(*this, OnBufferingStateChange(BUFFERING_HAVE_NOTHING)); |
- EXPECT_EQ(0, ConsumeBufferedData(kDataSize)); |
+ EXPECT_FALSE(ConsumeBufferedData(kDataSize, NULL)); |
// Verify we're still not getting audio data. |
+ bool muted = false; |
EXPECT_EQ(0, frames_buffered()); |
- EXPECT_EQ(0, ConsumeBufferedData(kDataSize)); |
+ EXPECT_FALSE(ConsumeBufferedData(kDataSize, &muted)); |
+ EXPECT_TRUE(muted); |
// Deliver enough data to have enough for buffering. |
EXPECT_CALL(*this, OnBufferingStateChange(BUFFERING_HAVE_ENOUGH)); |
DeliverRemainingAudio(); |
// Verify we're getting audio data. |
- EXPECT_EQ(kDataSize, ConsumeBufferedData(kDataSize)); |
+ EXPECT_TRUE(ConsumeBufferedData(kDataSize, &muted)); |
+ EXPECT_FALSE(muted); |
} |
TEST_F(AudioRendererImplTest, Underflow_CapacityResetsAfterFlush) { |
@@ -499,14 +594,14 @@ |
StartRendering(); |
// Drain internal buffer, we should have a pending read. |
- EXPECT_EQ(frames_buffered(), ConsumeBufferedData(frames_buffered())); |
+ EXPECT_TRUE(ConsumeBufferedData(frames_buffered(), NULL)); |
WaitForPendingRead(); |
// Verify the next FillBuffer() call triggers the underflow callback |
// since the decoder hasn't delivered any data after it was drained. |
int initial_capacity = buffer_capacity(); |
EXPECT_CALL(*this, OnBufferingStateChange(BUFFERING_HAVE_NOTHING)); |
- EXPECT_EQ(0, ConsumeBufferedData(kDataSize)); |
+ EXPECT_FALSE(ConsumeBufferedData(kDataSize, NULL)); |
// Verify that the buffer capacity increased as a result of underflowing. |
EXPECT_GT(buffer_capacity(), initial_capacity); |
@@ -522,10 +617,10 @@ |
StartRendering(); |
// Force underflow. |
- EXPECT_EQ(frames_buffered(), ConsumeBufferedData(frames_buffered())); |
+ EXPECT_TRUE(ConsumeBufferedData(frames_buffered(), NULL)); |
WaitForPendingRead(); |
EXPECT_CALL(*this, OnBufferingStateChange(BUFFERING_HAVE_NOTHING)); |
- EXPECT_EQ(0, ConsumeBufferedData(kDataSize)); |
+ EXPECT_FALSE(ConsumeBufferedData(kDataSize, NULL)); |
WaitForPendingRead(); |
StopRendering(); |
@@ -540,7 +635,7 @@ |
StartRendering(); |
// Partially drain internal buffer so we get a pending read. |
- EXPECT_EQ(frames_buffered() / 2, ConsumeBufferedData(frames_buffered() / 2)); |
+ EXPECT_TRUE(ConsumeBufferedData(frames_buffered() / 2, NULL)); |
WaitForPendingRead(); |
StopRendering(); |
@@ -562,7 +657,7 @@ |
StartRendering(); |
// Partially drain internal buffer so we get a pending read. |
- EXPECT_EQ(frames_buffered() / 2, ConsumeBufferedData(frames_buffered() / 2)); |
+ EXPECT_TRUE(ConsumeBufferedData(frames_buffered() / 2, NULL)); |
WaitForPendingRead(); |
StopRendering(); |
@@ -587,7 +682,7 @@ |
StartRendering(); |
// Partially drain internal buffer so we get a pending read. |
- EXPECT_EQ(frames_buffered() / 2, ConsumeBufferedData(frames_buffered() / 2)); |
+ EXPECT_TRUE(ConsumeBufferedData(frames_buffered() / 2, NULL)); |
WaitForPendingRead(); |
StopRendering(); |
@@ -621,7 +716,7 @@ |
StartRendering(); |
// Drain internal buffer, we should have a pending read. |
- EXPECT_EQ(frames_buffered(), ConsumeBufferedData(frames_buffered())); |
+ EXPECT_TRUE(ConsumeBufferedData(frames_buffered(), NULL)); |
WaitForPendingRead(); |
// Deliver a little bit of data. Use an odd data size to ensure there is data |
@@ -647,7 +742,7 @@ |
// Preroll() should be buffered some data, consume half of it now. |
int frames_to_consume = frames_buffered() / 2; |
- EXPECT_EQ(frames_to_consume, ConsumeBufferedData(frames_to_consume)); |
+ EXPECT_TRUE(ConsumeBufferedData(frames_to_consume, NULL)); |
WaitForPendingRead(); |
base::RunLoop().RunUntilIdle(); |
@@ -660,7 +755,7 @@ |
// The next time update should match the remaining frames_buffered(), but only |
// after running the message loop. |
frames_to_consume = frames_buffered(); |
- EXPECT_EQ(frames_to_consume, ConsumeBufferedData(frames_to_consume)); |
+ EXPECT_TRUE(ConsumeBufferedData(frames_to_consume, NULL)); |
EXPECT_EQ(timestamp_helper.GetTimestamp(), last_time_update()); |
base::RunLoop().RunUntilIdle(); |
@@ -680,9 +775,8 @@ |
StartRendering(); |
// Read a single frame. We shouldn't be able to satisfy it. |
- EXPECT_FALSE(ended()); |
- EXPECT_EQ(0, ConsumeBufferedData(1)); |
- EXPECT_TRUE(ended()); |
+ EXPECT_FALSE(ConsumeBufferedData(1, NULL)); |
+ WaitForEnded(); |
} |
TEST_F(AudioRendererImplTest, OnRenderErrorCausesDecodeError) { |