blob: 3c90504d82499520b2239095de13cd7238219470 [file] [log] [blame]
// Copyright (c) 2012 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 <string>
#include <vector>
#include "base/basictypes.h"
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/message_loop.h"
#include "base/run_loop.h"
#include "cobalt/media/base/audio_buffer.h"
#include "cobalt/media/base/decoder_buffer.h"
#include "cobalt/media/base/decrypt_config.h"
#include "cobalt/media/base/gmock_callback_support.h"
#include "cobalt/media/base/media_util.h"
#include "cobalt/media/base/mock_filters.h"
#include "cobalt/media/base/test_helpers.h"
#include "cobalt/media/base/timestamp_constants.h"
#include "cobalt/media/filters/decrypting_audio_decoder.h"
#include "starboard/types.h"
#include "testing/gmock/include/gmock/gmock.h"
using ::testing::_;
using ::testing::AtMost;
using ::testing::Return;
using ::testing::SaveArg;
using ::testing::StrictMock;
namespace cobalt {
namespace media {
const int kSampleRate = 44100;
// Make sure the kFakeAudioFrameSize is a valid frame size for all audio decoder
// configs used in this test.
const int kFakeAudioFrameSize = 48;
const uint8_t kFakeKeyId[] = {0x4b, 0x65, 0x79, 0x20, 0x49, 0x44};
const uint8_t kFakeIv[DecryptConfig::kDecryptionKeySize] = {0};
const int kDecodingDelay = 3;
// Create a fake non-empty encrypted buffer.
static scoped_refptr<DecoderBuffer> CreateFakeEncryptedBuffer() {
const int buffer_size = 16; // Need a non-empty buffer;
scoped_refptr<DecoderBuffer> buffer(new DecoderBuffer(buffer_size));
buffer->set_decrypt_config(std::unique_ptr<DecryptConfig>(new DecryptConfig(
std::string(reinterpret_cast<const char*>(kFakeKeyId),
arraysize(kFakeKeyId)),
std::string(reinterpret_cast<const char*>(kFakeIv), arraysize(kFakeIv)),
std::vector<SubsampleEntry>())));
return buffer;
}
// Use anonymous namespace here to prevent the actions to be defined multiple
// times across multiple test files. Sadly we can't use static for them.
namespace {
ACTION_P(ReturnBuffer, buffer) { return buffer; }
} // namespace
class DecryptingAudioDecoderTest : public testing::Test {
public:
DecryptingAudioDecoderTest()
: decoder_(new DecryptingAudioDecoder(
message_loop_.task_runner(), new MediaLog(),
base::Bind(&DecryptingAudioDecoderTest::OnWaitingForDecryptionKey,
base::Unretained(this)))),
cdm_context_(new StrictMock<MockCdmContext>()),
decryptor_(new StrictMock<MockDecryptor>()),
num_decrypt_and_decode_calls_(0),
num_frames_in_decryptor_(0),
encrypted_buffer_(CreateFakeEncryptedBuffer()),
decoded_frame_(NULL),
decoded_frame_list_() {}
virtual ~DecryptingAudioDecoderTest() { Destroy(); }
void InitializeAndExpectResult(const AudioDecoderConfig& config,
bool success) {
// Initialize data now that the config is known. Since the code uses
// invalid values (that CreateEmptyBuffer() doesn't support), tweak them
// just for CreateEmptyBuffer().
int channels = ChannelLayoutToChannelCount(config.channel_layout());
if (channels < 0) channels = 0;
decoded_frame_ = AudioBuffer::CreateEmptyBuffer(
config.channel_layout(), channels, kSampleRate, kFakeAudioFrameSize,
kNoTimestamp);
decoded_frame_list_.push_back(decoded_frame_);
decoder_->Initialize(config, cdm_context_.get(), NewExpectedBoolCB(success),
base::Bind(&DecryptingAudioDecoderTest::FrameReady,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
}
enum CdmType { CDM_WITHOUT_DECRYPTOR, CDM_WITH_DECRYPTOR };
void SetCdmType(CdmType cdm_type) {
const bool has_decryptor = cdm_type == CDM_WITH_DECRYPTOR;
EXPECT_CALL(*cdm_context_, GetDecryptor())
.WillRepeatedly(Return(has_decryptor ? decryptor_.get() : NULL));
}
void Initialize() {
SetCdmType(CDM_WITH_DECRYPTOR);
EXPECT_CALL(*decryptor_, InitializeAudioDecoder(_, _))
.Times(AtMost(1))
.WillOnce(RunCallback<1>(true));
EXPECT_CALL(*decryptor_, RegisterNewKeyCB(Decryptor::kAudio, _))
.WillOnce(SaveArg<1>(&key_added_cb_));
config_.Initialize(kCodecVorbis, kSampleFormatPlanarF32,
CHANNEL_LAYOUT_STEREO, kSampleRate, EmptyExtraData(),
AesCtrEncryptionScheme(), base::TimeDelta(), 0);
InitializeAndExpectResult(config_, true);
}
void Reinitialize() { ReinitializeConfigChange(config_); }
void ReinitializeConfigChange(const AudioDecoderConfig& new_config) {
EXPECT_CALL(*decryptor_, DeinitializeDecoder(Decryptor::kAudio));
EXPECT_CALL(*decryptor_, InitializeAudioDecoder(_, _))
.WillOnce(RunCallback<1>(true));
EXPECT_CALL(*decryptor_, RegisterNewKeyCB(Decryptor::kAudio, _))
.WillOnce(SaveArg<1>(&key_added_cb_));
decoder_->Initialize(new_config, NULL, NewExpectedBoolCB(true),
base::Bind(&DecryptingAudioDecoderTest::FrameReady,
base::Unretained(this)));
}
// Decode |buffer| and expect DecodeDone to get called with |status|.
void DecodeAndExpect(const scoped_refptr<DecoderBuffer>& buffer,
DecodeStatus status) {
EXPECT_CALL(*this, DecodeDone(status));
decoder_->Decode(buffer, base::Bind(&DecryptingAudioDecoderTest::DecodeDone,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
}
// Helper function to simulate the decrypting and decoding process in the
// |decryptor_| with a decoding delay of kDecodingDelay buffers.
void DecryptAndDecodeAudio(const scoped_refptr<DecoderBuffer>& encrypted,
const Decryptor::AudioDecodeCB& audio_decode_cb) {
num_decrypt_and_decode_calls_++;
if (!encrypted->end_of_stream()) num_frames_in_decryptor_++;
if (num_decrypt_and_decode_calls_ <= kDecodingDelay ||
num_frames_in_decryptor_ == 0) {
audio_decode_cb.Run(Decryptor::kNeedMoreData, Decryptor::AudioFrames());
return;
}
num_frames_in_decryptor_--;
audio_decode_cb.Run(Decryptor::kSuccess,
Decryptor::AudioFrames(1, decoded_frame_));
}
// Sets up expectations and actions to put DecryptingAudioDecoder in an
// active normal decoding state.
void EnterNormalDecodingState() {
EXPECT_CALL(*decryptor_, DecryptAndDecodeAudio(_, _))
.WillRepeatedly(
Invoke(this, &DecryptingAudioDecoderTest::DecryptAndDecodeAudio));
EXPECT_CALL(*this, FrameReady(decoded_frame_));
for (int i = 0; i < kDecodingDelay + 1; ++i)
DecodeAndExpect(encrypted_buffer_, DecodeStatus::OK);
}
// Sets up expectations and actions to put DecryptingAudioDecoder in an end
// of stream state. This function must be called after
// EnterNormalDecodingState() to work.
void EnterEndOfStreamState() {
// The codec in the |decryptor_| will be flushed.
EXPECT_CALL(*this, FrameReady(decoded_frame_)).Times(kDecodingDelay);
DecodeAndExpect(DecoderBuffer::CreateEOSBuffer(), DecodeStatus::OK);
EXPECT_EQ(0, num_frames_in_decryptor_);
}
// Make the audio decode callback pending by saving and not firing it.
void EnterPendingDecodeState() {
EXPECT_TRUE(pending_audio_decode_cb_.is_null());
EXPECT_CALL(*decryptor_, DecryptAndDecodeAudio(encrypted_buffer_, _))
.WillOnce(SaveArg<1>(&pending_audio_decode_cb_));
decoder_->Decode(encrypted_buffer_,
base::Bind(&DecryptingAudioDecoderTest::DecodeDone,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
// Make sure the Decode() on the decoder triggers a DecryptAndDecode() on
// the decryptor.
EXPECT_FALSE(pending_audio_decode_cb_.is_null());
}
void EnterWaitingForKeyState() {
EXPECT_CALL(*decryptor_, DecryptAndDecodeAudio(encrypted_buffer_, _))
.WillRepeatedly(
RunCallback<1>(Decryptor::kNoKey, Decryptor::AudioFrames()));
EXPECT_CALL(*this, OnWaitingForDecryptionKey());
decoder_->Decode(encrypted_buffer_,
base::Bind(&DecryptingAudioDecoderTest::DecodeDone,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
}
void AbortPendingAudioDecodeCB() {
if (!pending_audio_decode_cb_.is_null()) {
base::ResetAndReturn(&pending_audio_decode_cb_)
.Run(Decryptor::kSuccess, Decryptor::AudioFrames());
}
}
void AbortAllPendingCBs() {
if (!pending_init_cb_.is_null()) {
ASSERT_TRUE(pending_audio_decode_cb_.is_null());
base::ResetAndReturn(&pending_init_cb_).Run(false);
return;
}
AbortPendingAudioDecodeCB();
}
void Reset() {
EXPECT_CALL(*decryptor_, ResetDecoder(Decryptor::kAudio))
.WillRepeatedly(InvokeWithoutArgs(
this, &DecryptingAudioDecoderTest::AbortPendingAudioDecodeCB));
decoder_->Reset(NewExpectedClosure());
base::RunLoop().RunUntilIdle();
}
void Destroy() {
EXPECT_CALL(*decryptor_, DeinitializeDecoder(Decryptor::kAudio))
.WillRepeatedly(InvokeWithoutArgs(
this, &DecryptingAudioDecoderTest::AbortAllPendingCBs));
decoder_.reset();
base::RunLoop().RunUntilIdle();
}
MOCK_METHOD1(FrameReady, void(const scoped_refptr<AudioBuffer>&));
MOCK_METHOD1(DecodeDone, void(DecodeStatus));
MOCK_METHOD0(OnWaitingForDecryptionKey, void(void));
base::MessageLoop message_loop_;
std::unique_ptr<DecryptingAudioDecoder> decoder_;
std::unique_ptr<StrictMock<MockCdmContext>> cdm_context_;
std::unique_ptr<StrictMock<MockDecryptor>> decryptor_;
AudioDecoderConfig config_;
// Variables to help the |decryptor_| to simulate decoding delay and flushing.
int num_decrypt_and_decode_calls_;
int num_frames_in_decryptor_;
Decryptor::DecoderInitCB pending_init_cb_;
Decryptor::NewKeyCB key_added_cb_;
Decryptor::AudioDecodeCB pending_audio_decode_cb_;
// Constant buffer/frames, to be used/returned by |decoder_| and |decryptor_|.
scoped_refptr<DecoderBuffer> encrypted_buffer_;
scoped_refptr<AudioBuffer> decoded_frame_;
Decryptor::AudioFrames decoded_frame_list_;
private:
DISALLOW_COPY_AND_ASSIGN(DecryptingAudioDecoderTest);
};
TEST_F(DecryptingAudioDecoderTest, Initialize_Normal) { Initialize(); }
// Ensure that DecryptingAudioDecoder only accepts encrypted audio.
TEST_F(DecryptingAudioDecoderTest, Initialize_UnencryptedAudioConfig) {
AudioDecoderConfig config(kCodecVorbis, kSampleFormatPlanarF32,
CHANNEL_LAYOUT_STEREO, kSampleRate,
EmptyExtraData(), Unencrypted());
InitializeAndExpectResult(config, false);
}
// Ensure decoder handles invalid audio configs without crashing.
TEST_F(DecryptingAudioDecoderTest, Initialize_InvalidAudioConfig) {
AudioDecoderConfig config(kUnknownAudioCodec, kUnknownSampleFormat,
CHANNEL_LAYOUT_STEREO, 0, EmptyExtraData(),
AesCtrEncryptionScheme());
InitializeAndExpectResult(config, false);
}
// Ensure decoder handles unsupported audio configs without crashing.
TEST_F(DecryptingAudioDecoderTest, Initialize_UnsupportedAudioConfig) {
SetCdmType(CDM_WITH_DECRYPTOR);
EXPECT_CALL(*decryptor_, InitializeAudioDecoder(_, _))
.WillOnce(RunCallback<1>(false));
AudioDecoderConfig config(kCodecVorbis, kSampleFormatPlanarF32,
CHANNEL_LAYOUT_STEREO, kSampleRate,
EmptyExtraData(), AesCtrEncryptionScheme());
InitializeAndExpectResult(config, false);
}
TEST_F(DecryptingAudioDecoderTest, Initialize_CdmWithoutDecryptor) {
SetCdmType(CDM_WITHOUT_DECRYPTOR);
AudioDecoderConfig config(kCodecVorbis, kSampleFormatPlanarF32,
CHANNEL_LAYOUT_STEREO, kSampleRate,
EmptyExtraData(), AesCtrEncryptionScheme());
InitializeAndExpectResult(config, false);
}
// Test normal decrypt and decode case.
TEST_F(DecryptingAudioDecoderTest, DecryptAndDecode_Normal) {
Initialize();
EnterNormalDecodingState();
}
// Test the case where the decryptor returns error when doing decrypt and
// decode.
TEST_F(DecryptingAudioDecoderTest, DecryptAndDecode_DecodeError) {
Initialize();
EXPECT_CALL(*decryptor_, DecryptAndDecodeAudio(_, _))
.WillRepeatedly(
RunCallback<1>(Decryptor::kError, Decryptor::AudioFrames()));
DecodeAndExpect(encrypted_buffer_, DecodeStatus::DECODE_ERROR);
}
// Test the case where the decryptor returns multiple decoded frames.
TEST_F(DecryptingAudioDecoderTest, DecryptAndDecode_MultipleFrames) {
Initialize();
scoped_refptr<AudioBuffer> frame_a = AudioBuffer::CreateEmptyBuffer(
config_.channel_layout(),
ChannelLayoutToChannelCount(config_.channel_layout()), kSampleRate,
kFakeAudioFrameSize, kNoTimestamp);
scoped_refptr<AudioBuffer> frame_b = AudioBuffer::CreateEmptyBuffer(
config_.channel_layout(),
ChannelLayoutToChannelCount(config_.channel_layout()), kSampleRate,
kFakeAudioFrameSize, kNoTimestamp);
decoded_frame_list_.push_back(frame_a);
decoded_frame_list_.push_back(frame_b);
EXPECT_CALL(*decryptor_, DecryptAndDecodeAudio(_, _))
.WillOnce(RunCallback<1>(Decryptor::kSuccess, decoded_frame_list_));
EXPECT_CALL(*this, FrameReady(decoded_frame_));
EXPECT_CALL(*this, FrameReady(frame_a));
EXPECT_CALL(*this, FrameReady(frame_b));
DecodeAndExpect(encrypted_buffer_, DecodeStatus::OK);
}
// Test the case where the decryptor receives end-of-stream buffer.
TEST_F(DecryptingAudioDecoderTest, DecryptAndDecode_EndOfStream) {
Initialize();
EnterNormalDecodingState();
EnterEndOfStreamState();
}
// Test reinitializing decode with a new config
TEST_F(DecryptingAudioDecoderTest, Reinitialize_ConfigChange) {
Initialize();
EXPECT_CALL(*decryptor_, InitializeAudioDecoder(_, _))
.Times(AtMost(1))
.WillOnce(RunCallback<1>(true));
// The new config is different from the initial config in bits-per-channel,
// channel layout and samples_per_second.
AudioDecoderConfig new_config(kCodecVorbis, kSampleFormatPlanarS16,
CHANNEL_LAYOUT_5_1, 88200, EmptyExtraData(),
AesCtrEncryptionScheme());
EXPECT_NE(new_config.bits_per_channel(), config_.bits_per_channel());
EXPECT_NE(new_config.channel_layout(), config_.channel_layout());
EXPECT_NE(new_config.samples_per_second(), config_.samples_per_second());
ReinitializeConfigChange(new_config);
base::RunLoop().RunUntilIdle();
}
// Test the case where the a key is added when the decryptor is in
// kWaitingForKey state.
TEST_F(DecryptingAudioDecoderTest, KeyAdded_DuringWaitingForKey) {
Initialize();
EnterWaitingForKeyState();
EXPECT_CALL(*decryptor_, DecryptAndDecodeAudio(_, _))
.WillRepeatedly(RunCallback<1>(Decryptor::kSuccess, decoded_frame_list_));
EXPECT_CALL(*this, FrameReady(decoded_frame_));
EXPECT_CALL(*this, DecodeDone(DecodeStatus::OK));
key_added_cb_.Run();
base::RunLoop().RunUntilIdle();
}
// Test the case where the a key is added when the decryptor is in
// kPendingDecode state.
TEST_F(DecryptingAudioDecoderTest, KeyAdded_DruingPendingDecode) {
Initialize();
EnterPendingDecodeState();
EXPECT_CALL(*decryptor_, DecryptAndDecodeAudio(_, _))
.WillRepeatedly(RunCallback<1>(Decryptor::kSuccess, decoded_frame_list_));
EXPECT_CALL(*this, FrameReady(decoded_frame_));
EXPECT_CALL(*this, DecodeDone(DecodeStatus::OK));
// The audio decode callback is returned after the correct decryption key is
// added.
key_added_cb_.Run();
base::ResetAndReturn(&pending_audio_decode_cb_)
.Run(Decryptor::kNoKey, Decryptor::AudioFrames());
base::RunLoop().RunUntilIdle();
}
// Test resetting when the decoder is in kIdle state but has not decoded any
// frame.
TEST_F(DecryptingAudioDecoderTest, Reset_DuringIdleAfterInitialization) {
Initialize();
Reset();
}
// Test resetting when the decoder is in kIdle state after it has decoded one
// frame.
TEST_F(DecryptingAudioDecoderTest, Reset_DuringIdleAfterDecodedOneFrame) {
Initialize();
EnterNormalDecodingState();
Reset();
}
// Test resetting when the decoder is in kPendingDecode state.
TEST_F(DecryptingAudioDecoderTest, Reset_DuringPendingDecode) {
Initialize();
EnterPendingDecodeState();
EXPECT_CALL(*this, DecodeDone(DecodeStatus::ABORTED));
Reset();
}
// Test resetting when the decoder is in kWaitingForKey state.
TEST_F(DecryptingAudioDecoderTest, Reset_DuringWaitingForKey) {
Initialize();
EnterWaitingForKeyState();
EXPECT_CALL(*this, DecodeDone(DecodeStatus::ABORTED));
Reset();
}
// Test resetting when the decoder has hit end of stream and is in
// kDecodeFinished state.
TEST_F(DecryptingAudioDecoderTest, Reset_AfterDecodeFinished) {
Initialize();
EnterNormalDecodingState();
EnterEndOfStreamState();
Reset();
}
// Test resetting after the decoder has been reset.
TEST_F(DecryptingAudioDecoderTest, Reset_AfterReset) {
Initialize();
EnterNormalDecodingState();
Reset();
Reset();
}
} // namespace media
} // namespace cobalt