// Copyright 2017 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. //#define LOG_NDEBUG 0 #define LOG_TAG "codec2" #include <C2VDAComponent.h> #include <C2Buffer.h> #include <C2BufferPriv.h> #include <C2Component.h> #include <C2PlatformSupport.h> #include <C2Work.h> #include <SimpleC2Interface.h> #include <binder/IServiceManager.h> #include <binder/ProcessState.h> #include <gui/GLConsumer.h> #include <gui/IProducerListener.h> #include <gui/Surface.h> #include <gui/SurfaceComposerClient.h> #include <media/DataSource.h> #include <media/ICrypto.h> #include <media/IMediaHTTPService.h> #include <media/MediaSource.h> #include <media/stagefright/DataSourceFactory.h> #include <media/stagefright/MediaDefs.h> #include <media/stagefright/MediaErrors.h> #include <media/stagefright/MediaExtractor.h> #include <media/stagefright/MediaExtractorFactory.h> #include <media/stagefright/MetaData.h> #include <media/stagefright/Utils.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 <fcntl.h> #include <inttypes.h> #include <stdlib.h> #include <string.h> #include <sys/stat.h> #include <sys/time.h> #include <sys/types.h> #include <thread> using namespace android; using namespace std::chrono_literals; namespace { const std::string kH264DecoderName = "c2.vda.avc.decoder"; const std::string kVP8DecoderName = "c2.vda.vp8.decoder"; const std::string kVP9DecoderName = "c2.vda.vp9.decoder"; const int kWidth = 416; const int kHeight = 240; // BigBuckBunny.mp4 //const int kWidth = 560; //const int kHeight = 320; // small.mp4 const std::string kComponentName = kH264DecoderName; class C2VDALinearBuffer : public C2Buffer { public: explicit C2VDALinearBuffer(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); status_t play(const sp<IMediaSource>& source); private: typedef std::unique_lock<std::mutex> ULock; enum { kInputBufferCount = 8, kDefaultInputBufferSize = 1024 * 1024, }; std::shared_ptr<Listener> mListener; sp<IProducerListener> mProducerListener; // Allocators std::shared_ptr<C2Allocator> mLinearAlloc; std::shared_ptr<C2BlockPool> mLinearBlockPool; 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 DummyProducerListener), mComposerClient(new SurfaceComposerClient) { CHECK_EQ(mComposerClient->initCheck(), OK); std::shared_ptr<C2AllocatorStore> store = GetCodec2PlatformAllocatorStore(); CHECK_EQ(store->fetchAllocator(C2AllocatorStore::DEFAULT_LINEAR, &mLinearAlloc), C2_OK); mLinearBlockPool = std::make_shared<C2BasicLinearBlockPool>(mLinearAlloc); mControl = mComposerClient->createSurface(String8("A Surface"), kWidth, kHeight, HAL_PIXEL_FORMAT_YV12); 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) { (void)component; ULock l(mProcessedLock); for (auto& item : workItems) { mProcessedWork.emplace_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 } status_t SimplePlayer::play(const sp<IMediaSource>& source) { std::deque<sp<ABuffer>> csds; if (kComponentName == kH264DecoderName) { sp<AMessage> format; (void)convertMetaDataToMessage(source->getFormat(), &format); csds.resize(2); format->findBuffer("csd-0", &csds[0]); format->findBuffer("csd-1", &csds[1]); } status_t err = source->start(); if (err != OK) { ALOGE("source returned error %d (0x%08x)", err, err); fprintf(stderr, "source returned error %d (0x%08x)\n", err, err); return err; } std::shared_ptr<C2Component> component(std::make_shared<C2VDAComponent>( kComponentName, 0, std::make_shared<C2ReflectorHelper>())); component->setListener_vb(mListener, C2_DONT_BLOCK); std::unique_ptr<C2PortBlockPoolsTuning::output> pools = C2PortBlockPoolsTuning::output::AllocUnique( {static_cast<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(); mProcessedWork.clear(); for (int i = 0; i < kInputBufferCount; ++i) { mWorkQueue.emplace_back(new C2Work); } std::atomic_bool running(true); std::thread surfaceThread([this, &running]() { const sp<IGraphicBufferProducer>& igbp = mSurface->getIGraphicBufferProducer(); std::vector<std::shared_ptr<C2Buffer>> pendingDisplayBuffers; pendingDisplayBuffers.resize(BufferQueue::NUM_BUFFER_SLOTS); while (running) { std::unique_ptr<C2Work> work; { ULock l(mProcessedLock); if (mProcessedWork.empty()) { mProcessedCondition.wait_for(l, 100ms); if (mProcessedWork.empty()) { continue; } } work = std::move(mProcessedWork.front()); mProcessedWork.pop_front(); } CHECK_EQ(work->worklets.size(), 1u); if (work->worklets.front()->output.buffers.size() == 1u) { int slot; sp<Fence> fence; std::shared_ptr<C2Buffer> output = work->worklets.front()->output.buffers[0]; C2ConstGraphicBlock graphic_block = output->data().graphicBlocks().front(); sp<GraphicBuffer> buffer(new GraphicBuffer( graphic_block.handle(), GraphicBuffer::CLONE_HANDLE, graphic_block.width(), graphic_block.height(), HAL_PIXEL_FORMAT_YCbCr_420_888, 1 /* layerCount */, GRALLOC_USAGE_SW_READ_OFTEN, graphic_block.width())); CHECK_EQ(igbp->attachBuffer(&slot, buffer), OK); ALOGV("attachBuffer slot=%d ts=%lld", slot, (work->worklets.front()->output.ordinal.timestamp * 1000ll).peekll()); IGraphicBufferProducer::QueueBufferInput qbi( (work->worklets.front()->output.ordinal.timestamp * 1000ll).peekll(), false, HAL_DATASPACE_UNKNOWN, Rect(graphic_block.width(), graphic_block.height()), NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW, 0, Fence::NO_FENCE, 0); IGraphicBufferProducer::QueueBufferOutput qbo; CHECK_EQ(igbp->queueBuffer(slot, qbi, &qbo), OK); // If the slot is reused then we can make sure the previous graphic buffer is // displayed (consumed), so we could returned the graphic buffer. pendingDisplayBuffers[slot].swap(output); } bool eos = work->worklets.front()->output.flags & C2FrameData::FLAG_END_OF_STREAM; // input buffer should be reset in component side. CHECK_EQ(work->input.buffers.size(), 1u); CHECK(work->input.buffers.front() == nullptr); work->worklets.clear(); work->workletsProcessed = 0; if (eos) { running.store(false); // stop the thread } ULock l(mQueueLock); mWorkQueue.emplace_back(std::move(work)); mQueueCondition.notify_all(); } }); long numFrames = 0; for (;;) { size_t size = 0u; void* data = nullptr; int64_t timestamp = 0u; MediaBufferBase* buffer = nullptr; sp<ABuffer> csd; if (!csds.empty()) { csd = std::move(csds.front()); csds.pop_front(); size = csd->size(); data = csd->data(); } 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(); } // Prepare C2Work std::unique_ptr<C2Work> work; while (!work) { ULock l(mQueueLock); if (!mWorkQueue.empty()) { work = std::move(mWorkQueue.front()); mWorkQueue.pop_front(); } else { mQueueCondition.wait_for(l, 100ms); } } work->input.flags = static_cast<C2FrameData::flags_t>(0); work->input.ordinal.timestamp = timestamp; work->input.ordinal.frameIndex = numFrames; // Allocate input buffer. std::shared_ptr<C2LinearBlock> block; mLinearBlockPool->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\n", view.error()); break; } memcpy(view.base(), data, size); work->input.buffers.clear(); work->input.buffers.emplace_back(new C2VDALinearBuffer(std::move(block))); work->worklets.clear(); work->worklets.emplace_back(new C2Worklet); std::list<std::unique_ptr<C2Work>> items; items.push_back(std::move(work)); // DO THE DECODING component->queue_nb(&items); if (buffer) { buffer->release(); } ++numFrames; } component->drain_nb(C2Component::DRAIN_COMPONENT_WITH_EOS); surfaceThread.join(); source->stop(); component->stop(); printf("finished...\n"); return OK; } } // namespace static bool getMediaSourceFromFile(const char* filename, sp<IMediaSource>* source) { source->clear(); sp<DataSource> dataSource = DataSourceFactory::CreateFromURI(nullptr /* httpService */, filename); if (dataSource == nullptr) { fprintf(stderr, "Unable to create data source.\n"); return false; } sp<IMediaExtractor> extractor = MediaExtractorFactory::Create(dataSource); if (extractor == nullptr) { fprintf(stderr, "could not create extractor.\n"); return false; } std::string expectedMime; if (kComponentName == kH264DecoderName) { expectedMime = "video/avc"; } else if (kComponentName == kVP8DecoderName) { expectedMime = "video/x-vnd.on2.vp8"; } else if (kComponentName == kVP9DecoderName) { expectedMime = "video/x-vnd.on2.vp9"; } else { fprintf(stderr, "unrecognized component name: %s\n", kComponentName.c_str()); return false; } for (size_t i = 0, numTracks = extractor->countTracks(); i < numTracks; ++i) { sp<MetaData> meta = extractor->getTrackMetaData(i, MediaExtractor::kIncludeExtensiveMetaData); if (meta == nullptr) { continue; } const char* mime; meta->findCString(kKeyMIMEType, &mime); if (!strcasecmp(mime, expectedMime.c_str())) { *source = extractor->getTrack(i); if (*source == nullptr) { fprintf(stderr, "It's nullptr track for track %zu.\n", i); return false; } return true; } } fprintf(stderr, "No track found.\n"); return false; } 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; } SimplePlayer player; for (int k = 0; k < argc; ++k) { sp<IMediaSource> mediaSource; if (!getMediaSourceFromFile(argv[k], &mediaSource)) { fprintf(stderr, "Unable to get media source from file: %s\n", argv[k]); return -1; } if (player.play(mediaSource) != OK) { fprintf(stderr, "Player failed to play media source: %s\n", argv[k]); return -1; } } return 0; }