diff --git a/CMakeLists.txt b/CMakeLists.txt index e678a2919..e76b2725b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -229,6 +229,7 @@ add_library(${TARGET_CORE_NAME} src/pipeline/node/UVC.cpp src/pipeline/datatype/Buffer.cpp src/pipeline/datatype/ImgFrame.cpp + src/pipeline/datatype/EncodedFrame.cpp src/pipeline/datatype/ImageManipConfig.cpp src/pipeline/datatype/CameraControl.cpp src/pipeline/datatype/NNData.cpp @@ -247,6 +248,7 @@ add_library(${TARGET_CORE_NAME} src/pipeline/datatype/TrackedFeatures.cpp src/pipeline/datatype/FeatureTrackerConfig.cpp src/pipeline/datatype/ToFConfig.cpp + src/utility/H26xParsers.cpp src/utility/Initialization.cpp src/utility/Resources.cpp src/utility/Path.cpp diff --git a/cmake/Depthai/DepthaiDeviceSideConfig.cmake b/cmake/Depthai/DepthaiDeviceSideConfig.cmake index c07ba3710..99876d274 100644 --- a/cmake/Depthai/DepthaiDeviceSideConfig.cmake +++ b/cmake/Depthai/DepthaiDeviceSideConfig.cmake @@ -2,7 +2,7 @@ set(DEPTHAI_DEVICE_SIDE_MATURITY "snapshot") # "full commit hash of device side binary" -set(DEPTHAI_DEVICE_SIDE_COMMIT "d088dd017f964a3b51ecb1165b5e625e35d78af4") +set(DEPTHAI_DEVICE_SIDE_COMMIT "1cf15832ab1f408d8e4dab72e901ce050bf8850b") # "version if applicable" set(DEPTHAI_DEVICE_SIDE_VERSION "") diff --git a/include/depthai/pipeline/datatype/EncodedFrame.hpp b/include/depthai/pipeline/datatype/EncodedFrame.hpp new file mode 100644 index 000000000..99db79ba0 --- /dev/null +++ b/include/depthai/pipeline/datatype/EncodedFrame.hpp @@ -0,0 +1,144 @@ +#pragma once + +#include + +#include "depthai/pipeline/datatype/Buffer.hpp" + +// shared +#include "depthai-shared/datatype/RawEncodedFrame.hpp" + +// optional +#ifdef DEPTHAI_HAVE_OPENCV_SUPPORT + #include +#endif + +namespace dai { + +class EncodedFrame : public Buffer { + std::shared_ptr serialize() const override; + RawEncodedFrame& frame; + + public: + // Raw* mirror + using Profile = RawEncodedFrame::Profile; + using FrameType = RawEncodedFrame::FrameType; + + /** + * Construct EncodedFrame message. + * Timestamp is set to now + */ + EncodedFrame(); + explicit EncodedFrame(std::shared_ptr ptr); + virtual ~EncodedFrame() = default; + + // getters + /** + * Retrieves instance number + */ + unsigned int getInstanceNum() const; + /** + * Retrieves exposure time + */ + std::chrono::microseconds getExposureTime() const; + + /** + * Retrieves sensitivity, as an ISO value + */ + int getSensitivity() const; + + /** + * Retrieves white-balance color temperature of the light source, in kelvins + */ + int getColorTemperature() const; + + /** + * Retrieves lens position, range 0..255. Returns -1 if not available + */ + int getLensPosition() const; + /** + * Retrieves the encoding quality + */ + unsigned int getQuality() const; + + /** + * Retrieves the encoding bitrate + */ + unsigned int getBitrate() const; + + /** + * Returns true if encoding is lossless (JPEG only) + */ + bool getLossless() const; + + /** + * Retrieves frame type (H26x only) + */ + FrameType getFrameType() const; + + /** + * Retrieves the encoding profile (JPEG, AVC or HEVC) + */ + Profile getProfile() const; + + // setters + /** + * Retrieves image timestamp related to dai::Clock::now() + */ + EncodedFrame& setTimestamp(std::chrono::time_point tp); + + /** + * Sets image timestamp related to dai::Clock::now() + */ + EncodedFrame& setTimestampDevice(std::chrono::time_point tp); + + /** + * Specifies sequence number + * + * @param seq Sequence number + */ + EncodedFrame& setSequenceNum(int64_t seq); + + /** + * Instance number relates to the origin of the frame (which camera) + * + * @param instance Instance number + */ + EncodedFrame& setInstanceNum(unsigned int instance); + + /** + * Specifies the encoding quality + * + * @param quality Encoding quality + */ + EncodedFrame& setQuality(unsigned int quality); + + /** + * Specifies the encoding quality + * + * @param quality Encoding quality + */ + EncodedFrame& setBitrate(unsigned int bitrate); + + /** + * Specifies if encoding is lossless (JPEG only) + * + * @param lossless True if lossless + */ + EncodedFrame& setLossless(bool lossless); + + /** + * Specifies the frame type (H26x only) + * + * @param type Type of h26x frame (I, P, B) + */ + EncodedFrame& setFrameType(FrameType type); + + /** + * Specifies the encoding profile + * + * @param profile Encoding profile + */ + EncodedFrame& setProfile(Profile profile); +}; + +} // namespace dai diff --git a/include/depthai/pipeline/node/VideoEncoder.hpp b/include/depthai/pipeline/node/VideoEncoder.hpp index caa21ca82..59c186e52 100644 --- a/include/depthai/pipeline/node/VideoEncoder.hpp +++ b/include/depthai/pipeline/node/VideoEncoder.hpp @@ -25,10 +25,15 @@ class VideoEncoder : public NodeCRTP Input input{*this, "in", Input::Type::SReceiver, true, 4, true, {{DatatypeEnum::ImgFrame, true}}}; /** - * Outputs ImgFrame message that carries BITSTREAM encoded (MJPEG, H264 or H265) frame data. + * Outputs ImgFrame message that carries BITSTREAM encoded (MJPEG, H264 or H265) frame data. Mutually exclusive with out. */ Output bitstream{*this, "bitstream", Output::Type::MSender, {{DatatypeEnum::ImgFrame, false}}}; + /** + * Outputs EncodedFrame message that carries encoded (MJPEG, H264 or H265) frame data. Mutually exclusive with bitstream. + */ + Output out{*this, "out", Output::Type::MSender, {{DatatypeEnum::EncodedFrame, false}}}; + // Sets default options for a specified size and profile /** * Sets a default preset based on specified frame rate and profile diff --git a/shared/depthai-shared b/shared/depthai-shared index 2e8b3a4ea..1148fba38 160000 --- a/shared/depthai-shared +++ b/shared/depthai-shared @@ -1 +1 @@ -Subproject commit 2e8b3a4ea8dbe85a2b364119ca2b0b7405529110 +Subproject commit 1148fba385d29d4ef652ff7edf03a007c78f1734 diff --git a/shared/depthai-shared.cmake b/shared/depthai-shared.cmake index 72f46ce99..414e1ebf6 100644 --- a/shared/depthai-shared.cmake +++ b/shared/depthai-shared.cmake @@ -63,4 +63,4 @@ foreach(source_file ${DEPTHAI_SHARED_SOURCES}) if(NOT EXISTS ${source_file}) message(FATAL_ERROR "depthai-shared submodule files missing. Make sure to download prepackaged release instead of \"Source code\" on GitHub. Example: depthai-core-vX.Y.Z.tar.gz") endif() -endforeach() \ No newline at end of file +endforeach() diff --git a/src/pipeline/datatype/EncodedFrame.cpp b/src/pipeline/datatype/EncodedFrame.cpp new file mode 100644 index 000000000..0473d7e63 --- /dev/null +++ b/src/pipeline/datatype/EncodedFrame.cpp @@ -0,0 +1,121 @@ +#include "depthai/pipeline/datatype/EncodedFrame.hpp" + +#include "utility/H26xParsers.hpp" + +namespace dai { + +std::shared_ptr EncodedFrame::serialize() const { + return raw; +} + +EncodedFrame::EncodedFrame() : Buffer(std::make_shared()), frame(*dynamic_cast(raw.get())) { + // set timestamp to now + setTimestamp(std::chrono::steady_clock::now()); +} +EncodedFrame::EncodedFrame(std::shared_ptr ptr) : Buffer(std::move(ptr)), frame(*dynamic_cast(raw.get())) {} + +// getters +unsigned int EncodedFrame::getInstanceNum() const { + return frame.instanceNum; +} +std::chrono::microseconds EncodedFrame::getExposureTime() const { + return std::chrono::microseconds(frame.cam.exposureTimeUs); +} +int EncodedFrame::getSensitivity() const { + return frame.cam.sensitivityIso; +} +int EncodedFrame::getColorTemperature() const { + return frame.cam.wbColorTemp; +} +int EncodedFrame::getLensPosition() const { + return frame.cam.lensPosition; +} +unsigned int EncodedFrame::getQuality() const { + return frame.quality; +} +unsigned int EncodedFrame::getBitrate() const { + return frame.bitrate; +} +bool EncodedFrame::getLossless() const { + return frame.lossless; +} +EncodedFrame::FrameType EncodedFrame::getFrameType() const { + if(frame.type == FrameType::Unknown) { + utility::SliceType frameType; + switch(frame.profile) { + case RawEncodedFrame::Profile::JPEG: + frameType = utility::SliceType::I; + break; + case RawEncodedFrame::Profile::AVC: + frameType = utility::getTypesH264(frame.data, true)[0]; + break; + case RawEncodedFrame::Profile::HEVC: + frameType = utility::getTypesH265(frame.data, true)[0]; + break; + } + switch(frameType) { + case utility::SliceType::P: + frame.type = FrameType::P; + break; + case utility::SliceType::B: + frame.type = FrameType::B; + break; + case utility::SliceType::I: + frame.type = FrameType::I; + break; + case utility::SliceType::SP: + frame.type = FrameType::P; + break; + case utility::SliceType::SI: + frame.type = FrameType::I; + break; + case utility::SliceType::Unknown: + frame.type = FrameType::Unknown; + break; + } + } + return frame.type; +} +EncodedFrame::Profile EncodedFrame::getProfile() const { + return frame.profile; +} + +// setters +EncodedFrame& EncodedFrame::setTimestamp(std::chrono::time_point tp) { + // Set timestamp from timepoint + return static_cast(Buffer::setTimestamp(tp)); +} +EncodedFrame& EncodedFrame::setTimestampDevice(std::chrono::time_point tp) { + // Set timestamp from timepoint + return static_cast(Buffer::setTimestampDevice(tp)); +} +EncodedFrame& EncodedFrame::setSequenceNum(int64_t sequenceNum) { + return static_cast(Buffer::setSequenceNum(sequenceNum)); +} +EncodedFrame& EncodedFrame::setInstanceNum(unsigned int instanceNum) { + frame.instanceNum = instanceNum; + return *this; +} +EncodedFrame& EncodedFrame::setQuality(unsigned int quality) { + frame.quality = quality; + return *this; +} +EncodedFrame& EncodedFrame::setBitrate(unsigned int bitrate) { + frame.bitrate = bitrate; + return *this; +} + +EncodedFrame& EncodedFrame::setLossless(bool lossless) { + frame.lossless = lossless; + return *this; +} +EncodedFrame& EncodedFrame::setFrameType(FrameType frameType) { + frame.type = frameType; + return *this; +} +EncodedFrame& EncodedFrame::setProfile(Profile profile) { + frame.profile = profile; + return *this; +} + +} // namespace dai diff --git a/src/pipeline/datatype/StreamMessageParser.cpp b/src/pipeline/datatype/StreamMessageParser.cpp index db52b55d4..e40b3b9a1 100644 --- a/src/pipeline/datatype/StreamMessageParser.cpp +++ b/src/pipeline/datatype/StreamMessageParser.cpp @@ -15,6 +15,7 @@ #include "depthai/pipeline/datatype/Buffer.hpp" #include "depthai/pipeline/datatype/CameraControl.hpp" #include "depthai/pipeline/datatype/EdgeDetectorConfig.hpp" +#include "depthai/pipeline/datatype/EncodedFrame.hpp" #include "depthai/pipeline/datatype/FeatureTrackerConfig.hpp" #include "depthai/pipeline/datatype/IMUData.hpp" #include "depthai/pipeline/datatype/ImageManipConfig.hpp" @@ -37,6 +38,7 @@ #include "depthai-shared/datatype/RawBuffer.hpp" #include "depthai-shared/datatype/RawCameraControl.hpp" #include "depthai-shared/datatype/RawEdgeDetectorConfig.hpp" +#include "depthai-shared/datatype/RawEncodedFrame.hpp" #include "depthai-shared/datatype/RawFeatureTrackerConfig.hpp" #include "depthai-shared/datatype/RawIMUData.hpp" #include "depthai-shared/datatype/RawImageManipConfig.hpp" @@ -122,6 +124,10 @@ std::shared_ptr StreamMessageParser::parseMessage(streamPacketDesc_t* return parseDatatype(metadataStart, serializedObjectSize, data); break; + case DatatypeEnum::EncodedFrame: + return parseDatatype(metadataStart, serializedObjectSize, data); + break; + case DatatypeEnum::NNData: return parseDatatype(metadataStart, serializedObjectSize, data); break; @@ -213,6 +219,10 @@ std::shared_ptr StreamMessageParser::parseMessageToADatatype(streamPa return std::make_shared(parseDatatype(metadataStart, serializedObjectSize, data)); break; + case DatatypeEnum::EncodedFrame: + return std::make_shared(parseDatatype(metadataStart, serializedObjectSize, data)); + break; + case DatatypeEnum::NNData: return std::make_shared(parseDatatype(metadataStart, serializedObjectSize, data)); break; diff --git a/src/pipeline/node/VideoEncoder.cpp b/src/pipeline/node/VideoEncoder.cpp index da8efc018..0ff9afc7f 100644 --- a/src/pipeline/node/VideoEncoder.cpp +++ b/src/pipeline/node/VideoEncoder.cpp @@ -15,7 +15,7 @@ VideoEncoder::VideoEncoder(const std::shared_ptr& par, int64_t nod VideoEncoder::VideoEncoder(const std::shared_ptr& par, int64_t nodeId, std::unique_ptr props) : NodeCRTP(par, nodeId, std::move(props)) { setInputRefs({&input}); - setOutputRefs({&bitstream}); + setOutputRefs({&bitstream, &out}); } // node properties void VideoEncoder::setNumFramesPool(int frames) { diff --git a/src/utility/H26xParsers.cpp b/src/utility/H26xParsers.cpp new file mode 100644 index 000000000..62922d204 --- /dev/null +++ b/src/utility/H26xParsers.cpp @@ -0,0 +1,292 @@ +#include "H26xParsers.hpp" + +#include +#include + +namespace dai { +namespace utility { + +template +struct H26xParser { + protected: + virtual void parseNal(const std::vector& bs, unsigned int start, std::vector& out) = 0; + std::vector parseBytestream(const std::vector& bs, bool breakOnFirst); + + public: + static std::vector getTypes(const std::vector& bs, bool breakOnFirst); + virtual ~H26xParser() = default; +}; + +struct H264Parser : H26xParser { + void parseNal(const std::vector& bs, unsigned int start, std::vector& out); +}; + +struct H265Parser : H26xParser { + unsigned int nalUnitType = 0; // In NAL header + unsigned int dependentSliceSegmentsEnabledFlag = 0; // In picture parameter set + unsigned int numExtraSliceHeaderBits = 0; // In picture parameter set + // Getting the length of slice_segment address + unsigned int picWidthInLumaSamples = 0; // In sequence parameter set + unsigned int picHeightInLumaSamples = 0; // In sequence parameter set + unsigned int chromaFormatIdc = 0; // In sequence parameter set + unsigned int log2DiffMaxMinLumaCodingBlockSize = 0; // In sequence parameter set + unsigned int log2MinLumaCodingBlockSizeMinus3 = 0; // In sequence parameter set + + void parseNal(const std::vector& bs, unsigned int start, std::vector& out); +}; + +typedef unsigned int uint; +typedef unsigned long ulong; +typedef const std::vector buf; + +SliceType getSliceType(uint num, Profile p) { + switch(p) { + case Profile::H264: + switch(num) { + case 0: + case 5: + return SliceType::P; + case 1: + case 6: + return SliceType::B; + case 2: + case 7: + return SliceType::I; + case 3: + case 8: + return SliceType::SP; + case 4: + case 9: + return SliceType::SI; + default: + return SliceType::Unknown; + } + case Profile::H265: + switch(num) { + case 0: + return SliceType::B; + case 1: + return SliceType::P; + case 2: + return SliceType::I; + default: + return SliceType::Unknown; + } + default: + return SliceType::Unknown; + } +} + +bool scodeEq(buf& bs, uint pos, buf code) { + if(bs.size() - pos > code.size()) { + for(uint i = 0; i < code.size(); ++i) { + if(bs[pos + i] != code[i]) return false; + } + return true; + } else + return false; +} + +uint findStart(buf& bs, uint pos) { + buf codeLong = {0, 0, 0, 1}; + buf codeShort = {0, 0, 1}; + uint size = bs.size(); + for(uint i = pos; i < size; ++i) { + if(bs[i] == 0) { + if(scodeEq(bs, i, codeLong)) { + return i + 4; + } else if(scodeEq(bs, i, codeShort)) { + return i + 3; + } + } + } + return size; +} + +uint findEnd(buf& bs, uint pos) { + buf end1 = {0, 0, 0}; + buf end2 = {0, 0, 1}; + uint size = bs.size(); + for(uint i = pos; i < size; ++i) { + if(bs[i] == 0) { + if(scodeEq(bs, i, end1) || scodeEq(bs, i, end2)) { + return i; + } + } + } + return size; +} + +uint readUint(buf& bs, ulong start, ulong end) { + uint ret = 0; + for(ulong i = start; i < end; ++i) { + uint bit = (bs[(uint)(i / 8)] & (1 << (7 - i % 8))) > 0; + ret += bit * (1 << (end - i - 1)); + } + return ret; +} + +std::tuple readGE(buf& bs, ulong pos) { + uint count = 0; + ulong size = bs.size() * 8; + while(pos < size) { + std::uint8_t bit = bs[(uint)(pos / 8)] & (char)(1 << (7 - pos % 8)); + if(bit == 0) { + ++count; + ++pos; + } else + break; + } + ++count; + ulong start = pos; + ulong end = pos + count; + if(end > size) exit(30); + return std::make_tuple(readUint(bs, start, end) - 1, end); +} + +template +std::vector H26xParser::getTypes(buf& buffer, bool breakOnFirst) { + T p; + return p.parseBytestream(buffer, breakOnFirst); +} + +template +std::vector H26xParser::parseBytestream(buf& bs, bool breakOnFirst) { + uint pos = 0; + uint size = bs.size(); + std::vector ret; + while(pos < size) { + uint start = findStart(bs, pos); + uint end = findEnd(bs, start); + if(start >= end) break; + parseNal(bs, start, ret); + pos = end; + if(breakOnFirst && ret.size() > 0) break; + } + return ret; +} + +void H264Parser::parseNal(buf& bs, uint start, std::vector& out) { + uint pos = start; + uint nalUnitType = bs[pos++] & 31; + uint nalUnitHeaderBytes = 1; + if(nalUnitType == 14 || nalUnitType == 20 || nalUnitType == 21) { + uint avc3dExtensionFlag = 0; + if(nalUnitType == 21) avc3dExtensionFlag = readUint(bs, pos * 8, pos * 8 + 1); + if(avc3dExtensionFlag) + nalUnitHeaderBytes += 2; + else + nalUnitHeaderBytes += 3; + } + pos = start + nalUnitHeaderBytes; + if(nalUnitType == 1 || nalUnitType == 5) { + const auto bpos1 = std::get<1>(readGE(bs, pos * 8)); + uint tyNum; + ulong bpos2; + std::tie(tyNum, bpos2) = readGE(bs, bpos1); + pos = (uint)(bpos2 / 8) + (bpos2 % 8 > 0); + out.push_back(getSliceType(tyNum, Profile::H264)); + } +} + +void H265Parser::parseNal(buf& bs, uint start, std::vector& out) { + nalUnitType = (bs[start] & 126) >> 1; + uint pos = start + 2; + if(nalUnitType == 33) { + // Sequence parameter set + uint spsMaxSubLayersMinus1 = (bs[pos] & 14) >> 1; + ++pos; + const auto bpos1 = std::get<1>(readGE(bs, pos * 8)); // We don't need this value + uint chr; + ulong bpos2; + std::tie(chr, bpos2) = readGE(bs, bpos1); + chromaFormatIdc = chr; + if(chromaFormatIdc) ++bpos2; // We don't need this value + uint pw; + ulong bpos3; + std::tie(pw, bpos3) = readGE(bs, bpos2); + uint ph; + ulong bpos4; + std::tie(ph, bpos4) = readGE(bs, bpos3); + picWidthInLumaSamples = pw; + picHeightInLumaSamples = ph; + uint conformanceWindowFlag = readUint(bs, bpos4, bpos4 + 1); + ulong bpos8 = ++bpos4; + if(conformanceWindowFlag) { + // We don't care about any of these values + auto bpos5 = std::get<1>(readGE(bs, bpos4)); + auto bpos6 = std::get<1>(readGE(bs, bpos5)); + auto bpos7 = std::get<1>(readGE(bs, bpos6)); + auto tbpos8 = std::get<1>(readGE(bs, bpos7)); + bpos8 = tbpos8; + } + auto bpos9 = std::get<1>(readGE(bs, bpos8)); + auto bpos10 = std::get<1>(readGE(bs, bpos9)); + auto bpos11 = std::get<1>(readGE(bs, bpos10)); + uint spsSubLayerOrderingInfoPresentFlag = readUint(bs, bpos11, bpos11 + 1); + ulong bpos12 = ++bpos11; + for(uint i = spsSubLayerOrderingInfoPresentFlag ? 0 : spsMaxSubLayersMinus1; i <= spsMaxSubLayersMinus1; ++i) { + auto tbpos1 = std::get<1>(readGE(bs, bpos12)); + auto tbpos2 = std::get<1>(readGE(bs, tbpos1)); + auto tbpos3 = std::get<1>(readGE(bs, tbpos2)); + bpos12 = tbpos3; + } + uint lm, ldm; + ulong bpos13, bpos14; + std::tie(lm, bpos13) = readGE(bs, bpos12); + std::tie(ldm, bpos14) = readGE(bs, bpos13); + log2MinLumaCodingBlockSizeMinus3 = lm; + log2DiffMaxMinLumaCodingBlockSize = ldm; + pos = (uint)(bpos14 / 8) + (bpos14 % 8 > 0); // Update byte position with newest bit position + } else if(nalUnitType == 34) { + // Picture parameter set + auto bpos1 = std::get<1>(readGE(bs, pos * 8)); + auto bpos2 = std::get<1>(readGE(bs, bpos1)); + dependentSliceSegmentsEnabledFlag = readUint(bs, bpos2, bpos2 + 1); + bpos2 += 2; + numExtraSliceHeaderBits = readUint(bs, bpos2, bpos2 + 3); + bpos2 += 3; + pos = (uint)(bpos2 / 8) + (bpos2 % 8 > 0); + } else if((0 <= nalUnitType && nalUnitType <= 9) || (16 <= nalUnitType && nalUnitType <= 21)) { + // Coded slice segment + ulong bpos1 = pos * 8; + uint firstSliceSegmentInPicFlag = readUint(bs, bpos1, bpos1 + 1); + ++bpos1; + if(16 <= nalUnitType && nalUnitType <= 23) ++bpos1; + auto bpos2 = std::get<1>(readGE(bs, bpos1)); + uint dependentSliceSegmentFlag = 0; + if(!firstSliceSegmentInPicFlag) { + if(dependentSliceSegmentsEnabledFlag) { + dependentSliceSegmentFlag = readUint(bs, bpos2, bpos2 + 1); + ++bpos2; + } + uint ctbLog2SizeY = log2MinLumaCodingBlockSizeMinus3 + 3 + log2DiffMaxMinLumaCodingBlockSize; + uint ctbSizeY = 1 << ctbLog2SizeY; + uint picWidthInCtbsY = ceil(picWidthInLumaSamples / ctbSizeY); + uint picHeightInCtbsY = ceil(picHeightInLumaSamples / ctbSizeY); + uint picSizeInCtbsY = picWidthInCtbsY * picHeightInCtbsY; + uint vlen = ceil(log2(picSizeInCtbsY)); + bpos2 += vlen; + } + ulong bpos3 = bpos2; + if(!dependentSliceSegmentFlag) { + bpos2 += numExtraSliceHeaderBits; + uint tyNum; + ulong tbpos3; + std::tie(tyNum, tbpos3) = readGE(bs, bpos2); + bpos3 = tbpos3; + out.push_back(getSliceType(tyNum, Profile::H265)); + } + pos = (uint)(bpos3 / 8) + (bpos3 % 8 > 0); + } +} + +std::vector getTypesH264(buf& bs, bool breakOnFirst) { + return H264Parser::getTypes(bs, breakOnFirst); +} +std::vector getTypesH265(buf& bs, bool breakOnFirst) { + return H265Parser::getTypes(bs, breakOnFirst); +} + +} // namespace utility +} // namespace dai diff --git a/src/utility/H26xParsers.hpp b/src/utility/H26xParsers.hpp new file mode 100644 index 000000000..a8b1e3b6c --- /dev/null +++ b/src/utility/H26xParsers.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include +#include + +namespace dai { +namespace utility { + +enum class Profile { H264, H265 }; +enum class SliceType { P, B, I, SP, SI, Unknown }; + +std::vector getTypesH264(const std::vector& bs, bool breakOnFirst = false); +std::vector getTypesH265(const std::vector& bs, bool breakOnFirst = false); + +} // namespace utility +} // namespace dai diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 381072b80..ab662e8e4 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -225,6 +225,8 @@ dai_add_test(device_usbspeed_test src/device_usbspeed_test.cpp CONFORMING) dai_add_test(device_usbspeed_test_17 src/device_usbspeed_test.cpp CONFORMING CXX_STANDARD 17) dai_add_test(device_usbspeed_test_20 src/device_usbspeed_test.cpp CONFORMING CXX_STANDARD 20) +dai_add_test(encoded_frame_test src/encoded_frame_test.cpp CXX_STANDARD 17) + # Unlimited io connections test dai_add_test(unlimited_io_connection_test src/unlimited_io_connection_test.cpp) diff --git a/tests/src/encoded_frame_test.cpp b/tests/src/encoded_frame_test.cpp new file mode 100644 index 000000000..8c68bd79f --- /dev/null +++ b/tests/src/encoded_frame_test.cpp @@ -0,0 +1,122 @@ +#include +#include +#include + +#include "depthai-shared/datatype/RawEncodedFrame.hpp" +#include "depthai-shared/properties/VideoEncoderProperties.hpp" +#include "depthai/device/Device.hpp" +#include "depthai/pipeline/Pipeline.hpp" +#include "depthai/pipeline/datatype/EncodedFrame.hpp" +#include "depthai/pipeline/datatype/ImgFrame.hpp" +#include "depthai/pipeline/node/ColorCamera.hpp" +#include "depthai/pipeline/node/VideoEncoder.hpp" +#include "depthai/pipeline/node/XLinkOut.hpp" + +dai::Pipeline getPipeline(dai::VideoEncoderProperties::Profile profile, unsigned int quality, bool lossless, unsigned int bitrate) { + dai::Pipeline pipeline; + auto camNode = pipeline.create(); + auto encNode = pipeline.create(); + auto xlinkOut = pipeline.create(); + camNode->video.link(encNode->input); + encNode->out.link(xlinkOut->input); + + camNode->setVideoSize(1280, 720); + encNode->setProfile(profile); + encNode->setBitrate(bitrate); + encNode->setQuality(quality); + encNode->setLossless(lossless); + encNode->setKeyframeFrequency(30); + xlinkOut->setStreamName("out"); + + return pipeline; +} + +TEST_CASE("OLD_OUTPUT") { + dai::Pipeline pipeline; + auto camNode = pipeline.create(); + auto encNode = pipeline.create(); + auto xlinkOut = pipeline.create(); + camNode->video.link(encNode->input); + encNode->bitstream.link(xlinkOut->input); + + camNode->setVideoSize(1280, 720); + encNode->setProfile(dai::VideoEncoderProperties::Profile::H264_MAIN); + xlinkOut->setStreamName("out"); + + dai::Device device(pipeline); + + auto outQ = device.getOutputQueue("out"); + for(int i = 0; i < 100; ++i) { + REQUIRE_NOTHROW(outQ->get()); + } +} + +TEST_CASE("JPEG_ENCODING_LOSSLESS") { + dai::Device device(getPipeline(dai::VideoEncoderProperties::Profile::MJPEG, 30, true, 0)); + + auto outQ = device.getOutputQueue("out"); + for(int i = 0; i < 100; ++i) { + auto encfrm = outQ->get(); + REQUIRE(encfrm->getProfile() == dai::EncodedFrame::Profile::JPEG); + REQUIRE(encfrm->getLossless() == true); + REQUIRE(encfrm->getQuality() == 30); + } +} + +TEST_CASE("JPEG_ENCODING_LOSSY") { + dai::Device device(getPipeline(dai::VideoEncoderProperties::Profile::MJPEG, 30, false, 0)); + + auto outQ = device.getOutputQueue("out"); + for(int i = 0; i < 100; ++i) { + auto encfrm = outQ->get(); + REQUIRE(encfrm->getProfile() == dai::EncodedFrame::Profile::JPEG); + REQUIRE(encfrm->getLossless() == false); + REQUIRE(encfrm->getQuality() == 30); + } +} + +TEST_CASE("AVC_ENCODING") { + dai::Device device(getPipeline(dai::VideoEncoderProperties::Profile::H264_HIGH, 30, false, 8500000)); + + auto outQ = device.getOutputQueue("out"); + for(int i = 0; i < 100; ++i) { + auto encfrm = outQ->get(); + REQUIRE(encfrm->getProfile() == dai::EncodedFrame::Profile::AVC); + REQUIRE(encfrm->getLossless() == false); + if(i % 30 == 0) REQUIRE(encfrm->getFrameType() == dai::EncodedFrame::FrameType::I); + REQUIRE(encfrm->getQuality() == 30); + REQUIRE(encfrm->getBitrate() == 8500000); + } +} + +TEST_CASE("HEVC_ENCODING") { + dai::Device device(getPipeline(dai::VideoEncoderProperties::Profile::H265_MAIN, 30, false, 8500000)); + + auto outQ = device.getOutputQueue("out"); + for(int i = 0; i < 100; ++i) { + auto encfrm = outQ->get(); + REQUIRE(encfrm->getProfile() == dai::EncodedFrame::Profile::HEVC); + REQUIRE(encfrm->getLossless() == false); + if(i % 30 == 0) REQUIRE(encfrm->getFrameType() == dai::EncodedFrame::FrameType::I); + REQUIRE(encfrm->getQuality() == 30); + REQUIRE(encfrm->getBitrate() == 8500000); + } +} + +TEST_CASE("LINK_TO_BOTH") { + dai::Pipeline pipeline; + auto camNode = pipeline.create(); + auto encNode = pipeline.create(); + auto xlinkOut1 = pipeline.create(); + auto xlinkOut2 = pipeline.create(); + camNode->video.link(encNode->input); + encNode->bitstream.link(xlinkOut1->input); + encNode->out.link(xlinkOut2->input); + + camNode->setVideoSize(1280, 720); + encNode->setProfile(dai::VideoEncoderProperties::Profile::H264_MAIN); + xlinkOut1->setStreamName("out1"); + xlinkOut2->setStreamName("out2"); + + REQUIRE_THROWS(dai::Device(pipeline)); +}