// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/memory/scoped_ptr.h"
#include "media/base/audio_buffer.h"
#include "media/base/audio_bus.h"
#include "media/base/audio_discard_helper.h"
#include "media/base/buffers.h"
#include "media/base/decoder_buffer.h"
#include "media/base/test_helpers.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace media {
static const float kDataStep = 0.01f;
static const size_t kSampleRate = 48000;
static scoped_refptr<DecoderBuffer> CreateEncodedBuffer(
base::TimeDelta timestamp,
base::TimeDelta duration) {
scoped_refptr<DecoderBuffer> result(new DecoderBuffer(1));
result->set_timestamp(timestamp);
result->set_duration(duration);
return result;
}
static scoped_refptr<AudioBuffer> CreateDecodedBuffer(int frames) {
return MakeAudioBuffer(kSampleFormatPlanarF32,
CHANNEL_LAYOUT_MONO,
1,
kSampleRate,
0.0f,
kDataStep,
frames,
kNoTimestamp());
}
static float ExtractDecodedData(const scoped_refptr<AudioBuffer>& buffer,
int index) {
// This is really inefficient, but we can't access the raw AudioBuffer if any
// start trimming has been applied.
scoped_ptr<AudioBus> temp_bus = AudioBus::Create(buffer->channel_count(), 1);
buffer->ReadFrames(1, index, 0, temp_bus.get());
return temp_bus->channel(0)[0];
}
TEST(AudioDiscardHelperTest, TimeDeltaToFrames) {
AudioDiscardHelper discard_helper(kSampleRate, 0);
EXPECT_EQ(0u, discard_helper.TimeDeltaToFrames(base::TimeDelta()));
EXPECT_EQ(
kSampleRate / 100,
discard_helper.TimeDeltaToFrames(base::TimeDelta::FromMilliseconds(10)));
// Ensure partial frames are rounded down correctly. The equation below
// calculates a frame count with a fractional part < 0.5.
const int small_remainder =
base::Time::kMicrosecondsPerSecond * (kSampleRate - 0.9) / kSampleRate;
EXPECT_EQ(kSampleRate - 1,
discard_helper.TimeDeltaToFrames(
base::TimeDelta::FromMicroseconds(small_remainder)));
// Ditto, but rounded up using a fractional part > 0.5.
const int large_remainder =
base::Time::kMicrosecondsPerSecond * (kSampleRate - 0.4) / kSampleRate;
EXPECT_EQ(kSampleRate,
discard_helper.TimeDeltaToFrames(
base::TimeDelta::FromMicroseconds(large_remainder)));
}
TEST(AudioDiscardHelperTest, BasicProcessBuffers) {
AudioDiscardHelper discard_helper(kSampleRate, 0);
ASSERT_FALSE(discard_helper.initialized());
const base::TimeDelta kTimestamp = base::TimeDelta();
// Use an estimated duration which doesn't match the number of decoded frames
// to ensure the helper is correctly setting durations based on output frames.
const base::TimeDelta kEstimatedDuration =
base::TimeDelta::FromMilliseconds(9);
const base::TimeDelta kActualDuration = base::TimeDelta::FromMilliseconds(10);
const int kTestFrames = discard_helper.TimeDeltaToFrames(kActualDuration);
scoped_refptr<DecoderBuffer> encoded_buffer =
CreateEncodedBuffer(kTimestamp, kEstimatedDuration);
scoped_refptr<AudioBuffer> decoded_buffer = CreateDecodedBuffer(kTestFrames);
// Verify the basic case where nothing is discarded.
ASSERT_TRUE(discard_helper.ProcessBuffers(encoded_buffer, decoded_buffer));
ASSERT_TRUE(discard_helper.initialized());
EXPECT_EQ(kTimestamp, decoded_buffer->timestamp());
EXPECT_EQ(kActualDuration, decoded_buffer->duration());
EXPECT_EQ(kTestFrames, decoded_buffer->frame_count());
// Verify a Reset() takes us back to an uninitialized state.
discard_helper.Reset(0);
ASSERT_FALSE(discard_helper.initialized());
// Verify a NULL output buffer returns false.
ASSERT_FALSE(discard_helper.ProcessBuffers(encoded_buffer, NULL));
}
TEST(AudioDiscardHelperTest, NegativeTimestampClampsToZero) {
AudioDiscardHelper discard_helper(kSampleRate, 0);
ASSERT_FALSE(discard_helper.initialized());
const base::TimeDelta kTimestamp = -base::TimeDelta::FromSeconds(1);
const base::TimeDelta kDuration = base::TimeDelta::FromMilliseconds(10);
const int kTestFrames = discard_helper.TimeDeltaToFrames(kDuration);
scoped_refptr<DecoderBuffer> encoded_buffer =
CreateEncodedBuffer(kTimestamp, kDuration);
scoped_refptr<AudioBuffer> decoded_buffer = CreateDecodedBuffer(kTestFrames);
// Verify the basic case where nothing is discarded.
ASSERT_TRUE(discard_helper.ProcessBuffers(encoded_buffer, decoded_buffer));
ASSERT_TRUE(discard_helper.initialized());
EXPECT_EQ(base::TimeDelta(), decoded_buffer->timestamp());
EXPECT_EQ(kDuration, decoded_buffer->duration());
EXPECT_EQ(kTestFrames, decoded_buffer->frame_count());
}
TEST(AudioDiscardHelperTest, ProcessBuffersWithInitialDiscard) {
AudioDiscardHelper discard_helper(kSampleRate, 0);
ASSERT_FALSE(discard_helper.initialized());
const base::TimeDelta kTimestamp = base::TimeDelta();
const base::TimeDelta kDuration = base::TimeDelta::FromMilliseconds(10);
const int kTestFrames = discard_helper.TimeDeltaToFrames(kDuration);
// Tell the helper we want to discard half of the initial frames.
const int kDiscardFrames = kTestFrames / 2;
discard_helper.Reset(kDiscardFrames);
scoped_refptr<DecoderBuffer> encoded_buffer =
CreateEncodedBuffer(kTimestamp, kDuration);
scoped_refptr<AudioBuffer> decoded_buffer = CreateDecodedBuffer(kTestFrames);
// Verify half the frames end up discarded.
ASSERT_TRUE(discard_helper.ProcessBuffers(encoded_buffer, decoded_buffer));
ASSERT_TRUE(discard_helper.initialized());
EXPECT_EQ(kTimestamp, decoded_buffer->timestamp());
EXPECT_EQ(kDuration / 2, decoded_buffer->duration());
EXPECT_EQ(kDiscardFrames, decoded_buffer->frame_count());
ASSERT_FLOAT_EQ(kDiscardFrames * kDataStep,
ExtractDecodedData(decoded_buffer, 0));
}
TEST(AudioDiscardHelperTest, ProcessBuffersWithLargeInitialDiscard) {
AudioDiscardHelper discard_helper(kSampleRate, 0);
ASSERT_FALSE(discard_helper.initialized());
const base::TimeDelta kTimestamp = base::TimeDelta();
const base::TimeDelta kDuration = base::TimeDelta::FromMilliseconds(10);
const int kTestFrames = discard_helper.TimeDeltaToFrames(kDuration);
// Tell the helper we want to discard 1.5 buffers worth of frames.
discard_helper.Reset(kTestFrames * 1.5);
scoped_refptr<DecoderBuffer> encoded_buffer =
CreateEncodedBuffer(kTimestamp, kDuration);
scoped_refptr<AudioBuffer> decoded_buffer = CreateDecodedBuffer(kTestFrames);
// The first call should fail since no output buffer remains.
ASSERT_FALSE(discard_helper.ProcessBuffers(encoded_buffer, decoded_buffer));
ASSERT_TRUE(discard_helper.initialized());
// Generate another set of buffers and expect half the output frames.
encoded_buffer = CreateEncodedBuffer(kTimestamp + kDuration, kDuration);
decoded_buffer = CreateDecodedBuffer(kTestFrames);
ASSERT_TRUE(discard_helper.ProcessBuffers(encoded_buffer, decoded_buffer));
// The timestamp should match that of the initial buffer.
const int kDiscardFrames = kTestFrames / 2;
EXPECT_EQ(kTimestamp, decoded_buffer->timestamp());
EXPECT_EQ(kDuration / 2, decoded_buffer->duration());
EXPECT_EQ(kDiscardFrames, decoded_buffer->frame_count());
ASSERT_FLOAT_EQ(kDiscardFrames * kDataStep,
ExtractDecodedData(decoded_buffer, 0));
}
TEST(AudioDiscardHelperTest, AllowNonMonotonicTimestamps) {
AudioDiscardHelper discard_helper(kSampleRate, 0);
ASSERT_FALSE(discard_helper.initialized());
const base::TimeDelta kTimestamp = base::TimeDelta();
const base::TimeDelta kDuration = base::TimeDelta::FromMilliseconds(10);
const int kTestFrames = discard_helper.TimeDeltaToFrames(kDuration);
scoped_refptr<DecoderBuffer> encoded_buffer =
CreateEncodedBuffer(kTimestamp, kDuration);
scoped_refptr<AudioBuffer> decoded_buffer = CreateDecodedBuffer(kTestFrames);
ASSERT_TRUE(discard_helper.ProcessBuffers(encoded_buffer, decoded_buffer));
ASSERT_TRUE(discard_helper.initialized());
EXPECT_EQ(kTimestamp, decoded_buffer->timestamp());
EXPECT_EQ(kDuration, decoded_buffer->duration());
EXPECT_EQ(kTestFrames, decoded_buffer->frame_count());
// Process the same input buffer again to ensure input timestamps which go
// backwards in time are not errors.
ASSERT_TRUE(discard_helper.ProcessBuffers(encoded_buffer, decoded_buffer));
EXPECT_EQ(kTimestamp + kDuration, decoded_buffer->timestamp());
EXPECT_EQ(kDuration, decoded_buffer->duration());
EXPECT_EQ(kTestFrames, decoded_buffer->frame_count());
}
TEST(AudioDiscardHelperTest, DiscardEndPadding) {
AudioDiscardHelper discard_helper(kSampleRate, 0);
ASSERT_FALSE(discard_helper.initialized());
const base::TimeDelta kTimestamp = base::TimeDelta();
const base::TimeDelta kDuration = base::TimeDelta::FromMilliseconds(10);
const int kTestFrames = discard_helper.TimeDeltaToFrames(kDuration);
scoped_refptr<DecoderBuffer> encoded_buffer =
CreateEncodedBuffer(kTimestamp, kDuration);
scoped_refptr<AudioBuffer> decoded_buffer = CreateDecodedBuffer(kTestFrames);
// Set a discard padding equivalent to half the buffer.
encoded_buffer->set_discard_padding(
std::make_pair(base::TimeDelta(), kDuration / 2));
ASSERT_TRUE(discard_helper.ProcessBuffers(encoded_buffer, decoded_buffer));
ASSERT_TRUE(discard_helper.initialized());
EXPECT_EQ(kTimestamp, decoded_buffer->timestamp());
EXPECT_EQ(kDuration / 2, decoded_buffer->duration());
EXPECT_EQ(kTestFrames / 2, decoded_buffer->frame_count());
}
TEST(AudioDiscardHelperTest, BadDiscardEndPadding) {
AudioDiscardHelper discard_helper(kSampleRate, 0);
ASSERT_FALSE(discard_helper.initialized());
const base::TimeDelta kTimestamp = base::TimeDelta();
const base::TimeDelta kDuration = base::TimeDelta::FromMilliseconds(10);
const int kTestFrames = discard_helper.TimeDeltaToFrames(kDuration);
scoped_refptr<DecoderBuffer> encoded_buffer =
CreateEncodedBuffer(kTimestamp, kDuration);
scoped_refptr<AudioBuffer> decoded_buffer = CreateDecodedBuffer(kTestFrames);
// Set a discard padding equivalent to double the buffer size.
encoded_buffer->set_discard_padding(
std::make_pair(base::TimeDelta(), kDuration * 2));
// Verify the end discard padding is rejected.
ASSERT_FALSE(discard_helper.ProcessBuffers(encoded_buffer, decoded_buffer));
ASSERT_TRUE(discard_helper.initialized());
}
TEST(AudioDiscardHelperTest, InitialDiscardAndDiscardEndPadding) {
AudioDiscardHelper discard_helper(kSampleRate, 0);
ASSERT_FALSE(discard_helper.initialized());
const base::TimeDelta kTimestamp = base::TimeDelta();
const base::TimeDelta kDuration = base::TimeDelta::FromMilliseconds(10);
const int kTestFrames = discard_helper.TimeDeltaToFrames(kDuration);
scoped_refptr<DecoderBuffer> encoded_buffer =
CreateEncodedBuffer(kTimestamp, kDuration);
scoped_refptr<AudioBuffer> decoded_buffer = CreateDecodedBuffer(kTestFrames);
// Set a discard padding equivalent to a quarter of the buffer.
encoded_buffer->set_discard_padding(
std::make_pair(base::TimeDelta(), kDuration / 4));
// Set an initial discard of a quarter of the buffer.
const int kDiscardFrames = kTestFrames / 4;
discard_helper.Reset(kDiscardFrames);
ASSERT_TRUE(discard_helper.ProcessBuffers(encoded_buffer, decoded_buffer));
ASSERT_TRUE(discard_helper.initialized());
EXPECT_EQ(kTimestamp, decoded_buffer->timestamp());
EXPECT_EQ(kDuration / 2, decoded_buffer->duration());
EXPECT_EQ(kTestFrames / 2, decoded_buffer->frame_count());
ASSERT_FLOAT_EQ(kDiscardFrames * kDataStep,
ExtractDecodedData(decoded_buffer, 0));
}
TEST(AudioDiscardHelperTest, InitialDiscardAndDiscardPadding) {
AudioDiscardHelper discard_helper(kSampleRate, 0);
ASSERT_FALSE(discard_helper.initialized());
const base::TimeDelta kTimestamp = base::TimeDelta();
const base::TimeDelta kDuration = base::TimeDelta::FromMilliseconds(10);
const int kTestFrames = discard_helper.TimeDeltaToFrames(kDuration);
scoped_refptr<DecoderBuffer> encoded_buffer =
CreateEncodedBuffer(kTimestamp, kDuration);
scoped_refptr<AudioBuffer> decoded_buffer = CreateDecodedBuffer(kTestFrames);
// Set all the discard values to be different to ensure each is properly used.
const int kDiscardFrames = kTestFrames / 4;
encoded_buffer->set_discard_padding(
std::make_pair(kDuration / 8, kDuration / 16));
discard_helper.Reset(kDiscardFrames);
ASSERT_TRUE(discard_helper.ProcessBuffers(encoded_buffer, decoded_buffer));
ASSERT_TRUE(discard_helper.initialized());
EXPECT_EQ(kTimestamp, decoded_buffer->timestamp());
EXPECT_EQ(kDuration - kDuration / 4 - kDuration / 8 - kDuration / 16,
decoded_buffer->duration());
EXPECT_EQ(kTestFrames - kTestFrames / 4 - kTestFrames / 8 - kTestFrames / 16,
decoded_buffer->frame_count());
}
TEST(AudioDiscardHelperTest, InitialDiscardAndDiscardPaddingAndDecoderDelay) {
// Use a decoder delay of 5ms.
const int kDecoderDelay = kSampleRate / 100 / 2;
AudioDiscardHelper discard_helper(kSampleRate, kDecoderDelay);
ASSERT_FALSE(discard_helper.initialized());
discard_helper.Reset(kDecoderDelay);
const base::TimeDelta kTimestamp = base::TimeDelta();
const base::TimeDelta kDuration = base::TimeDelta::FromMilliseconds(10);
const int kTestFrames = discard_helper.TimeDeltaToFrames(kDuration);
scoped_refptr<DecoderBuffer> encoded_buffer =
CreateEncodedBuffer(kTimestamp, kDuration);
scoped_refptr<AudioBuffer> decoded_buffer = CreateDecodedBuffer(kTestFrames);
// Set a discard padding equivalent to half of the buffer.
encoded_buffer->set_discard_padding(
std::make_pair(kDuration / 2, base::TimeDelta()));
// All of the first buffer should be discarded.
ASSERT_FALSE(discard_helper.ProcessBuffers(encoded_buffer, decoded_buffer));
ASSERT_TRUE(discard_helper.initialized());
// Processing another buffer (with the same discard padding) should discard
// the back half of the buffer since kDecoderDelay is half a buffer.
encoded_buffer->set_timestamp(kTimestamp + kDuration);
decoded_buffer = CreateDecodedBuffer(kTestFrames);
ASSERT_FLOAT_EQ(0.0f, ExtractDecodedData(decoded_buffer, 0));
ASSERT_NEAR(kDecoderDelay * kDataStep,
ExtractDecodedData(decoded_buffer, kDecoderDelay),
kDataStep * 1000);
ASSERT_TRUE(discard_helper.ProcessBuffers(encoded_buffer, decoded_buffer));
EXPECT_EQ(kTimestamp, decoded_buffer->timestamp());
EXPECT_EQ(kDuration / 2, decoded_buffer->duration());
EXPECT_EQ(kTestFrames / 2, decoded_buffer->frame_count());
// Verify it was actually the latter half of the buffer that was removed.
ASSERT_FLOAT_EQ(0.0f, ExtractDecodedData(decoded_buffer, 0));
}
TEST(AudioDiscardHelperTest, DelayedDiscardInitialDiscardAndDiscardPadding) {
AudioDiscardHelper discard_helper(kSampleRate, 0);
ASSERT_FALSE(discard_helper.initialized());
const base::TimeDelta kTimestamp = base::TimeDelta();
const base::TimeDelta kDuration = base::TimeDelta::FromMilliseconds(10);
const int kTestFrames = discard_helper.TimeDeltaToFrames(kDuration);
scoped_refptr<DecoderBuffer> encoded_buffer =
CreateEncodedBuffer(kTimestamp, kDuration);
// Set all the discard values to be different to ensure each is properly used.
const int kDiscardFrames = kTestFrames / 4;
encoded_buffer->set_discard_padding(
std::make_pair(kDuration / 8, kDuration / 16));
discard_helper.Reset(kDiscardFrames);
// Verify nothing is output for the first buffer, yet initialized is true.
ASSERT_FALSE(discard_helper.ProcessBuffers(encoded_buffer, NULL));
ASSERT_TRUE(discard_helper.initialized());
// Create an encoded buffer with no discard padding.
encoded_buffer = CreateEncodedBuffer(kTimestamp + kDuration, kDuration);
scoped_refptr<AudioBuffer> decoded_buffer = CreateDecodedBuffer(kTestFrames);
// Verify that when the decoded buffer is consumed, the discards from the
// previous encoded buffer are applied.
ASSERT_TRUE(discard_helper.ProcessBuffers(encoded_buffer, decoded_buffer));
EXPECT_EQ(kTimestamp, decoded_buffer->timestamp());
EXPECT_EQ(kDuration - kDuration / 4 - kDuration / 8 - kDuration / 16,
decoded_buffer->duration());
EXPECT_EQ(kTestFrames - kTestFrames / 4 - kTestFrames / 8 - kTestFrames / 16,
decoded_buffer->frame_count());
}
TEST(AudioDiscardHelperTest, CompleteDiscard) {
AudioDiscardHelper discard_helper(kSampleRate, 0);
ASSERT_FALSE(discard_helper.initialized());
const base::TimeDelta kTimestamp = base::TimeDelta();
const base::TimeDelta kDuration = base::TimeDelta::FromMilliseconds(10);
const int kTestFrames = discard_helper.TimeDeltaToFrames(kDuration);
discard_helper.Reset(0);
scoped_refptr<DecoderBuffer> encoded_buffer =
CreateEncodedBuffer(kTimestamp, kDuration);
encoded_buffer->set_discard_padding(
std::make_pair(kInfiniteDuration(), base::TimeDelta()));
scoped_refptr<AudioBuffer> decoded_buffer = CreateDecodedBuffer(kTestFrames);
// Verify all of the first buffer is discarded.
ASSERT_FALSE(discard_helper.ProcessBuffers(encoded_buffer, decoded_buffer));
ASSERT_TRUE(discard_helper.initialized());
encoded_buffer->set_timestamp(kTimestamp + kDuration);
encoded_buffer->set_discard_padding(DecoderBuffer::DiscardPadding());
// Verify a second buffer goes through untouched.
decoded_buffer = CreateDecodedBuffer(kTestFrames / 2);
ASSERT_TRUE(discard_helper.ProcessBuffers(encoded_buffer, decoded_buffer));
EXPECT_EQ(kTimestamp, decoded_buffer->timestamp());
EXPECT_EQ(kDuration / 2, decoded_buffer->duration());
EXPECT_EQ(kTestFrames / 2, decoded_buffer->frame_count());
ASSERT_FLOAT_EQ(0.0f, ExtractDecodedData(decoded_buffer, 0));
}
TEST(AudioDiscardHelperTest, CompleteDiscardWithDelayedDiscard) {
AudioDiscardHelper discard_helper(kSampleRate, 0);
ASSERT_FALSE(discard_helper.initialized());
const base::TimeDelta kTimestamp = base::TimeDelta();
const base::TimeDelta kDuration = base::TimeDelta::FromMilliseconds(10);
const int kTestFrames = discard_helper.TimeDeltaToFrames(kDuration);
discard_helper.Reset(0);
scoped_refptr<DecoderBuffer> encoded_buffer =
CreateEncodedBuffer(kTimestamp, kDuration);
encoded_buffer->set_discard_padding(
std::make_pair(kInfiniteDuration(), base::TimeDelta()));
scoped_refptr<AudioBuffer> decoded_buffer = CreateDecodedBuffer(kTestFrames);
// Setup a delayed discard.
ASSERT_FALSE(discard_helper.ProcessBuffers(encoded_buffer, NULL));
ASSERT_TRUE(discard_helper.initialized());
// Verify the first output buffer is dropped.
encoded_buffer->set_timestamp(kTimestamp + kDuration);
encoded_buffer->set_discard_padding(DecoderBuffer::DiscardPadding());
ASSERT_FALSE(discard_helper.ProcessBuffers(encoded_buffer, decoded_buffer));
// Verify the second buffer goes through untouched.
encoded_buffer->set_timestamp(kTimestamp + 2 * kDuration);
decoded_buffer = CreateDecodedBuffer(kTestFrames / 2);
ASSERT_TRUE(discard_helper.ProcessBuffers(encoded_buffer, decoded_buffer));
EXPECT_EQ(kTimestamp, decoded_buffer->timestamp());
EXPECT_EQ(kDuration / 2, decoded_buffer->duration());
EXPECT_EQ(kTestFrames / 2, decoded_buffer->frame_count());
ASSERT_FLOAT_EQ(0.0f, ExtractDecodedData(decoded_buffer, 0));
}
TEST(AudioDiscardHelperTest, CompleteDiscardWithInitialDiscardDecoderDelay) {
// Use a decoder delay of 5ms.
const int kDecoderDelay = kSampleRate / 100 / 2;
AudioDiscardHelper discard_helper(kSampleRate, kDecoderDelay);
ASSERT_FALSE(discard_helper.initialized());
discard_helper.Reset(kDecoderDelay);
const base::TimeDelta kTimestamp = base::TimeDelta();
const base::TimeDelta kDuration = base::TimeDelta::FromMilliseconds(10);
const int kTestFrames = discard_helper.TimeDeltaToFrames(kDuration);
scoped_refptr<DecoderBuffer> encoded_buffer =
CreateEncodedBuffer(kTimestamp, kDuration);
encoded_buffer->set_discard_padding(
std::make_pair(kInfiniteDuration(), base::TimeDelta()));
scoped_refptr<AudioBuffer> decoded_buffer = CreateDecodedBuffer(kTestFrames);
// Verify all of the first buffer is discarded.
ASSERT_FALSE(discard_helper.ProcessBuffers(encoded_buffer, decoded_buffer));
ASSERT_TRUE(discard_helper.initialized());
encoded_buffer->set_timestamp(kTimestamp + kDuration);
encoded_buffer->set_discard_padding(DecoderBuffer::DiscardPadding());
// Verify 5ms off the front of the second buffer is discarded.
decoded_buffer = CreateDecodedBuffer(kTestFrames * 2);
ASSERT_TRUE(discard_helper.ProcessBuffers(encoded_buffer, decoded_buffer));
EXPECT_EQ(kTimestamp, decoded_buffer->timestamp());
EXPECT_EQ(kDuration * 2 - kDuration / 2, decoded_buffer->duration());
EXPECT_EQ(kTestFrames * 2 - kDecoderDelay, decoded_buffer->frame_count());
ASSERT_FLOAT_EQ(kDecoderDelay * kDataStep,
ExtractDecodedData(decoded_buffer, 0));
}
} // namespace media