/* * Copyright (C) 2019 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. */ #define LOG_TAG "android.hardware.tv.tuner@1.0-Dvr" #include "Dvr.h" #include namespace android { namespace hardware { namespace tv { namespace tuner { namespace V1_0 { namespace implementation { #define WAIT_TIMEOUT 3000000000 Dvr::Dvr() {} Dvr::Dvr(DvrType type, uint32_t bufferSize, const sp& cb, sp demux) { mType = type; mBufferSize = bufferSize; mCallback = cb; mDemux = demux; } Dvr::~Dvr() {} Return Dvr::getQueueDesc(getQueueDesc_cb _hidl_cb) { ALOGV("%s", __FUNCTION__); _hidl_cb(Result::SUCCESS, *mDvrMQ->getDesc()); return Void(); } Return Dvr::configure(const DvrSettings& settings) { ALOGV("%s", __FUNCTION__); mDvrSettings = settings; mDvrConfigured = true; return Result::SUCCESS; } Return Dvr::attachFilter(const sp& filter) { ALOGV("%s", __FUNCTION__); uint32_t filterId; Result status; filter->getId([&](Result result, uint32_t id) { filterId = id; status = result; }); if (status != Result::SUCCESS) { return status; } // TODO check if the attached filter is a record filter if (!mDemux->attachRecordFilter(filterId)) { return Result::INVALID_ARGUMENT; } return Result::SUCCESS; } Return Dvr::detachFilter(const sp& filter) { ALOGV("%s", __FUNCTION__); uint32_t filterId; Result status; filter->getId([&](Result result, uint32_t id) { filterId = id; status = result; }); if (status != Result::SUCCESS) { return status; } if (!mDemux->detachRecordFilter(filterId)) { return Result::INVALID_ARGUMENT; } return Result::SUCCESS; } Return Dvr::start() { ALOGV("%s", __FUNCTION__); if (!mCallback) { return Result::NOT_INITIALIZED; } if (!mDvrConfigured) { return Result::INVALID_STATE; } if (mType == DvrType::PLAYBACK) { pthread_create(&mDvrThread, NULL, __threadLoopPlayback, this); pthread_setname_np(mDvrThread, "playback_waiting_loop"); } else if (mType == DvrType::RECORD) { mRecordStatus = RecordStatus::DATA_READY; mDemux->setIsRecording(mType == DvrType::RECORD); } // TODO start another thread to send filter status callback to the framework return Result::SUCCESS; } Return Dvr::stop() { ALOGV("%s", __FUNCTION__); mDvrThreadRunning = false; std::lock_guard lock(mDvrThreadLock); mIsRecordStarted = false; mDemux->setIsRecording(false); return Result::SUCCESS; } Return Dvr::flush() { ALOGV("%s", __FUNCTION__); mRecordStatus = RecordStatus::DATA_READY; return Result::SUCCESS; } Return Dvr::close() { ALOGV("%s", __FUNCTION__); return Result::SUCCESS; } bool Dvr::createDvrMQ() { ALOGV("%s", __FUNCTION__); // Create a synchronized FMQ that supports blocking read/write std::unique_ptr tmpDvrMQ = std::unique_ptr(new (std::nothrow) DvrMQ(mBufferSize, true)); if (!tmpDvrMQ->isValid()) { ALOGW("[Dvr] Failed to create FMQ of DVR"); return false; } mDvrMQ = std::move(tmpDvrMQ); if (EventFlag::createEventFlag(mDvrMQ->getEventFlagWord(), &mDvrEventFlag) != OK) { return false; } return true; } EventFlag* Dvr::getDvrEventFlag() { return mDvrEventFlag; } void* Dvr::__threadLoopPlayback(void* user) { Dvr* const self = static_cast(user); self->playbackThreadLoop(); return 0; } void Dvr::playbackThreadLoop() { ALOGD("[Dvr] playback threadLoop start."); std::lock_guard lock(mDvrThreadLock); mDvrThreadRunning = true; while (mDvrThreadRunning) { uint32_t efState = 0; status_t status = mDvrEventFlag->wait(static_cast(DemuxQueueNotifyBits::DATA_READY), &efState, WAIT_TIMEOUT, true /* retry on spurious wake */); if (status != OK) { ALOGD("[Dvr] wait for data ready on the playback FMQ"); continue; } // Our current implementation filter the data and write it into the filter FMQ immediately // after the DATA_READY from the VTS/framework if (!readPlaybackFMQ(false /*isVirtualFrontend*/, false /*isRecording*/) || !startFilterDispatcher(false /*isVirtualFrontend*/, false /*isRecording*/)) { ALOGE("[Dvr] playback data failed to be filtered. Ending thread"); break; } maySendPlaybackStatusCallback(); } mDvrThreadRunning = false; ALOGD("[Dvr] playback thread ended."); } void Dvr::maySendPlaybackStatusCallback() { std::lock_guard lock(mPlaybackStatusLock); int availableToRead = mDvrMQ->availableToRead(); int availableToWrite = mDvrMQ->availableToWrite(); PlaybackStatus newStatus = checkPlaybackStatusChange(availableToWrite, availableToRead, mDvrSettings.playback().highThreshold, mDvrSettings.playback().lowThreshold); if (mPlaybackStatus != newStatus) { mCallback->onPlaybackStatus(newStatus); mPlaybackStatus = newStatus; } } PlaybackStatus Dvr::checkPlaybackStatusChange(uint32_t availableToWrite, uint32_t availableToRead, uint32_t highThreshold, uint32_t lowThreshold) { if (availableToWrite == 0) { return PlaybackStatus::SPACE_FULL; } else if (availableToRead > highThreshold) { return PlaybackStatus::SPACE_ALMOST_FULL; } else if (availableToRead < lowThreshold) { return PlaybackStatus::SPACE_ALMOST_EMPTY; } else if (availableToRead == 0) { return PlaybackStatus::SPACE_EMPTY; } return mPlaybackStatus; } bool Dvr::readPlaybackFMQ(bool isVirtualFrontend, bool isRecording) { // Read playback data from the input FMQ int size = mDvrMQ->availableToRead(); int playbackPacketSize = mDvrSettings.playback().packetSize; vector dataOutputBuffer; dataOutputBuffer.resize(playbackPacketSize); // Dispatch the packet to the PID matching filter output buffer for (int i = 0; i < size / playbackPacketSize; i++) { if (!mDvrMQ->read(dataOutputBuffer.data(), playbackPacketSize)) { return false; } if (isVirtualFrontend) { if (isRecording) { mDemux->sendFrontendInputToRecord(dataOutputBuffer); } else { mDemux->startBroadcastTsFilter(dataOutputBuffer); } } else { startTpidFilter(dataOutputBuffer); } } return true; } void Dvr::startTpidFilter(vector data) { std::map>::iterator it; for (it = mFilters.begin(); it != mFilters.end(); it++) { uint16_t pid = ((data[1] & 0x1f) << 8) | ((data[2] & 0xff)); if (DEBUG_DVR) { ALOGW("[Dvr] start ts filter pid: %d", pid); } if (pid == mDemux->getFilterTpid(it->first)) { mDemux->updateFilterOutput(it->first, data); } } } bool Dvr::startFilterDispatcher(bool isVirtualFrontend, bool isRecording) { if (isVirtualFrontend) { if (isRecording) { return mDemux->startRecordFilterDispatcher(); } else { return mDemux->startBroadcastFilterDispatcher(); } } std::map>::iterator it; // Handle the output data per filter type for (it = mFilters.begin(); it != mFilters.end(); it++) { if (mDemux->startFilterHandler(it->first) != Result::SUCCESS) { return false; } } return true; } bool Dvr::writeRecordFMQ(const std::vector& data) { std::lock_guard lock(mWriteLock); if (mRecordStatus == RecordStatus::OVERFLOW) { ALOGW("[Dvr] stops writing and wait for the client side flushing."); return true; } if (mDvrMQ->write(data.data(), data.size())) { mDvrEventFlag->wake(static_cast(DemuxQueueNotifyBits::DATA_READY)); maySendRecordStatusCallback(); return true; } maySendRecordStatusCallback(); return false; } void Dvr::maySendRecordStatusCallback() { std::lock_guard lock(mRecordStatusLock); int availableToRead = mDvrMQ->availableToRead(); int availableToWrite = mDvrMQ->availableToWrite(); RecordStatus newStatus = checkRecordStatusChange(availableToWrite, availableToRead, mDvrSettings.record().highThreshold, mDvrSettings.record().lowThreshold); if (mRecordStatus != newStatus) { mCallback->onRecordStatus(newStatus); mRecordStatus = newStatus; } } RecordStatus Dvr::checkRecordStatusChange(uint32_t availableToWrite, uint32_t availableToRead, uint32_t highThreshold, uint32_t lowThreshold) { if (availableToWrite == 0) { return DemuxFilterStatus::OVERFLOW; } else if (availableToRead > highThreshold) { return DemuxFilterStatus::HIGH_WATER; } else if (availableToRead < lowThreshold) { return DemuxFilterStatus::LOW_WATER; } return mRecordStatus; } bool Dvr::addPlaybackFilter(uint32_t filterId, sp filter) { mFilters[filterId] = filter; return true; } bool Dvr::removePlaybackFilter(uint32_t filterId) { mFilters.erase(filterId); return true; } } // namespace implementation } // namespace V1_0 } // namespace tuner } // namespace tv } // namespace hardware } // namespace android