blob: ac1e9b1dde2d31be77dd671d9888ec460bb4932d [file] [log] [blame]
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <future>
#include <thread>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <gui/Surface.h>
#include <mediadrm/ICrypto.h>
#include <media/stagefright/CodecBase.h>
#include <media/stagefright/MediaCodec.h>
#include <media/stagefright/MediaCodecListWriter.h>
#include <media/MediaCodecInfo.h>
#include "MediaTestHelper.h"
namespace android {
class MockBufferChannel : public BufferChannelBase {
public:
~MockBufferChannel() override = default;
MOCK_METHOD(void, setCrypto, (const sp<ICrypto> &crypto), (override));
MOCK_METHOD(void, setDescrambler, (const sp<IDescrambler> &descrambler), (override));
MOCK_METHOD(status_t, queueInputBuffer, (const sp<MediaCodecBuffer> &buffer), (override));
MOCK_METHOD(status_t, queueSecureInputBuffer,
(const sp<MediaCodecBuffer> &buffer,
bool secure,
const uint8_t *key,
const uint8_t *iv,
CryptoPlugin::Mode mode,
CryptoPlugin::Pattern pattern,
const CryptoPlugin::SubSample *subSamples,
size_t numSubSamples,
AString *errorDetailMsg),
(override));
MOCK_METHOD(status_t, attachBuffer,
(const std::shared_ptr<C2Buffer> &c2Buffer, const sp<MediaCodecBuffer> &buffer),
(override));
MOCK_METHOD(status_t, attachEncryptedBuffer,
(const sp<hardware::HidlMemory> &memory,
bool secure,
const uint8_t *key,
const uint8_t *iv,
CryptoPlugin::Mode mode,
CryptoPlugin::Pattern pattern,
size_t offset,
const CryptoPlugin::SubSample *subSamples,
size_t numSubSamples,
const sp<MediaCodecBuffer> &buffer),
(override));
MOCK_METHOD(status_t, renderOutputBuffer,
(const sp<MediaCodecBuffer> &buffer, int64_t timestampNs),
(override));
MOCK_METHOD(status_t, discardBuffer, (const sp<MediaCodecBuffer> &buffer), (override));
MOCK_METHOD(void, getInputBufferArray, (Vector<sp<MediaCodecBuffer>> *array), (override));
MOCK_METHOD(void, getOutputBufferArray, (Vector<sp<MediaCodecBuffer>> *array), (override));
};
class MockCodec : public CodecBase {
public:
MockCodec(std::function<void(const std::shared_ptr<MockBufferChannel> &)> mock) {
mMockBufferChannel = std::make_shared<MockBufferChannel>();
mock(mMockBufferChannel);
}
~MockCodec() override = default;
MOCK_METHOD(void, initiateAllocateComponent, (const sp<AMessage> &msg), (override));
MOCK_METHOD(void, initiateConfigureComponent, (const sp<AMessage> &msg), (override));
MOCK_METHOD(void, initiateCreateInputSurface, (), (override));
MOCK_METHOD(void, initiateSetInputSurface, (const sp<PersistentSurface> &surface), (override));
MOCK_METHOD(void, initiateStart, (), (override));
MOCK_METHOD(void, initiateShutdown, (bool keepComponentAllocated), (override));
MOCK_METHOD(void, onMessageReceived, (const sp<AMessage> &msg), (override));
MOCK_METHOD(status_t, setSurface, (const sp<Surface> &surface), (override));
MOCK_METHOD(void, signalFlush, (), (override));
MOCK_METHOD(void, signalResume, (), (override));
MOCK_METHOD(void, signalRequestIDRFrame, (), (override));
MOCK_METHOD(void, signalSetParameters, (const sp<AMessage> &msg), (override));
MOCK_METHOD(void, signalEndOfInputStream, (), (override));
std::shared_ptr<BufferChannelBase> getBufferChannel() override {
return mMockBufferChannel;
}
const std::unique_ptr<CodecCallback> &callback() {
return mCallback;
}
std::shared_ptr<MockBufferChannel> mMockBufferChannel;
};
class Counter {
public:
Counter() = default;
explicit Counter(int32_t initCount) : mCount(initCount) {}
~Counter() = default;
int32_t advance() {
std::unique_lock<std::mutex> lock(mMutex);
++mCount;
mCondition.notify_all();
return mCount;
}
template <typename Rep, typename Period, typename ...Args>
int32_t waitFor(const std::chrono::duration<Rep, Period> &duration, Args... values) {
std::initializer_list<int32_t> list = {values...};
std::unique_lock<std::mutex> lock(mMutex);
mCondition.wait_for(
lock,
duration,
[&list, this]{
return std::find(list.begin(), list.end(), mCount) != list.end();
});
return mCount;
}
template <typename ...Args>
int32_t wait(Args... values) {
std::initializer_list<int32_t> list = {values...};
std::unique_lock<std::mutex> lock(mMutex);
mCondition.wait(
lock,
[&list, this]{
return std::find(list.begin(), list.end(), mCount) != list.end();
});
return mCount;
}
private:
std::mutex mMutex;
std::condition_variable mCondition;
int32_t mCount = 0;
};
} // namespace android
using namespace android;
using ::testing::_;
static sp<MediaCodec> SetupMediaCodec(
const AString &owner,
const AString &codecName,
const AString &mediaType,
const sp<ALooper> &looper,
std::function<sp<CodecBase>(const AString &name, const char *owner)> getCodecBase) {
std::shared_ptr<MediaCodecListWriter> listWriter =
MediaTestHelper::CreateCodecListWriter();
std::unique_ptr<MediaCodecInfoWriter> infoWriter = listWriter->addMediaCodecInfo();
infoWriter->setName(codecName.c_str());
infoWriter->setOwner(owner.c_str());
infoWriter->addMediaType(mediaType.c_str());
std::vector<sp<MediaCodecInfo>> codecInfos;
MediaTestHelper::WriteCodecInfos(listWriter, &codecInfos);
std::function<status_t(const AString &, sp<MediaCodecInfo> *)> getCodecInfo =
[codecInfos](const AString &name, sp<MediaCodecInfo> *info) -> status_t {
auto it = std::find_if(
codecInfos.begin(), codecInfos.end(),
[&name](const sp<MediaCodecInfo> &info) {
return name.equalsIgnoreCase(info->getCodecName());
});
*info = (it == codecInfos.end()) ? nullptr : *it;
return (*info) ? OK : NAME_NOT_FOUND;
};
looper->start();
return MediaTestHelper::CreateCodec(
codecName, looper, getCodecBase, getCodecInfo);
}
TEST(MediaCodecTest, ReclaimReleaseRace) {
// Test scenario:
//
// 1) ResourceManager thread calls reclaim(), message posted to
// MediaCodec looper thread.
// 2) MediaCodec looper thread calls initiateShutdown(), shutdown being
// handled at the component thread.
// 3) Client thread calls release(), message posted to & handle at
// MediaCodec looper thread.
// 4) MediaCodec looper thread may call initiateShutdown().
// 5) initiateShutdown() from 2) is handled at onReleaseComplete() event
// posted to MediaCodec looper thread.
// 6) If called, initiateShutdown() from 4) is handled and
// onReleaseComplete() event posted to MediaCodec looper thread.
static const AString kCodecName{"test.codec"};
static const AString kCodecOwner{"nobody"};
static const AString kMediaType{"video/x-test"};
enum {
kInit,
kShutdownFromReclaimReceived,
kReleaseCalled,
};
Counter counter{kInit};
sp<MockCodec> mockCodec;
std::function<sp<CodecBase>(const AString &name, const char *owner)> getCodecBase =
[&mockCodec, &counter](const AString &, const char *) {
mockCodec = new MockCodec([](const std::shared_ptr<MockBufferChannel> &) {
// No mock setup, as we don't expect any buffer operations
// in this scenario.
});
ON_CALL(*mockCodec, initiateAllocateComponent(_))
.WillByDefault([mockCodec](const sp<AMessage> &) {
mockCodec->callback()->onComponentAllocated(kCodecName.c_str());
});
ON_CALL(*mockCodec, initiateShutdown(_))
.WillByDefault([mockCodec, &counter](bool) {
int32_t stage = counter.wait(kInit, kReleaseCalled);
if (stage == kInit) {
// Mark that 2) happened, so test can proceed to 3)
counter.advance();
} else if (stage == kReleaseCalled) {
// Handle 6)
mockCodec->callback()->onReleaseCompleted();
}
});
return mockCodec;
};
sp<ALooper> looper{new ALooper};
sp<MediaCodec> codec = SetupMediaCodec(
kCodecOwner, kCodecName, kMediaType, looper, getCodecBase);
ASSERT_NE(nullptr, codec) << "Codec must not be null";
ASSERT_NE(nullptr, mockCodec) << "MockCodec must not be null";
std::promise<void> reclaimCompleted;
std::promise<void> releaseCompleted;
Counter threadExitCounter;
std::thread([codec, &reclaimCompleted]{
// Simulate ResourceManager thread. Proceed with 1)
MediaTestHelper::Reclaim(codec, true /* force */);
reclaimCompleted.set_value();
}).detach();
std::thread([codec, &counter, &releaseCompleted]{
// Simulate client thread. Wait until 2) is complete
(void)counter.wait(kShutdownFromReclaimReceived);
// Proceed to 3), and mark that 5) is ready to happen.
// NOTE: it's difficult to pinpoint when 4) happens, so we will sleep
// to meet the timing.
counter.advance();
codec->release();
releaseCompleted.set_value();
}).detach();
std::thread([mockCodec, &counter]{
// Simulate component thread. Wait until 3) is complete
(void)counter.wait(kReleaseCalled);
// We want 4) to complete before moving forward, but it is hard to
// wait for this exact event. Just sleep so that the other thread can
// proceed and complete 4).
std::this_thread::sleep_for(std::chrono::milliseconds(100));
// Proceed to 5).
mockCodec->callback()->onReleaseCompleted();
}).detach();
EXPECT_EQ(
std::future_status::ready,
reclaimCompleted.get_future().wait_for(std::chrono::seconds(5)))
<< "reclaim timed out";
EXPECT_EQ(
std::future_status::ready,
releaseCompleted.get_future().wait_for(std::chrono::seconds(5)))
<< "release timed out";
looper->stop();
}
TEST(MediaCodecTest, ErrorWhileStopping) {
// Test scenario:
//
// 1) Client thread calls stop(); MediaCodec looper thread calls
// initiateShutdown(); shutdown is being handled at the component thread.
// 2) Error occurred, but the shutdown operation is still being done.
// 3) Another error occurred during the shutdown operation.
// 4) MediaCodec looper thread handles the error.
// 5) Client releases the codec upon the error; previous shutdown is still
// going on.
// 6) Component thread completes shutdown and posts onStopCompleted();
// Shutdown from release also completes.
static const AString kCodecName{"test.codec"};
static const AString kCodecOwner{"nobody"};
static const AString kMediaType{"video/x-test"};
sp<MockCodec> mockCodec;
std::function<sp<CodecBase>(const AString &name, const char *owner)> getCodecBase =
[&mockCodec](const AString &, const char *) {
mockCodec = new MockCodec([](const std::shared_ptr<MockBufferChannel> &) {
// No mock setup, as we don't expect any buffer operations
// in this scenario.
});
ON_CALL(*mockCodec, initiateAllocateComponent(_))
.WillByDefault([mockCodec](const sp<AMessage> &) {
mockCodec->callback()->onComponentAllocated(kCodecName.c_str());
});
ON_CALL(*mockCodec, initiateConfigureComponent(_))
.WillByDefault([mockCodec](const sp<AMessage> &msg) {
mockCodec->callback()->onComponentConfigured(
msg->dup(), msg->dup());
});
ON_CALL(*mockCodec, initiateStart())
.WillByDefault([mockCodec]() {
mockCodec->callback()->onStartCompleted();
});
ON_CALL(*mockCodec, initiateShutdown(true))
.WillByDefault([mockCodec](bool) {
// 2)
mockCodec->callback()->onError(UNKNOWN_ERROR, ACTION_CODE_FATAL);
// 3)
mockCodec->callback()->onError(UNKNOWN_ERROR, ACTION_CODE_FATAL);
});
ON_CALL(*mockCodec, initiateShutdown(false))
.WillByDefault([mockCodec](bool) {
// Previous stop finished now.
mockCodec->callback()->onStopCompleted();
// Release also finished.
mockCodec->callback()->onReleaseCompleted();
});
return mockCodec;
};
sp<ALooper> looper{new ALooper};
sp<MediaCodec> codec = SetupMediaCodec(
kCodecOwner, kCodecName, kMediaType, looper, getCodecBase);
ASSERT_NE(nullptr, codec) << "Codec must not be null";
ASSERT_NE(nullptr, mockCodec) << "MockCodec must not be null";
codec->configure(new AMessage, nullptr, nullptr, 0);
codec->start();
// stop() will fail because of the error
EXPECT_NE(OK, codec->stop());
// sleep here so that the looper thread can handle all the errors.
std::this_thread::sleep_for(std::chrono::milliseconds(100));
// upon receiving the error, client tries to release the codec.
codec->release();
looper->stop();
}
TEST(MediaCodecTest, DeadWhileAsyncReleasing) {
// Test scenario:
//
// 1) Client thread calls release(); MediaCodec looper thread calls
// initiateShutdown(); shutdown is being handled at the component thread.
// 2) Codec service died during the shutdown operation.
// 3) MediaCodec looper thread handles the death.
static const AString kCodecName{"test.codec"};
static const AString kCodecOwner{"nobody"};
static const AString kMediaType{"video/x-test"};
sp<MockCodec> mockCodec;
std::function<sp<CodecBase>(const AString &name, const char *owner)> getCodecBase =
[&mockCodec](const AString &, const char *) {
mockCodec = new MockCodec([](const std::shared_ptr<MockBufferChannel> &) {
// No mock setup, as we don't expect any buffer operations
// in this scenario.
});
ON_CALL(*mockCodec, initiateAllocateComponent(_))
.WillByDefault([mockCodec](const sp<AMessage> &) {
mockCodec->callback()->onComponentAllocated(kCodecName.c_str());
});
ON_CALL(*mockCodec, initiateShutdown(_))
.WillByDefault([mockCodec](bool) {
// 2)
mockCodec->callback()->onError(DEAD_OBJECT, ACTION_CODE_FATAL);
// Codec service has died, no callback.
});
return mockCodec;
};
sp<ALooper> looper{new ALooper};
sp<MediaCodec> codec = SetupMediaCodec(
kCodecOwner, kCodecName, kMediaType, looper, getCodecBase);
ASSERT_NE(nullptr, codec) << "Codec must not be null";
ASSERT_NE(nullptr, mockCodec) << "MockCodec must not be null";
codec->releaseAsync(new AMessage);
// sleep here so that the looper thread can handle the error
std::this_thread::sleep_for(std::chrono::milliseconds(100));
looper->stop();
}