| // Copyright 2020 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 "EncodeHelpers" | |
| #include <v4l2_codec2/common/EncodeHelpers.h> | |
| #include <linux/v4l2-controls.h> | |
| #include <C2AllocatorGralloc.h> | |
| #include <cutils/native_handle.h> | |
| #include <ui/GraphicBuffer.h> | |
| #include <utils/Log.h> | |
| #include <v4l2_codec2/common/NalParser.h> | |
| namespace android { | |
| namespace { | |
| // Android frameworks needs 4 bytes start code. | |
| constexpr uint8_t kH264StartCode[] = {0x00, 0x00, 0x00, 0x01}; | |
| constexpr size_t kH264StartCodeSize = 4; | |
| // Copy an H.264 NAL unit with size |srcSize| (without a start code) into a buffer with size | |
| // |dstSize|. An H.264 start code is prepended to the NAL unit. After copying |dst| is adjusted to | |
| // point to the address immediately following the copied data, and the |dstSize| is updated to | |
| // reflect the remaining destination buffer size. | |
| bool copyNALUPrependingStartCode(const uint8_t* src, size_t srcSize, uint8_t** dst, | |
| size_t* dstSize) { | |
| size_t naluSize = srcSize + kH264StartCodeSize; | |
| if (naluSize > *dstSize) { | |
| ALOGE("Couldn't copy NAL unit, not enough space in destination buffer"); | |
| return false; | |
| } | |
| memcpy(*dst, kH264StartCode, kH264StartCodeSize); | |
| memcpy(*dst + kH264StartCodeSize, src, srcSize); | |
| *dst += naluSize; | |
| *dstSize -= naluSize; | |
| return true; | |
| } | |
| } // namespace | |
| uint8_t c2LevelToV4L2Level(C2Config::level_t level) { | |
| switch (level) { | |
| case C2Config::LEVEL_AVC_1: | |
| return V4L2_MPEG_VIDEO_H264_LEVEL_1_0; | |
| case C2Config::LEVEL_AVC_1B: | |
| return V4L2_MPEG_VIDEO_H264_LEVEL_1B; | |
| case C2Config::LEVEL_AVC_1_1: | |
| return V4L2_MPEG_VIDEO_H264_LEVEL_1_1; | |
| case C2Config::LEVEL_AVC_1_2: | |
| return V4L2_MPEG_VIDEO_H264_LEVEL_1_2; | |
| case C2Config::LEVEL_AVC_1_3: | |
| return V4L2_MPEG_VIDEO_H264_LEVEL_1_3; | |
| case C2Config::LEVEL_AVC_2: | |
| return V4L2_MPEG_VIDEO_H264_LEVEL_2_0; | |
| case C2Config::LEVEL_AVC_2_1: | |
| return V4L2_MPEG_VIDEO_H264_LEVEL_2_1; | |
| case C2Config::LEVEL_AVC_2_2: | |
| return V4L2_MPEG_VIDEO_H264_LEVEL_2_2; | |
| case C2Config::LEVEL_AVC_3: | |
| return V4L2_MPEG_VIDEO_H264_LEVEL_3_0; | |
| case C2Config::LEVEL_AVC_3_1: | |
| return V4L2_MPEG_VIDEO_H264_LEVEL_3_1; | |
| case C2Config::LEVEL_AVC_3_2: | |
| return V4L2_MPEG_VIDEO_H264_LEVEL_3_2; | |
| case C2Config::LEVEL_AVC_4: | |
| return V4L2_MPEG_VIDEO_H264_LEVEL_4_0; | |
| case C2Config::LEVEL_AVC_4_1: | |
| return V4L2_MPEG_VIDEO_H264_LEVEL_4_1; | |
| case C2Config::LEVEL_AVC_4_2: | |
| return V4L2_MPEG_VIDEO_H264_LEVEL_4_2; | |
| case C2Config::LEVEL_AVC_5: | |
| return V4L2_MPEG_VIDEO_H264_LEVEL_5_0; | |
| case C2Config::LEVEL_AVC_5_1: | |
| return V4L2_MPEG_VIDEO_H264_LEVEL_5_1; | |
| default: | |
| ALOGE("Unrecognizable C2 level (value = 0x%x)...", level); | |
| return 0; | |
| } | |
| } | |
| android_ycbcr getGraphicBlockInfo(const C2ConstGraphicBlock& block) { | |
| uint32_t width, height, format, stride, igbp_slot, generation; | |
| uint64_t usage, igbp_id; | |
| android::_UnwrapNativeCodec2GrallocMetadata(block.handle(), &width, &height, &format, &usage, | |
| &stride, &generation, &igbp_id, &igbp_slot); | |
| native_handle_t* grallocHandle = android::UnwrapNativeCodec2GrallocHandle(block.handle()); | |
| sp<GraphicBuffer> buf = new GraphicBuffer(grallocHandle, GraphicBuffer::CLONE_HANDLE, width, | |
| height, format, 1, usage, stride); | |
| native_handle_delete(grallocHandle); | |
| // Pass SW flag so that ARCVM returns the guest buffer dimensions instead | |
| // of the host buffer dimensions. This means we will have to convert the | |
| // return value from ptrs to buffer offsets ourselves. | |
| android_ycbcr ycbcr = {}; | |
| int32_t status = buf->lockYCbCr(GRALLOC_USAGE_SW_READ_OFTEN, &ycbcr); | |
| if (status != OK) ALOGE("lockYCbCr is failed: %d", (int)status); | |
| buf->unlock(); | |
| uintptr_t y = reinterpret_cast<uintptr_t>(ycbcr.y); | |
| ycbcr.y = nullptr; | |
| ycbcr.cb = reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(ycbcr.cb) - y); | |
| ycbcr.cr = reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(ycbcr.cr) - y); | |
| return ycbcr; | |
| } | |
| bool extractSPSPPS(const uint8_t* data, size_t length, std::vector<uint8_t>* sps, | |
| std::vector<uint8_t>* pps) { | |
| bool foundSPS = false; | |
| bool foundPPS = false; | |
| NalParser parser(data, length); | |
| while (!(foundSPS && foundPPS) && parser.locateNextNal()) { | |
| switch (parser.type()) { | |
| case NalParser::kSPSType: | |
| sps->resize(parser.length()); | |
| memcpy(sps->data(), parser.data(), parser.length()); | |
| foundSPS = true; | |
| break; | |
| case NalParser::kPPSType: | |
| pps->resize(parser.length()); | |
| memcpy(pps->data(), parser.data(), parser.length()); | |
| foundPPS = true; | |
| break; | |
| } | |
| } | |
| return foundSPS && foundPPS; | |
| } | |
| bool extractCSDInfo(std::unique_ptr<C2StreamInitDataInfo::output>* const csd, const uint8_t* data, | |
| size_t length) { | |
| csd->reset(); | |
| std::vector<uint8_t> sps; | |
| std::vector<uint8_t> pps; | |
| if (!extractSPSPPS(data, length, &sps, &pps)) { | |
| return false; | |
| } | |
| size_t configDataLength = sps.size() + pps.size() + (2u * kH264StartCodeSize); | |
| ALOGV("Extracted codec config data: length=%zu", configDataLength); | |
| *csd = C2StreamInitDataInfo::output::AllocUnique(configDataLength, 0u); | |
| uint8_t* csdBuffer = (*csd)->m.value; | |
| return copyNALUPrependingStartCode(sps.data(), sps.size(), &csdBuffer, &configDataLength) && | |
| copyNALUPrependingStartCode(pps.data(), pps.size(), &csdBuffer, &configDataLength); | |
| } | |
| size_t prependSPSPPSToIDR(const uint8_t* src, size_t srcSize, uint8_t* dst, size_t dstSize, | |
| std::vector<uint8_t>* sps, std::vector<uint8_t>* pps) { | |
| bool foundStreamParams = false; | |
| size_t remainingDstSize = dstSize; | |
| NalParser parser(src, srcSize); | |
| while (parser.locateNextNal()) { | |
| switch (parser.type()) { | |
| case NalParser::kSPSType: | |
| // SPS found, copy to cache. | |
| ALOGV("Found SPS (length %zu)", parser.length()); | |
| sps->resize(parser.length()); | |
| memcpy(sps->data(), parser.data(), parser.length()); | |
| foundStreamParams = true; | |
| break; | |
| case NalParser::kPPSType: | |
| // PPS found, copy to cache. | |
| ALOGV("Found PPS (length %zu)", parser.length()); | |
| pps->resize(parser.length()); | |
| memcpy(pps->data(), parser.data(), parser.length()); | |
| foundStreamParams = true; | |
| break; | |
| case NalParser::kIDRType: | |
| ALOGV("Found IDR (length %zu)", parser.length()); | |
| if (foundStreamParams) { | |
| ALOGV("Not injecting SPS and PPS before IDR, already present"); | |
| break; | |
| } | |
| // Prepend the cached SPS and PPS to the IDR NAL unit. | |
| if (sps->empty() || pps->empty()) { | |
| ALOGE("No cached SPS or PPS NAL unit available to inject before IDR"); | |
| return 0; | |
| } | |
| if (!copyNALUPrependingStartCode(sps->data(), sps->size(), &dst, &remainingDstSize)) { | |
| ALOGE("Not enough space to inject SPS NAL unit before IDR"); | |
| return 0; | |
| } | |
| if (!copyNALUPrependingStartCode(pps->data(), pps->size(), &dst, &remainingDstSize)) { | |
| ALOGE("Not enough space to inject PPS NAL unit before IDR"); | |
| return 0; | |
| } | |
| ALOGV("Stream header injected before IDR"); | |
| break; | |
| } | |
| // Copy the NAL unit to the new output buffer. | |
| if (!copyNALUPrependingStartCode(parser.data(), parser.length(), &dst, &remainingDstSize)) { | |
| ALOGE("NAL unit does not fit in the provided output buffer"); | |
| return 0; | |
| } | |
| } | |
| return dstSize - remainingDstSize; | |
| } | |
| } // namespace android |