// Copyright (c) 2012-2017 VideoStitch SAS // Copyright (c) 2018 stitchEm #include "controllerInputFrames.hpp" #include "gpu/allocator.hpp" #include "gpu/buffer.hpp" #include "gpu/memcpy.hpp" #include "image/unpack.hpp" namespace VideoStitch { namespace Core { namespace { static Status videoLoadStatus(const Input::ReadStatus& readStatus) { switch (readStatus.getCode()) { case Input::ReadStatusCode::Ok: return Status::OK(); case Input::ReadStatusCode::ErrorWithStatus: return Status{Origin::Input, ErrType::RuntimeError, "Could not load input frames", readStatus.getStatus()}; case Input::ReadStatusCode::EndOfFile: return Status{Origin::Input, ErrType::RuntimeError, "Could not load input frame, reader reported end of stream"}; case Input::ReadStatusCode::TryAgain: return Status{Origin::Input, ErrType::RuntimeError, "Could not load input frame, reader starved"}; } assert(false); return Status{Origin::Input, ErrType::ImplementationError, "Could not load input frames, unknown error code"}; } } // namespace template <> Status ControllerInputFrames::processFrame(Buffer readerFrame, GPU::HostBuffer readbackDestination, readerid_t readerID) { GPU::Buffer tmp; switch (readerFrame.addressSpace()) { case Host: FAIL_RETURN(GPU::memcpyAsync(devBuffer, readerFrame.hostBuffer(), readerFrame.hostBuffer().byteSize(), stream)); tmp = devBuffer; break; case Device: tmp = readerFrame.deviceBuffer(); break; } auto reader = readerController->getReader(readerID); auto spec = reader->getSpec(); FAIL_RETURN(Image::unpackCommonPixelFormat(spec.format, *surf->pimpl->surface, tmp.as_const(), spec.width, spec.height, stream)); FAIL_RETURN(GPU::memcpyAsync(readbackDestination.hostPtr(), *surf->pimpl->surface, stream)); return stream.synchronize(); } template <> Status ControllerInputFrames::processFrame( Buffer readerFrame, GPU::HostBuffer readbackDestination, readerid_t readerID) { GPU::Buffer tmp; switch (readerFrame.addressSpace()) { case Host: FAIL_RETURN(GPU::memcpyAsync(devBuffer, readerFrame.hostBuffer(), readerFrame.hostBuffer().byteSize(), stream)); tmp = devBuffer; break; case Device: tmp = readerFrame.deviceBuffer(); break; } auto reader = readerController->getReader(readerID); auto spec = reader->getSpec(); FAIL_RETURN(Image::unpackCommonPixelFormat(spec.format, *surf->pimpl->surface, tmp.as_const(), spec.width, spec.height, stream)); // convert from RGBA into grayscale Image::unpackGrayscale(grayscale, *surf->pimpl->surface, spec.width, spec.height, stream); FAIL_RETURN(GPU::memcpyAsync(readbackDestination, grayscale, stream)); return stream.synchronize(); } template Status ControllerInputFrames::load( std::map>>& processedFrames, mtime_t* date) { std::map framesFromReader; processedFrames.clear(); Audio::audioBlocks_t audioBlocks; mtime_t tempDate = 0; Input::MetadataChunk metadata; auto loadStatus = readerController->load(tempDate, framesFromReader, audioBlocks, metadata); FAIL_RETURN(videoLoadStatus(std::get<0>(loadStatus))); if (date) { *date = tempDate; } for (auto inputFrame : framesFromReader) { if (inputFrame.second.status.ok()) { auto processStatus = processFrame(inputFrame.second.frame, readbackFrames[inputFrame.first], inputFrame.first); processedFrames.insert({inputFrame.first, {processStatus, readbackFrames[inputFrame.first]}}); } else { processedFrames.insert({inputFrame.first, videoLoadStatus(inputFrame.second.status)}); } } readerController->releaseBuffer(framesFromReader); return Status::OK(); } template Potential> ControllerInputFrames::create(const Core::PanoDefinition* pano) { auto cif = new ControllerInputFrames(); Status initStatus = cif->init(pano); if (!initStatus.ok()) { delete cif; return initStatus; } return cif; } template Status ControllerInputFrames::init(const Core::PanoDefinition* pano) { FAIL_RETURN(GPU::useDefaultBackendDevice()); auto potStream = GPU::Stream::create(); FAIL_RETURN(potStream.status()); stream = potStream.value(); std::unique_ptr audioPipe(Core::AudioPipeDefinition::createDefault()); auto potReaderController = ReaderController::create(*pano, *audioPipe, new Input::DefaultReaderFactory(0, -1), 0); if (!potReaderController.ok()) { readerController = nullptr; return potReaderController.status(); } readerController = potReaderController.release(); FAIL_RETURN(readerController->setupReaders()); // Allocate buffers for readback. int64_t height = readerController->getReaderSpec(0).height; int64_t width = readerController->getReaderSpec(0).width; for (int k = 0; k < (int)pano->numInputs(); ++k) { if (!pano->getInput(k).getIsVideoEnabled()) { continue; } const Input::VideoReader::Spec& spec = readerController->getReaderSpec(k); auto potReadback = GPU::HostBuffer::allocate(spec.width * spec.height, "ControllerInputFrames"); FAIL_RETURN(potReadback.status()); readbackFrames.push_back(potReadback.value()); if ((spec.height != height) || (spec.width != width)) { return {Origin::Input, ErrType::InvalidConfiguration, "All inputs must have the same size"}; } } auto potDevBuffer = GPU::Buffer::allocate(readerController->getReaderSpec(0).frameDataSize, "ControllerInputFrames"); FAIL_RETURN(potDevBuffer.status()); devBuffer = potDevBuffer.value(); PotentialValue potGray = GPU::Buffer2D::allocate( readerController->getReaderSpec(0).width, readerController->getReaderSpec(0).height, "Grayscale image"); FAIL_RETURN(potGray.status()); grayscale = potGray.value(); Potential potSurf = OffscreenAllocator::createSourceSurface(width, height, "ControllerInputFrames"); FAIL_RETURN(potSurf.status()); surf = potSurf.release(); return Status::OK(); } template ControllerInputFrames::~ControllerInputFrames() { if (readerController) { readerController->cleanReaders(); delete readerController; } if (devBuffer.wasAllocated()) { devBuffer.release(); } grayscale.release(); if (surf) { delete surf; } for (auto hostBuf : readbackFrames) { if (hostBuf.byteSize()) { hostBuf.release(); } } stream.destroy(); } template Status ControllerInputFrames::seek(frameid_t fr) { return readerController->seekFrame(fr); } template class ControllerInputFrames; template class ControllerInputFrames; } // namespace Core } // namespace VideoStitch