You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
484 lines
14 KiB
484 lines
14 KiB
4 months ago
|
/*
|
||
|
* Copyright (C) 2017 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 <inttypes.h>
|
||
|
#include <fcntl.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <string.h>
|
||
|
#include <sys/time.h>
|
||
|
#include <sys/types.h>
|
||
|
#include <sys/stat.h>
|
||
|
|
||
|
#include <thread>
|
||
|
|
||
|
//#define LOG_NDEBUG 0
|
||
|
#define LOG_TAG "codec2"
|
||
|
#include <log/log.h>
|
||
|
|
||
|
#include <binder/IServiceManager.h>
|
||
|
#include <binder/ProcessState.h>
|
||
|
#include <datasource/DataSourceFactory.h>
|
||
|
#include <media/DataSource.h>
|
||
|
#include <mediadrm/ICrypto.h>
|
||
|
#include <media/IMediaHTTPService.h>
|
||
|
#include <media/stagefright/MediaSource.h>
|
||
|
#include <media/stagefright/foundation/ABuffer.h>
|
||
|
#include <media/stagefright/foundation/ALooper.h>
|
||
|
#include <media/stagefright/foundation/AMessage.h>
|
||
|
#include <media/stagefright/foundation/AUtils.h>
|
||
|
#include <media/stagefright/MediaDefs.h>
|
||
|
#include <media/stagefright/MediaErrors.h>
|
||
|
#include <media/stagefright/MediaExtractorFactory.h>
|
||
|
#include <media/stagefright/MetaData.h>
|
||
|
#include <media/stagefright/Utils.h>
|
||
|
|
||
|
#include <gui/GLConsumer.h>
|
||
|
#include <gui/IProducerListener.h>
|
||
|
#include <gui/Surface.h>
|
||
|
#include <gui/SurfaceComposerClient.h>
|
||
|
|
||
|
#include <C2AllocatorGralloc.h>
|
||
|
#include <C2Buffer.h>
|
||
|
#include <C2BufferPriv.h>
|
||
|
#include <C2Component.h>
|
||
|
#include <C2Config.h>
|
||
|
#include <C2Debug.h>
|
||
|
#include <C2PlatformSupport.h>
|
||
|
#include <C2Work.h>
|
||
|
|
||
|
using namespace android;
|
||
|
using namespace std::chrono_literals;
|
||
|
|
||
|
namespace {
|
||
|
|
||
|
class LinearBuffer : public C2Buffer {
|
||
|
public:
|
||
|
explicit LinearBuffer(const std::shared_ptr<C2LinearBlock> &block)
|
||
|
: C2Buffer({ block->share(block->offset(), block->size(), ::C2Fence()) }) {}
|
||
|
};
|
||
|
|
||
|
class Listener;
|
||
|
|
||
|
class SimplePlayer {
|
||
|
public:
|
||
|
SimplePlayer();
|
||
|
~SimplePlayer();
|
||
|
|
||
|
void onWorkDone(std::weak_ptr<C2Component> component,
|
||
|
std::list<std::unique_ptr<C2Work>> workItems);
|
||
|
void onTripped(std::weak_ptr<C2Component> component,
|
||
|
std::vector<std::shared_ptr<C2SettingResult>> settingResult);
|
||
|
void onError(std::weak_ptr<C2Component> component, uint32_t errorCode);
|
||
|
|
||
|
void play(const sp<IMediaSource> &source);
|
||
|
|
||
|
private:
|
||
|
typedef std::unique_lock<std::mutex> ULock;
|
||
|
|
||
|
std::shared_ptr<Listener> mListener;
|
||
|
std::shared_ptr<C2Component> mComponent;
|
||
|
|
||
|
sp<IProducerListener> mProducerListener;
|
||
|
|
||
|
std::atomic_int mLinearPoolId;
|
||
|
|
||
|
std::shared_ptr<C2Allocator> mAllocIon;
|
||
|
std::shared_ptr<C2BlockPool> mLinearPool;
|
||
|
|
||
|
std::mutex mQueueLock;
|
||
|
std::condition_variable mQueueCondition;
|
||
|
std::list<std::unique_ptr<C2Work>> mWorkQueue;
|
||
|
|
||
|
std::mutex mProcessedLock;
|
||
|
std::condition_variable mProcessedCondition;
|
||
|
std::list<std::unique_ptr<C2Work>> mProcessedWork;
|
||
|
|
||
|
sp<Surface> mSurface;
|
||
|
sp<SurfaceComposerClient> mComposerClient;
|
||
|
sp<SurfaceControl> mControl;
|
||
|
};
|
||
|
|
||
|
class Listener : public C2Component::Listener {
|
||
|
public:
|
||
|
explicit Listener(SimplePlayer *thiz) : mThis(thiz) {}
|
||
|
virtual ~Listener() = default;
|
||
|
|
||
|
virtual void onWorkDone_nb(std::weak_ptr<C2Component> component,
|
||
|
std::list<std::unique_ptr<C2Work>> workItems) override {
|
||
|
mThis->onWorkDone(component, std::move(workItems));
|
||
|
}
|
||
|
|
||
|
virtual void onTripped_nb(std::weak_ptr<C2Component> component,
|
||
|
std::vector<std::shared_ptr<C2SettingResult>> settingResult) override {
|
||
|
mThis->onTripped(component, settingResult);
|
||
|
}
|
||
|
|
||
|
virtual void onError_nb(std::weak_ptr<C2Component> component,
|
||
|
uint32_t errorCode) override {
|
||
|
mThis->onError(component, errorCode);
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
SimplePlayer * const mThis;
|
||
|
};
|
||
|
|
||
|
|
||
|
SimplePlayer::SimplePlayer()
|
||
|
: mListener(new Listener(this)),
|
||
|
mProducerListener(new StubProducerListener),
|
||
|
mLinearPoolId(C2BlockPool::PLATFORM_START),
|
||
|
mComposerClient(new SurfaceComposerClient) {
|
||
|
CHECK_EQ(mComposerClient->initCheck(), (status_t)OK);
|
||
|
|
||
|
std::shared_ptr<C2AllocatorStore> store = GetCodec2PlatformAllocatorStore();
|
||
|
CHECK_EQ(store->fetchAllocator(C2AllocatorStore::DEFAULT_LINEAR, &mAllocIon), C2_OK);
|
||
|
mLinearPool = std::make_shared<C2PooledBlockPool>(mAllocIon, mLinearPoolId++);
|
||
|
|
||
|
mControl = mComposerClient->createSurface(
|
||
|
String8("A Surface"),
|
||
|
1280,
|
||
|
800,
|
||
|
HAL_PIXEL_FORMAT_YV12);
|
||
|
//PIXEL_FORMAT_RGB_565);
|
||
|
|
||
|
CHECK(mControl != nullptr);
|
||
|
CHECK(mControl->isValid());
|
||
|
|
||
|
SurfaceComposerClient::Transaction{}
|
||
|
.setLayer(mControl, INT_MAX)
|
||
|
.show(mControl)
|
||
|
.apply();
|
||
|
|
||
|
mSurface = mControl->getSurface();
|
||
|
CHECK(mSurface != nullptr);
|
||
|
mSurface->connect(NATIVE_WINDOW_API_CPU, mProducerListener);
|
||
|
}
|
||
|
|
||
|
SimplePlayer::~SimplePlayer() {
|
||
|
mComposerClient->dispose();
|
||
|
}
|
||
|
|
||
|
void SimplePlayer::onWorkDone(
|
||
|
std::weak_ptr<C2Component> component, std::list<std::unique_ptr<C2Work>> workItems) {
|
||
|
ALOGV("SimplePlayer::onWorkDone");
|
||
|
(void) component;
|
||
|
ULock l(mProcessedLock);
|
||
|
for (auto & item : workItems) {
|
||
|
mProcessedWork.push_back(std::move(item));
|
||
|
}
|
||
|
mProcessedCondition.notify_all();
|
||
|
}
|
||
|
|
||
|
void SimplePlayer::onTripped(
|
||
|
std::weak_ptr<C2Component> component,
|
||
|
std::vector<std::shared_ptr<C2SettingResult>> settingResult) {
|
||
|
(void) component;
|
||
|
(void) settingResult;
|
||
|
// TODO
|
||
|
}
|
||
|
|
||
|
void SimplePlayer::onError(std::weak_ptr<C2Component> component, uint32_t errorCode) {
|
||
|
(void) component;
|
||
|
(void) errorCode;
|
||
|
// TODO
|
||
|
}
|
||
|
|
||
|
void SimplePlayer::play(const sp<IMediaSource> &source) {
|
||
|
ALOGV("SimplePlayer::play");
|
||
|
sp<AMessage> format;
|
||
|
(void) convertMetaDataToMessage(source->getFormat(), &format);
|
||
|
|
||
|
sp<ABuffer> csd0, csd1;
|
||
|
format->findBuffer("csd-0", &csd0);
|
||
|
format->findBuffer("csd-1", &csd1);
|
||
|
|
||
|
status_t err = source->start();
|
||
|
|
||
|
if (err != OK) {
|
||
|
fprintf(stderr, "source returned error %d (0x%08x)\n", err, err);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
std::shared_ptr<C2ComponentStore> store = GetCodec2PlatformComponentStore();
|
||
|
std::shared_ptr<C2Component> component;
|
||
|
(void)store->createComponent("c2.android.avc.decoder", &component);
|
||
|
|
||
|
(void)component->setListener_vb(mListener, C2_DONT_BLOCK);
|
||
|
std::unique_ptr<C2PortBlockPoolsTuning::output> pools =
|
||
|
C2PortBlockPoolsTuning::output::AllocUnique({ (uint64_t)C2BlockPool::BASIC_GRAPHIC });
|
||
|
std::vector<std::unique_ptr<C2SettingResult>> result;
|
||
|
(void)component->intf()->config_vb({pools.get()}, C2_DONT_BLOCK, &result);
|
||
|
component->start();
|
||
|
|
||
|
for (int i = 0; i < 8; ++i) {
|
||
|
mWorkQueue.emplace_back(new C2Work);
|
||
|
}
|
||
|
|
||
|
std::atomic_bool running(true);
|
||
|
std::thread surfaceThread([this, &running]() {
|
||
|
const sp<IGraphicBufferProducer> &igbp = mSurface->getIGraphicBufferProducer();
|
||
|
while (running) {
|
||
|
std::unique_ptr<C2Work> work;
|
||
|
{
|
||
|
ULock l(mProcessedLock);
|
||
|
if (mProcessedWork.empty()) {
|
||
|
mProcessedCondition.wait_for(l, 100ms);
|
||
|
if (mProcessedWork.empty()) {
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
work.swap(mProcessedWork.front());
|
||
|
mProcessedWork.pop_front();
|
||
|
}
|
||
|
int slot;
|
||
|
sp<Fence> fence;
|
||
|
ALOGV("Render: Frame #%lld", work->worklets.front()->output.ordinal.frameIndex.peekll());
|
||
|
const std::shared_ptr<C2Buffer> &output = work->worklets.front()->output.buffers[0];
|
||
|
if (output) {
|
||
|
const C2ConstGraphicBlock block = output->data().graphicBlocks().front();
|
||
|
native_handle_t *grallocHandle = UnwrapNativeCodec2GrallocHandle(block.handle());
|
||
|
sp<GraphicBuffer> buffer(new GraphicBuffer(
|
||
|
grallocHandle,
|
||
|
GraphicBuffer::CLONE_HANDLE,
|
||
|
block.width(),
|
||
|
block.height(),
|
||
|
HAL_PIXEL_FORMAT_YV12,
|
||
|
1,
|
||
|
(uint64_t)GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN,
|
||
|
block.width()));
|
||
|
native_handle_delete(grallocHandle);
|
||
|
|
||
|
status_t err = igbp->attachBuffer(&slot, buffer);
|
||
|
|
||
|
IGraphicBufferProducer::QueueBufferInput qbi(
|
||
|
(work->worklets.front()->output.ordinal.timestamp * 1000ll).peekll(),
|
||
|
false,
|
||
|
HAL_DATASPACE_UNKNOWN,
|
||
|
Rect(block.width(), block.height()),
|
||
|
NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW,
|
||
|
0,
|
||
|
Fence::NO_FENCE,
|
||
|
0);
|
||
|
IGraphicBufferProducer::QueueBufferOutput qbo;
|
||
|
err = igbp->queueBuffer(slot, qbi, &qbo);
|
||
|
}
|
||
|
|
||
|
work->input.buffers.clear();
|
||
|
work->worklets.clear();
|
||
|
|
||
|
ULock l(mQueueLock);
|
||
|
mWorkQueue.push_back(std::move(work));
|
||
|
mQueueCondition.notify_all();
|
||
|
}
|
||
|
ALOGV("render loop finished");
|
||
|
});
|
||
|
|
||
|
long numFrames = 0;
|
||
|
mLinearPool.reset(new C2PooledBlockPool(mAllocIon, mLinearPoolId++));
|
||
|
|
||
|
for (;;) {
|
||
|
size_t size = 0u;
|
||
|
void *data = nullptr;
|
||
|
int64_t timestamp = 0u;
|
||
|
MediaBufferBase *buffer = nullptr;
|
||
|
sp<ABuffer> csd;
|
||
|
if (csd0 != nullptr) {
|
||
|
csd = csd0;
|
||
|
csd0 = nullptr;
|
||
|
} else if (csd1 != nullptr) {
|
||
|
csd = csd1;
|
||
|
csd1 = nullptr;
|
||
|
} else {
|
||
|
status_t err = source->read(&buffer);
|
||
|
if (err != OK) {
|
||
|
CHECK(buffer == nullptr);
|
||
|
|
||
|
if (err == INFO_FORMAT_CHANGED) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
MetaDataBase &meta = buffer->meta_data();
|
||
|
CHECK(meta.findInt64(kKeyTime, ×tamp));
|
||
|
|
||
|
size = buffer->size();
|
||
|
data = buffer->data();
|
||
|
}
|
||
|
|
||
|
if (csd != nullptr) {
|
||
|
size = csd->size();
|
||
|
data = csd->data();
|
||
|
}
|
||
|
|
||
|
// Prepare C2Work
|
||
|
|
||
|
std::unique_ptr<C2Work> work;
|
||
|
while (!work) {
|
||
|
ULock l(mQueueLock);
|
||
|
if (!mWorkQueue.empty()) {
|
||
|
work.swap(mWorkQueue.front());
|
||
|
mWorkQueue.pop_front();
|
||
|
} else {
|
||
|
mQueueCondition.wait_for(l, 100ms);
|
||
|
}
|
||
|
}
|
||
|
work->input.flags = (C2FrameData::flags_t)0;
|
||
|
work->input.ordinal.timestamp = timestamp;
|
||
|
work->input.ordinal.frameIndex = numFrames;
|
||
|
|
||
|
std::shared_ptr<C2LinearBlock> block;
|
||
|
mLinearPool->fetchLinearBlock(
|
||
|
size,
|
||
|
{ C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE },
|
||
|
&block);
|
||
|
C2WriteView view = block->map().get();
|
||
|
if (view.error() != C2_OK) {
|
||
|
fprintf(stderr, "C2LinearBlock::map() failed : %d", view.error());
|
||
|
break;
|
||
|
}
|
||
|
memcpy(view.base(), data, size);
|
||
|
|
||
|
work->input.buffers.clear();
|
||
|
work->input.buffers.emplace_back(new LinearBuffer(block));
|
||
|
work->worklets.clear();
|
||
|
work->worklets.emplace_back(new C2Worklet);
|
||
|
|
||
|
std::list<std::unique_ptr<C2Work>> items;
|
||
|
items.push_back(std::move(work));
|
||
|
|
||
|
ALOGV("Frame #%ld size = %zu", numFrames, size);
|
||
|
// DO THE DECODING
|
||
|
component->queue_nb(&items);
|
||
|
|
||
|
if (buffer) {
|
||
|
buffer->release();
|
||
|
buffer = nullptr;
|
||
|
}
|
||
|
|
||
|
++numFrames;
|
||
|
}
|
||
|
ALOGV("main loop finished");
|
||
|
source->stop();
|
||
|
running.store(false);
|
||
|
surfaceThread.join();
|
||
|
|
||
|
component->release();
|
||
|
printf("\n");
|
||
|
}
|
||
|
|
||
|
} // namespace
|
||
|
|
||
|
static void usage(const char *me) {
|
||
|
fprintf(stderr, "usage: %s [options] [input_filename]\n", me);
|
||
|
fprintf(stderr, " -h(elp)\n");
|
||
|
}
|
||
|
|
||
|
int main(int argc, char **argv) {
|
||
|
android::ProcessState::self()->startThreadPool();
|
||
|
|
||
|
int res;
|
||
|
while ((res = getopt(argc, argv, "h")) >= 0) {
|
||
|
switch (res) {
|
||
|
case 'h':
|
||
|
default:
|
||
|
{
|
||
|
usage(argv[0]);
|
||
|
exit(1);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
argc -= optind;
|
||
|
argv += optind;
|
||
|
|
||
|
if (argc < 1) {
|
||
|
fprintf(stderr, "No input file specified\n");
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
status_t err = OK;
|
||
|
SimplePlayer player;
|
||
|
|
||
|
for (int k = 0; k < argc && err == OK; ++k) {
|
||
|
const char *filename = argv[k];
|
||
|
|
||
|
sp<DataSource> dataSource =
|
||
|
DataSourceFactory::getInstance()->CreateFromURI(nullptr /* httpService */, filename);
|
||
|
|
||
|
if (strncasecmp(filename, "sine:", 5) && dataSource == nullptr) {
|
||
|
fprintf(stderr, "Unable to create data source.\n");
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
Vector<sp<IMediaSource> > mediaSources;
|
||
|
sp<IMediaSource> mediaSource;
|
||
|
|
||
|
sp<IMediaExtractor> extractor = MediaExtractorFactory::Create(dataSource);
|
||
|
|
||
|
if (extractor == nullptr) {
|
||
|
fprintf(stderr, "could not create extractor.\n");
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
sp<MetaData> meta = extractor->getMetaData();
|
||
|
|
||
|
if (meta != nullptr) {
|
||
|
const char *mime;
|
||
|
if (!meta->findCString(kKeyMIMEType, &mime)) {
|
||
|
fprintf(stderr, "extractor did not provide MIME type.\n");
|
||
|
return -1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
size_t numTracks = extractor->countTracks();
|
||
|
|
||
|
size_t i;
|
||
|
for (i = 0; i < numTracks; ++i) {
|
||
|
meta = extractor->getTrackMetaData(i, 0);
|
||
|
|
||
|
if (meta == nullptr) {
|
||
|
break;
|
||
|
}
|
||
|
const char *mime;
|
||
|
meta->findCString(kKeyMIMEType, &mime);
|
||
|
|
||
|
// TODO: allowing AVC only for the time being
|
||
|
if (!strncasecmp(mime, "video/avc", 9)) {
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
meta = nullptr;
|
||
|
}
|
||
|
|
||
|
if (meta == nullptr) {
|
||
|
fprintf(stderr, "No AVC track found.\n");
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
mediaSource = extractor->getTrack(i);
|
||
|
if (mediaSource == nullptr) {
|
||
|
fprintf(stderr, "skip NULL track %zu, total tracks %zu.\n", i, numTracks);
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
player.play(mediaSource);
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|