diff --git a/app/videonative/src/main/cpp/.clang-format b/app/videonative/src/main/cpp/.clang-format new file mode 100644 index 0000000..a8a7562 --- /dev/null +++ b/app/videonative/src/main/cpp/.clang-format @@ -0,0 +1,45 @@ +BasedOnStyle: Google +IndentWidth: 4 +TabWidth: 4 +UseTab: Never +AlignAfterOpenBracket: AlwaysBreak +BreakBeforeBraces: Custom +BraceWrapping: + BeforeElse: true + AfterFunction: true + AfterEnum: true + AfterControlStatement: Always + AfterClass: true + AfterCaseLabel: true + AfterNamespace: true + AfterObjCDeclaration: true + AfterStruct: true + AfterUnion: true + AfterExternBlock: true + BeforeCatch: true + BeforeLambdaBody: true + BeforeWhile: true + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: false + SplitEmptyNamespace: true +AllowShortFunctionsOnASingleLine: Inline +ColumnLimit: 120 +NamespaceIndentation: None +AccessModifierOffset: -2 +AlignConsecutiveAssignments: true +AlignConsecutiveDeclarations: true +AlignOperands: true +AlignTrailingComments: true +BinPackArguments: false +BinPackParameters: false +PointerAlignment: Left +DerivePointerAlignment: false +SpaceBeforeParens: ControlStatements +SpaceAfterCStyleCast: true +SpacesInAngles: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +SortIncludes: true +IncludeBlocks: Preserve +IndentCaseLabels: true diff --git a/app/videonative/src/main/cpp/AndroidThreadPrioValues.hpp b/app/videonative/src/main/cpp/AndroidThreadPrioValues.hpp index ce0050e..17c2148 100644 --- a/app/videonative/src/main/cpp/AndroidThreadPrioValues.hpp +++ b/app/videonative/src/main/cpp/AndroidThreadPrioValues.hpp @@ -8,14 +8,18 @@ // The values for android thread priorities and // the values used by my FPV_VR app (2 different namespaces though) -namespace AndroidThreadPriorityValues { - //This one was taken from https://android.googlesource.com/platform/system/core/+/jb-dev/include/system/graphics.h - constexpr auto HAL_PRIORITY_URGENT_DISPLAY = -8; - // values taken from https://android.googlesource.com/platform/frameworks/native/+/android-4.2.2_r1/include/utils/ThreadDefs.h +namespace AndroidThreadPriorityValues +{ +// This one was taken from https://android.googlesource.com/platform/system/core/+/jb-dev/include/system/graphics.h +constexpr auto HAL_PRIORITY_URGENT_DISPLAY = -8; +// values taken from +// https://android.googlesource.com/platform/frameworks/native/+/android-4.2.2_r1/include/utils/ThreadDefs.h #ifdef __cplusplus - extern "C" { +extern "C" +{ #endif - enum { + enum + { /* * *********************************************** * ** Keep in sync with android.os.Process.java ** @@ -52,27 +56,28 @@ namespace AndroidThreadPriorityValues { ANDROID_PRIORITY_URGENT_AUDIO = -19, /* should never be used in practice. regular process might not * be allowed to use this level */ - ANDROID_PRIORITY_HIGHEST = -20, - ANDROID_PRIORITY_DEFAULT = ANDROID_PRIORITY_NORMAL, + ANDROID_PRIORITY_HIGHEST = -20, + ANDROID_PRIORITY_DEFAULT = ANDROID_PRIORITY_NORMAL, ANDROID_PRIORITY_MORE_FAVORABLE = -1, ANDROID_PRIORITY_LESS_FAVORABLE = +1, }; #ifdef __cplusplus - } // extern "C" +} // extern "C" #endif -} +} // namespace AndroidThreadPriorityValues // All these values are for FPVue_Android -//namespace FPV_VR_PRIORITY{ +// namespace FPV_VR_PRIORITY{ // constexpr int CPU_PRIORITY_GLRENDERER_STEREO_FB=-19; //This one needs a whole CPU core all the time anyways // constexpr int CPU_PRIORITY_GLRENDERER_STEREO=-16; //The GL thread also should get 1 whole cpu core // constexpr int CPU_PRIORITY_UDPRECEIVER_VIDEO=-16; //needs low latency and does not use the cpu that much // constexpr int CPU_PRIORITY_DECODER_OUTPUT=-16; //needs low latency and does not use the cpu that much -// constexpr int CPU_PRIORITY_UVC_FRAME_CALLBACK=-17; //needs low latency but uses CPU a lot (decoding). More prio than GLRenderer +// constexpr int CPU_PRIORITY_UVC_FRAME_CALLBACK=-17; //needs low latency but uses CPU a lot (decoding). More prio +// than GLRenderer // // These are much lower // constexpr int CPU_PRIORITY_GLRENDERER_MONO=-4; //only shows the OSD not video -// constexpr int CPU_PRIORITY_UDPRECEIVER_TELEMETRY=-4; //not as important as video but also needs almost no CPU processing time -// constexpr int CPU_PRIORITY_UDPSENDER_HEADTRACKING=-4; +// constexpr int CPU_PRIORITY_UDPRECEIVER_TELEMETRY=-4; //not as important as video but also needs almost no CPU +// processing time constexpr int CPU_PRIORITY_UDPSENDER_HEADTRACKING=-4; //} -#endif //FPVUE_ANDROIDTHREADPRIOVALUES_HPP +#endif // FPVUE_ANDROIDTHREADPRIOVALUES_HPP diff --git a/app/videonative/src/main/cpp/AudioDecoder.cpp b/app/videonative/src/main/cpp/AudioDecoder.cpp index 9012821..144fe0b 100644 --- a/app/videonative/src/main/cpp/AudioDecoder.cpp +++ b/app/videonative/src/main/cpp/AudioDecoder.cpp @@ -10,19 +10,20 @@ #define SAMPLE_RATE 8000 #define CHANNELS 1 -AudioDecoder::AudioDecoder() { +AudioDecoder::AudioDecoder() +{ initAudio(); } -AudioDecoder::~AudioDecoder() { - +AudioDecoder::~AudioDecoder() +{ stopAudioProcessing(); delete pOpusDecoder; AAudioStream_requestStop(m_stream); AAudioStream_close(m_stream); } -void AudioDecoder::enqueueAudio(const uint8_t *data, const std::size_t data_length) +void AudioDecoder::enqueueAudio(const uint8_t* data, const std::size_t data_length) { { std::lock_guard lock(m_mtxQueue); @@ -31,15 +32,19 @@ void AudioDecoder::enqueueAudio(const uint8_t *data, const std::size_t data_leng m_cvQueue.notify_one(); } -void AudioDecoder::processAudioQueue() { - while (true) { +void AudioDecoder::processAudioQueue() +{ + while (true) + { std::unique_lock lock(m_mtxQueue); m_cvQueue.wait(lock, [this] { return !m_audioQueue.empty() || stopAudioFlag; }); - if (stopAudioFlag) { + if (stopAudioFlag) + { break; } - if (!m_audioQueue.empty()) { + if (!m_audioQueue.empty()) + { AudioUDPPacket audioPkt = m_audioQueue.front(); onNewAudioData(audioPkt.data, audioPkt.len); m_audioQueue.pop(); @@ -48,7 +53,8 @@ void AudioDecoder::processAudioQueue() { } } -void AudioDecoder::initAudio() { +void AudioDecoder::initAudio() +{ __android_log_print(ANDROID_LOG_DEBUG, TAG, "initAudio"); int error; pOpusDecoder = opus_decoder_create(SAMPLE_RATE, CHANNELS, &error); @@ -57,8 +63,8 @@ void AudioDecoder::initAudio() { // Set the stream format AAudioStreamBuilder_setFormat(m_builder, AAUDIO_FORMAT_PCM_I16); - AAudioStreamBuilder_setChannelCount(m_builder, CHANNELS); // Mono - AAudioStreamBuilder_setSampleRate(m_builder, SAMPLE_RATE); // 8000 Hz + AAudioStreamBuilder_setChannelCount(m_builder, CHANNELS); // Mono + AAudioStreamBuilder_setSampleRate(m_builder, SAMPLE_RATE); // 8000 Hz AAudioStreamBuilder_setBufferCapacityInFrames(m_builder, BUFFER_CAPACITY_IN_FRAMES); @@ -80,22 +86,24 @@ void AudioDecoder::stopAudio() isInit = false; } -void AudioDecoder::onNewAudioData(const uint8_t *data, const std::size_t data_length) { - const int rtp_header_size = 12; - const uint8_t* opus_payload = data + rtp_header_size; - int opus_payload_size = data_length - rtp_header_size; +void AudioDecoder::onNewAudioData(const uint8_t* data, const std::size_t data_length) +{ + const int rtp_header_size = 12; + const uint8_t* opus_payload = data + rtp_header_size; + int opus_payload_size = data_length - rtp_header_size; int frame_size = opus_packet_get_samples_per_frame(opus_payload, SAMPLE_RATE); - int nb_frames = opus_packet_get_nb_frames(opus_payload, opus_payload_size); + int nb_frames = opus_packet_get_nb_frames(opus_payload, opus_payload_size); // Decode the frame int pcm_size = frame_size * nb_frames * CHANNELS; - if(pOpusDecoder && m_stream) { + if (pOpusDecoder && m_stream) + { opus_int16 pcm[pcm_size]; - int decoded_samples = opus_decode(pOpusDecoder, opus_payload, opus_payload_size, pcm, - pcm_size, 0); + int decoded_samples = opus_decode(pOpusDecoder, opus_payload, opus_payload_size, pcm, pcm_size, 0); - if (decoded_samples < 0) { + if (decoded_samples < 0) + { return; } // Process the decoded PCM data diff --git a/app/videonative/src/main/cpp/AudioDecoder.h b/app/videonative/src/main/cpp/AudioDecoder.h index b3740f2..39d2596 100644 --- a/app/videonative/src/main/cpp/AudioDecoder.h +++ b/app/videonative/src/main/cpp/AudioDecoder.h @@ -4,44 +4,49 @@ #ifndef PIXELPILOT_AUDIODECODER_H #define PIXELPILOT_AUDIODECODER_H -#include "libs/include/opus.h" #include +#include #include -#include #include -#include +#include #include +#include "libs/include/opus.h" -typedef struct _AudioUDPPacket{ +typedef struct _AudioUDPPacket +{ _AudioUDPPacket(const uint8_t* _data, size_t _len) { memcpy(data, _data, _len); len = _len; }; uint8_t data[250]; - size_t len; + size_t len; } AudioUDPPacket; -class AudioDecoder { -public: +class AudioDecoder +{ + public: AudioDecoder(); ~AudioDecoder(); // Audio buffer void initAudio(); - void enqueueAudio(const uint8_t *data, const std::size_t data_length); - void startAudioProcessing() { + void enqueueAudio(const uint8_t* data, const std::size_t data_length); + void startAudioProcessing() + { stopAudioFlag = false; m_audioThread = std::thread(&AudioDecoder::processAudioQueue, this); } - void stopAudioProcessing() { + void stopAudioProcessing() + { { std::lock_guard lock(m_mtxQueue); stopAudioFlag = true; } m_cvQueue.notify_all(); - if (m_audioThread.joinable()) { + if (m_audioThread.joinable()) + { m_audioThread.join(); } } @@ -49,18 +54,18 @@ class AudioDecoder { void stopAudio(); bool isInit = false; -private: - void onNewAudioData(const uint8_t *data, const std::size_t data_length); + private: + void onNewAudioData(const uint8_t* data, const std::size_t data_length); -private: - const int BUFFER_CAPACITY_IN_FRAMES = (1024 + 256); + private: + const int BUFFER_CAPACITY_IN_FRAMES = (1024 + 256); std::queue m_audioQueue; - std::mutex m_mtxQueue; - std::condition_variable m_cvQueue; - bool stopAudioFlag = false; - std::thread m_audioThread; - AAudioStreamBuilder* m_builder = nullptr; - AAudioStream* m_stream = nullptr; - OpusDecoder *pOpusDecoder = nullptr; + std::mutex m_mtxQueue; + std::condition_variable m_cvQueue; + bool stopAudioFlag = false; + std::thread m_audioThread; + AAudioStreamBuilder* m_builder = nullptr; + AAudioStream* m_stream = nullptr; + OpusDecoder* pOpusDecoder = nullptr; }; -#endif //PIXELPILOT_AUDIODECODER_H +#endif // PIXELPILOT_AUDIODECODER_H diff --git a/app/videonative/src/main/cpp/BufferedPacketQueue.h b/app/videonative/src/main/cpp/BufferedPacketQueue.h index 1333d6a..91161b7 100644 --- a/app/videonative/src/main/cpp/BufferedPacketQueue.h +++ b/app/videonative/src/main/cpp/BufferedPacketQueue.h @@ -1,11 +1,11 @@ +#include #include -#include +#include #include -#include -#include +#include #include -#include -#include +#include +#include // Define logging tag and maximum buffer size #define BUFFERED_QUEUE_LOG_TAG "BufferedPacketQueue" @@ -21,8 +21,9 @@ using SeqType = uint16_t; * @brief BufferedPacketQueue class handles packet processing with sequence numbers, * ensuring in-order delivery and buffering out-of-order packets. */ -class BufferedPacketQueue { -public: +class BufferedPacketQueue +{ + public: /** * @brief Constructs a BufferedPacketQueue instance. */ @@ -37,13 +38,16 @@ class BufferedPacketQueue { * @param callback Callable to handle processed packets. */ template - void processPacket(SeqType currPacketIdx, const uint8_t *data, - std::size_t data_length, Callback &callback) { - - logDebug("Processing packet with Sequence=%u, lastPacketIdx=%u, firstPacket=%s", - currPacketIdx, mLastPacketIdx, mFirstPacket ? "true" : "false"); - - if (isFirstPacket(currPacketIdx)) { + void processPacket(SeqType currPacketIdx, const uint8_t* data, std::size_t data_length, Callback& callback) + { + logDebug( + "Processing packet with Sequence=%u, lastPacketIdx=%u, firstPacket=%s", + currPacketIdx, + mLastPacketIdx, + mFirstPacket ? "true" : "false"); + + if (isFirstPacket(currPacketIdx)) + { handleFirstPacket(currPacketIdx); // Continue processing the first packet processInOrderPacket(currPacketIdx, data, data_length, callback); @@ -51,27 +55,30 @@ class BufferedPacketQueue { return; } - if (isNextExpectedPacket(currPacketIdx)) { + if (isNextExpectedPacket(currPacketIdx)) + { // In-order packet processInOrderPacket(currPacketIdx, data, data_length, callback); processBufferedPackets(callback); // Reset monotonic increase counter after in-order packet mMonotonicOutOfOrderIncreaseCount = 0; } - else { + else + { // Out-of-order packet handleOutOfOrderPacket(currPacketIdx, data, data_length, callback); } } -private: - bool mFirstPacket; + private: + bool mFirstPacket; SeqType mLastPacketIdx; std::unordered_map> mPackets; - // This variable is used to track a situation where the sequence number is increasing monotonically while packets are out of order. - // if this counter reaches MONOTONIC_THRESHOLD, we will restart buffering and update lastPacketIdx to the highest sequence index received. + // This variable is used to track a situation where the sequence number is increasing monotonically while packets + // are out of order. if this counter reaches MONOTONIC_THRESHOLD, we will restart buffering and update lastPacketIdx + // to the highest sequence index received. size_t mMonotonicOutOfOrderIncreaseCount; /** @@ -79,17 +86,16 @@ class BufferedPacketQueue { * @param currPacketIdx Sequence index of the incoming packet. * @return True if it's the first packet; otherwise, false. */ - bool isFirstPacket(SeqType currPacketIdx) const { - return mFirstPacket; - } + bool isFirstPacket(SeqType currPacketIdx) const { return mFirstPacket; } /** * @brief Handles the first packet by initializing the lastPacketIdx. * @param currPacketIdx Sequence index of the first packet. */ - void handleFirstPacket(SeqType currPacketIdx) { + void handleFirstPacket(SeqType currPacketIdx) + { mLastPacketIdx = currPacketIdx - 1; - mFirstPacket = false; + mFirstPacket = false; logDebug("First packet received. Initialized lastPacketIdx to %u", mLastPacketIdx); } @@ -98,9 +104,7 @@ class BufferedPacketQueue { * @param currPacketIdx Sequence index of the incoming packet. * @return True if it's the next expected packet; otherwise, false. */ - bool isNextExpectedPacket(SeqType currPacketIdx) const { - return currPacketIdx == mLastPacketIdx + 1; - } + bool isNextExpectedPacket(SeqType currPacketIdx) const { return currPacketIdx == mLastPacketIdx + 1; } /** * @brief Processes an in-order packet by invoking the callback and updating state. @@ -111,8 +115,8 @@ class BufferedPacketQueue { * @param callback Callable to handle processed packets. */ template - void processInOrderPacket(SeqType currPacketIdx, const uint8_t *data, - std::size_t data_length, Callback &callback) { + void processInOrderPacket(SeqType currPacketIdx, const uint8_t* data, std::size_t data_length, Callback& callback) + { logDebug("In-order packet detected. Processing immediately."); // in-order packet receiver which means we restart tracking out of order monotonic increases @@ -129,18 +133,22 @@ class BufferedPacketQueue { * @param callback Callable to handle processed packets. */ template - void processBufferedPackets(Callback &callback) { - while (true) { + void processBufferedPackets(Callback& callback) + { + while (true) + { SeqType nextIdx = mLastPacketIdx + 1; - auto it = mPackets.find(nextIdx); - if (it != mPackets.end()) { + auto it = mPackets.find(nextIdx); + if (it != mPackets.end()) + { logDebug("Found buffered packet with Sequence=%u. Processing.", it->first); callback(it->second.data(), it->second.size()); mLastPacketIdx = it->first; logDebug("Updated lastPacketIdx to %u after processing buffered packet.", mLastPacketIdx); mPackets.erase(it); } - else { + else + { logDebug("No buffered packet found for Sequence=%u.", nextIdx); break; } @@ -156,11 +164,12 @@ class BufferedPacketQueue { * @param callback Callable to handle processed packets. */ template - void handleOutOfOrderPacket(SeqType currPacketIdx, const uint8_t *data, - std::size_t data_length, Callback &callback) { + void handleOutOfOrderPacket(SeqType currPacketIdx, const uint8_t* data, std::size_t data_length, Callback& callback) + { logDebug("Out-of-order packet detected. Sequence=%u", currPacketIdx); - if (isDuplicatePacket(currPacketIdx)) { + if (isDuplicatePacket(currPacketIdx)) + { logWarning("Duplicate packet received with Sequence=%u. Ignoring.", currPacketIdx); return; } @@ -168,27 +177,33 @@ class BufferedPacketQueue { bufferPacket(currPacketIdx, data, data_length); auto dist = calculateDistance(currPacketIdx, mLastPacketIdx); - if(std::abs(dist) < MONOTONIC_THRESHOLD) { + if (std::abs(dist) < MONOTONIC_THRESHOLD) + { // Check for monotonic increases - if (dist > 0) { + if (dist > 0) + { mMonotonicOutOfOrderIncreaseCount++; logDebug("Monotonic increase count: %zu", mMonotonicOutOfOrderIncreaseCount); - if (mMonotonicOutOfOrderIncreaseCount >= MONOTONIC_THRESHOLD) { + if (mMonotonicOutOfOrderIncreaseCount >= MONOTONIC_THRESHOLD) + { restartBuffering(callback, currPacketIdx); // Update lastPacketIdx to the highest sequence index received SeqType newLastIdx = currPacketIdx; - logWarning("Monotonic threshold reached. Updating lastPacketIdx to %u", - newLastIdx); + logWarning("Monotonic threshold reached. Updating lastPacketIdx to %u", newLastIdx); } - } else { + } + else + { // Reset the counter if a non-increasing packet is received mMonotonicOutOfOrderIncreaseCount = 0; logDebug("Non-increasing packet received. Resetting monotonic increase count."); } } // If buffer size exceeds MAX_BUFFER_SIZE, handle buffer overflow - if (mPackets.size() >= MAX_BUFFER_SIZE) { - logWarning("Buffer size exceeded MAX_BUFFER_SIZE (%zu). Processing in-order buffered packets.", MAX_BUFFER_SIZE); + if (mPackets.size() >= MAX_BUFFER_SIZE) + { + logWarning( + "Buffer size exceeded MAX_BUFFER_SIZE (%zu). Processing in-order buffered packets.", MAX_BUFFER_SIZE); restartBuffering(callback, currPacketIdx); } } @@ -198,9 +213,7 @@ class BufferedPacketQueue { * @param currPacketIdx Sequence index of the incoming packet. * @return True if the packet is a duplicate; otherwise, false. */ - bool isDuplicatePacket(SeqType currPacketIdx) const { - return mPackets.find(currPacketIdx) != mPackets.end(); - } + bool isDuplicatePacket(SeqType currPacketIdx) const { return mPackets.find(currPacketIdx) != mPackets.end(); } /** * @brief Buffers an out-of-order packet. @@ -208,8 +221,8 @@ class BufferedPacketQueue { * @param data Pointer to the packet data. * @param data_length Size of the packet data. */ - void bufferPacket(SeqType currPacketIdx, const uint8_t *data, - std::size_t data_length) { + void bufferPacket(SeqType currPacketIdx, const uint8_t* data, std::size_t data_length) + { mPackets[currPacketIdx] = std::vector(data, data + data_length); logDebug("Buffered out-of-order packet. Buffer size: %zu", mPackets.size()); } @@ -220,11 +233,13 @@ class BufferedPacketQueue { * @param callback Callable to handle processed packets. */ template - void restartBuffering(Callback &callback, SeqType currPacketIdx) { + void restartBuffering(Callback& callback, SeqType currPacketIdx) + { // Process as many in-order buffered packets as possible processBufferedPackets(callback); - if (!mPackets.empty()) { + if (!mPackets.empty()) + { logWarning("Processing %zu buffered packets that might be out of order.", mPackets.size()); // Create a vector of iterators to the map elements @@ -232,16 +247,20 @@ class BufferedPacketQueue { sortedPackets.reserve(mPackets.size()); // Populate the vector with iterators to the map elements - for (auto it = mPackets.cbegin(); it != mPackets.cend(); ++it) { + for (auto it = mPackets.cbegin(); it != mPackets.cend(); ++it) + { sortedPackets.push_back(it); } // Sort the vector based on the keys - std::sort(sortedPackets.begin(), sortedPackets.end(), - [](const auto& a, const auto& b) { return a->first < b->first; }); + std::sort( + sortedPackets.begin(), + sortedPackets.end(), + [](const auto& a, const auto& b) { return a->first < b->first; }); // Iterate over the sorted packets and invoke the callback - for (const auto& it : sortedPackets) { + for (const auto& it : sortedPackets) + { const auto& packet = it->second; logDebug("Processing possibly out-of-order buffered packet with Sequence=%u.", it->first); callback(packet.data(), packet.size()); @@ -260,14 +279,16 @@ class BufferedPacketQueue { * @param b Second sequence number. * @return True if sequence a is less than b, accounting for wrap-around. */ - bool seqLessThan(SeqType a, SeqType b) const { + bool seqLessThan(SeqType a, SeqType b) const + { bool result = calculateDistance(a, b) > 0; logDebug("seqLessThan: a=%u, b=%u, result=%s", a, b, result ? "true" : "false"); return result; } template - typename std::make_signed::type to_signed(T value) { + typename std::make_signed::type to_signed(T value) + { static_assert(std::is_unsigned::value, "Type must be unsigned"); using SignedType = typename std::make_signed::type; return static_cast(value); @@ -279,7 +300,8 @@ class BufferedPacketQueue { * @param to Destination sequence number. * @return The distance from 'from' to 'to'. */ - static std::make_signed::type calculateDistance(SeqType a, SeqType b) { + static std::make_signed::type calculateDistance(SeqType a, SeqType b) + { return static_cast::type>(b - a); } @@ -288,7 +310,8 @@ class BufferedPacketQueue { * @param format printf-style format string. * @param ... Additional arguments. */ - void logDebug(const char* format, ...) const { + void logDebug(const char* format, ...) const + { return; va_list args; va_start(args, format); @@ -301,7 +324,8 @@ class BufferedPacketQueue { * @param format printf-style format string. * @param ... Additional arguments. */ - void logWarning(const char* format, ...) const { + void logWarning(const char* format, ...) const + { va_list args; va_start(args, format); __android_log_vprint(ANDROID_LOG_WARN, BUFFERED_QUEUE_LOG_TAG, format, args); diff --git a/app/videonative/src/main/cpp/NALU/H26X.hpp b/app/videonative/src/main/cpp/NALU/H26X.hpp index b1f59a8..4a943b4 100644 --- a/app/videonative/src/main/cpp/NALU/H26X.hpp +++ b/app/videonative/src/main/cpp/NALU/H26X.hpp @@ -10,320 +10,326 @@ // namespaces for H264 H265 helper // A H265 NALU is kind of similar to a H264 NALU in that it has the same [0,0,0,1] prefix -namespace RBSPHelper { - // The rbsp buffer starts after 5 bytes for h264 (4 bytes prefix and 1 byte unit header) - // and after 6 bytes for h265 (4 bytes prefix and 2 byte unit header) - // the h264bitstream nal_to_rbsp function is buggy ! - // Also, its implementation unescapes the NAL unit header which is wrong (only the payload should be unescaped) - // escaping/unescaping rbsp is the same for h264 and h265 - - static std::vector - unescapeRbsp(const uint8_t *rbsp_buff, const std::size_t rbsp_buff_size) { - auto ret = h265nal::UnescapeRbsp(rbsp_buff, rbsp_buff_size); - return ret; - } +namespace RBSPHelper +{ +// The rbsp buffer starts after 5 bytes for h264 (4 bytes prefix and 1 byte unit header) +// and after 6 bytes for h265 (4 bytes prefix and 2 byte unit header) +// the h264bitstream nal_to_rbsp function is buggy ! +// Also, its implementation unescapes the NAL unit header which is wrong (only the payload should be unescaped) +// escaping/unescaping rbsp is the same for h264 and h265 + +static std::vector unescapeRbsp(const uint8_t* rbsp_buff, const std::size_t rbsp_buff_size) +{ + auto ret = h265nal::UnescapeRbsp(rbsp_buff, rbsp_buff_size); + return ret; +} - static std::vector unescapeRbsp(const std::vector &rbspData) { - return unescapeRbsp(rbspData.data(), rbspData.size()); +static std::vector unescapeRbsp(const std::vector& rbspData) +{ + return unescapeRbsp(rbspData.data(), rbspData.size()); +} + +static std::vector escapeRbsp(const std::vector& rbspBuff) +{ + std::vector rbspBuffEscaped; + rbspBuffEscaped.resize(rbspBuff.size() + 32); + int rbspSize = rbspBuff.size(); + int rbspBuffEscapedSize = rbspBuffEscaped.size(); + auto tmp = rbsp_to_nal(rbspBuff.data(), &rbspSize, rbspBuffEscaped.data(), &rbspBuffEscapedSize); + assert(tmp > 0); + rbspBuffEscaped.resize(tmp); + // workaround h264bitstream library + return std::vector(&rbspBuffEscaped.data()[1], &rbspBuffEscaped.data()[1] + rbspBuffEscaped.size() - 1); +} + +static void test_unescape_escape(const std::vector& rbspDataEscaped) +{ + auto unescapedData = unescapeRbsp(rbspDataEscaped.data(), rbspDataEscaped.size()); + auto unescapedThenEscapedData = escapeRbsp(unescapedData); + MLOGD << "Y1:" << StringHelper::vectorAsString(rbspDataEscaped); + MLOGD << "Y2:" << StringHelper::vectorAsString(unescapedData); + MLOGD << "Y3:" << StringHelper::vectorAsString(unescapedThenEscapedData); + // check if the data stayed the same after unescaping then escaping it again + assert(rbspDataEscaped.size() == unescapedThenEscapedData.size()); + assert(std::memcmp(rbspDataEscaped.data(), unescapedThenEscapedData.data(), rbspDataEscaped.size()) == 0); +} +} // namespace RBSPHelper + +namespace H264 +{ +// reverse order due to architecture +// The nal unit header is not part of the rbsp-escaped bitstream and therefore can be read without unescaping anything +typedef struct nal_unit_header +{ + uint8_t nal_unit_type : 5; + uint8_t nal_ref_idc : 2; + uint8_t forbidden_zero_bit : 1; + + std::string asString() const + { + std::stringstream ss; + ss << "nal_unit_type:" << (int) nal_unit_type << " nal_ref_idc:" << (int) nal_ref_idc + << " forbidden_zero_bit:" << (int) forbidden_zero_bit; + return ss.str(); } +} __attribute__((packed)) nal_unit_header_t; +static_assert(sizeof(nal_unit_header_t) == 1); + +// Parse raw NALU data into an sps struct (using the h264bitstream library) +class SPS +{ + public: + nal_unit_header_t nal_header; + sps_t parsed; - static std::vector escapeRbsp(const std::vector &rbspBuff) { - std::vector rbspBuffEscaped; - rbspBuffEscaped.resize(rbspBuff.size() + 32); - int rbspSize = rbspBuff.size(); - int rbspBuffEscapedSize = rbspBuffEscaped.size(); - auto tmp = rbsp_to_nal(rbspBuff.data(), &rbspSize, rbspBuffEscaped.data(), - &rbspBuffEscapedSize); - assert(tmp > 0); - rbspBuffEscaped.resize(tmp); - // workaround h264bitstream library - return std::vector(&rbspBuffEscaped.data()[1], - &rbspBuffEscaped.data()[1] + rbspBuffEscaped.size() - 1); + public: + // data buffer= NALU data with prefix + SPS(const uint8_t* nalu_data, size_t data_len) + { + memcpy(&nal_header, &nalu_data[4], 1); + assert(nal_header.forbidden_zero_bit == 0); + assert(nal_header.nal_unit_type == NAL_UNIT_TYPE_SPS); + auto rbsp_buf = RBSPHelper::unescapeRbsp(&nalu_data[5], data_len - 5); + BitStream b(rbsp_buf); + read_seq_parameter_set_rbsp(&parsed, b.bs_t()); + read_rbsp_trailing_bits(b.bs_t()); } - static void test_unescape_escape(const std::vector &rbspDataEscaped) { - auto unescapedData = unescapeRbsp(rbspDataEscaped.data(), rbspDataEscaped.size()); - auto unescapedThenEscapedData = escapeRbsp(unescapedData); - MLOGD << "Y1:" << StringHelper::vectorAsString(rbspDataEscaped); - MLOGD << "Y2:" << StringHelper::vectorAsString(unescapedData); - MLOGD << "Y3:" << StringHelper::vectorAsString(unescapedThenEscapedData); - // check if the data stayed the same after unescaping then escaping it again - assert(rbspDataEscaped.size() == unescapedThenEscapedData.size()); - assert(std::memcmp(rbspDataEscaped.data(), unescapedThenEscapedData.data(), - rbspDataEscaped.size()) == 0); + std::array getWidthHeightPx() const + { + int Width = ((parsed.pic_width_in_mbs_minus1 + 1) * 16) - parsed.frame_crop_right_offset * 2 - + parsed.frame_crop_left_offset * 2; + int Height = ((2 - parsed.frame_mbs_only_flag) * (parsed.pic_height_in_map_units_minus1 + 1) * 16) - + (parsed.frame_crop_bottom_offset * 2) - (parsed.frame_crop_top_offset * 2); + return {Width, Height}; } -} -namespace H264 { - // reverse order due to architecture - // The nal unit header is not part of the rbsp-escaped bitstream and therefore can be read without unescaping anything - typedef struct nal_unit_header { - uint8_t nal_unit_type: 5; - uint8_t nal_ref_idc: 2; - uint8_t forbidden_zero_bit: 1; - - std::string asString() const { - std::stringstream ss; - ss << "nal_unit_type:" << (int) nal_unit_type << " nal_ref_idc:" << (int) nal_ref_idc - << " forbidden_zero_bit:" << (int) forbidden_zero_bit; - return ss.str(); - } - }__attribute__ ((packed)) nal_unit_header_t; - static_assert(sizeof(nal_unit_header_t) == 1); - - // Parse raw NALU data into an sps struct (using the h264bitstream library) - class SPS { - public: - nal_unit_header_t nal_header; - sps_t parsed; - public: - // data buffer= NALU data with prefix - SPS(const uint8_t *nalu_data, size_t data_len) { - memcpy(&nal_header, &nalu_data[4], 1); - assert(nal_header.forbidden_zero_bit == 0); - assert(nal_header.nal_unit_type == NAL_UNIT_TYPE_SPS); - auto rbsp_buf = RBSPHelper::unescapeRbsp(&nalu_data[5], data_len - 5); - BitStream b(rbsp_buf); - read_seq_parameter_set_rbsp(&parsed, b.bs_t()); - read_rbsp_trailing_bits(b.bs_t()); - } + std::string asString() const { return H264Stream::spsAsString(&parsed); } - std::array getWidthHeightPx() const { - int Width = ((parsed.pic_width_in_mbs_minus1 + 1) * 16) - - parsed.frame_crop_right_offset * 2 - parsed.frame_crop_left_offset * 2; - int Height = ((2 - parsed.frame_mbs_only_flag) * - (parsed.pic_height_in_map_units_minus1 + 1) * 16) - - (parsed.frame_crop_bottom_offset * 2) - (parsed.frame_crop_top_offset * 2); - return {Width, Height}; - } + // --------------------------------------------- crude hacking -------------------------------------------- + std::vector asNALU() const + { + std::vector naluBuff; + // write prefix and nalu header + naluBuff.resize(5); + naluBuff[0] = 0; + naluBuff[1] = 0; + naluBuff[2] = 0; + naluBuff[3] = 1; + std::memcpy(&naluBuff.data()[4], &nal_header, 1); + // write sps payload into rbspBuff + std::vector rbspBuff; + rbspBuff.resize(14 + 64 * 4); + // MLOGD<<"rbspbuffSize"< asNALU() const { - std::vector naluBuff; - // write prefix and nalu header - naluBuff.resize(5); - naluBuff[0] = 0; - naluBuff[1] = 0; - naluBuff[2] = 0; - naluBuff[3] = 1; - std::memcpy(&naluBuff.data()[4], &nal_header, 1); - // write sps payload into rbspBuff - std::vector rbspBuff; - rbspBuff.resize(14 + 64 * 4); - //MLOGD<<"rbspbuffSize"<(nalu_data, nalu_data + data_len)); - MLOGD << "Z2:" << StringHelper::vectorAsString(naluBuff); - assert(data_len == naluBuff.size()); - assert(std::memcmp(nalu_data, naluBuff.data(), data_len) == 0); + parsed.vui_parameters_present_flag = 1; + parsed.vui.aspect_ratio_info_present_flag = 0; + parsed.vui.aspect_ratio_idc = 0; + parsed.vui.sar_width = 0; + parsed.vui.sar_height = 0; + parsed.vui.overscan_info_present_flag = 0; + parsed.vui.overscan_appropriate_flag = 0; + parsed.vui.video_signal_type_present_flag = 1; + parsed.vui.video_format = 5; + parsed.vui.video_full_range_flag = 0; + parsed.vui.colour_description_present_flag = 1; + parsed.vui.colour_primaries = 0; + parsed.vui.transfer_characteristics = 0; + parsed.vui.matrix_coefficients = 5; + parsed.vui.chroma_loc_info_present_flag = 0; + parsed.vui.chroma_sample_loc_type_top_field = 0; + parsed.vui.chroma_sample_loc_type_bottom_field = 0; + parsed.vui.timing_info_present_flag = 1; + parsed.vui.num_units_in_tick = 1; + parsed.vui.time_scale = 120; + parsed.vui.fixed_frame_rate_flag = 1; + parsed.vui.nal_hrd_parameters_present_flag = 0; + parsed.vui.vcl_hrd_parameters_present_flag = 0; + parsed.vui.low_delay_hrd_flag = 0; + parsed.vui.pic_struct_present_flag = 0; + parsed.vui.bitstream_restriction_flag = 1; + parsed.vui.motion_vectors_over_pic_boundaries_flag = 1; + parsed.vui.max_bytes_per_pic_denom = 0; + parsed.vui.max_bits_per_mb_denom = 0; + parsed.vui.log2_max_mv_length_horizontal = 16; + parsed.vui.log2_max_mv_length_vertical = 16; + parsed.vui.num_reorder_frames = 0; + parsed.vui.max_dec_frame_buffering = 1; } +}; + +static void testSPSConversion(const uint8_t* nalu_data, size_t data_len) +{ + auto sps = H264::SPS(nalu_data, data_len); + auto naluBuff = sps.asNALU(); + MLOGD << "Z1:" << StringHelper::vectorAsString(std::vector(nalu_data, nalu_data + data_len)); + MLOGD << "Z2:" << StringHelper::vectorAsString(naluBuff); + assert(data_len == naluBuff.size()); + assert(std::memcmp(nalu_data, naluBuff.data(), data_len) == 0); +} // --------------------------------------------- crude hacking -------------------------------------------- - class PPS { - public: - nal_unit_header_t nal_header; - pps_t parsed; - public: - // data buffer= NALU data with prefix - PPS(const uint8_t *nalu_data, size_t data_len) { - memcpy(&nal_header, &nalu_data[4], 1); - assert(nal_header.forbidden_zero_bit == 0); - assert(nal_header.nal_unit_type == NAL_UNIT_TYPE_PPS); - auto rbsp_buf = RBSPHelper::unescapeRbsp(&nalu_data[5], data_len - 5); - BitStream b(rbsp_buf); - read_pic_parameter_set_rbsp(&parsed, b.bs_t()); - read_rbsp_trailing_bits(b.bs_t()); - } +class PPS +{ + public: + nal_unit_header_t nal_header; + pps_t parsed; - std::string asString() const { - return H264Stream::ppsAsString(parsed); - } - }; - - class Slice { - public: - nal_unit_header_t nal_header; - slice_header_t parsed; - public: - // data buffer= NALU data with prefix - Slice(const uint8_t *nalu_data, size_t data_len) { - memcpy(&nal_header, &nalu_data[4], 1); - assert(nal_header.forbidden_zero_bit == 0); - assert(nal_header.nal_unit_type == NAL_UNIT_TYPE_CODED_SLICE_IDR || - nal_header.nal_unit_type == NAL_UNIT_TYPE_CODED_SLICE_NON_IDR); - auto rbsp_buf = RBSPHelper::unescapeRbsp(&nalu_data[5], data_len - 5); - BitStream b(rbsp_buf); - // parsing - parsed.first_mb_in_slice = b.read_ue(); - parsed.slice_type = b.read_ue(); - parsed.pic_parameter_set_id = b.read_ue(); - parsed.frame_num = b.read_ue(); - } + public: + // data buffer= NALU data with prefix + PPS(const uint8_t* nalu_data, size_t data_len) + { + memcpy(&nal_header, &nalu_data[4], 1); + assert(nal_header.forbidden_zero_bit == 0); + assert(nal_header.nal_unit_type == NAL_UNIT_TYPE_PPS); + auto rbsp_buf = RBSPHelper::unescapeRbsp(&nalu_data[5], data_len - 5); + BitStream b(rbsp_buf); + read_pic_parameter_set_rbsp(&parsed, b.bs_t()); + read_rbsp_trailing_bits(b.bs_t()); + } - std::string asString() const { - std::stringstream ss; - ss << "[first_mb_in_slice=" << parsed.first_mb_in_slice << ",slice_type=" - << parsed.slice_type << ",pic_parameter_set_id=" << parsed.pic_parameter_set_id - << "frame_num=" << parsed.frame_num << "]"; - return ss.str(); - } - }; - - /*class SEI{ - public: - nal_unit_header_t nal_header; - slice_header_t parsed; - public: - // data buffer= NALU data with prefix - Slice(const uint8_t* nalu_data,size_t data_len){ - memcpy(&nal_header,&nalu_data[4],1); - assert(nal_header.forbidden_zero_bit==0); - assert(nal_header.nal_unit_type==NAL_UNIT_TYPE_CODED_SLICE_IDR || nal_header.nal_unit_type==NAL_UNIT_TYPE_CODED_SLICE_NON_IDR); - auto rbsp_buf= RBSPHelper::unescapeRbsp(&nalu_data[5], data_len-5); - BitStream b(rbsp_buf); - // parsing - parsed.first_mb_in_slice=b.read_ue(); - parsed.slice_type=b.read_ue(); - parsed.pic_parameter_set_id=b.read_ue(); - parsed.frame_num=b.read_ue(); - } - std::string asString()const{ - std::stringstream ss; - return ss.str(); - } - };*/ - - // this is the data for an h264 AUD unit - static std::array EXAMPLE_AUD = { - 0, 0, 0, 1, 9, 48 - }; - static std::array EXAMPLE_SEI = { - 0, 0, 0, 1, 6, 5, 255, 255, 167, 220, 69, 233, 189, 230, 217, 72, 183, 150, 44, 216, 32, - 217, 35, 238, 239, 120, 50, 54, 52, 32, 45, 32, 99, 111, 114, 101, 32, 49, 52, 56, 32, - 45, 32, 72, 46, 50, 54, 52, 47, 77, 80, 69, 71, 45, 52, 32, 65, 86, 67, 32, 99, 111, - 100, 101, 99, 32, 45, 32, 67, 111, 112, 121, 108, 101, 102, 116, 32, 50, 48, 48, 51, 45, - 50, 48, 49, 54, 32, 45, 32, 104, 116, 116, 112, 58, 47, 47, 119, 119, 119, 46, 118, 105, - 100, 101, 111, 108, 97, 110, 46, 111, 114, 103, 47, 120, 50, 54, 52, 46, 104, 116, 109, - 108, 32, 45, 32, 111, 112, 116, 105, 111, 110, 115, 58, 32, 99, 97, 98, 97, 99, 61, 48, - 32, 114, 101, 102, 61, 51, 32, 100, 101, 98, 108, 111, 99, 107, 61, 49, 58, 48, 58, 48, - 32, 97, 110, 97, 108, 121, 115, 101, 61, 48, 120, 49, 58, 48, 120, 49, 49, 49, 32, 109, - 101, 61, 104, 101, 120, 32, 115, 117, 98, 109, 101, 61, 55, 32, 112, 115, 121, 61, 49, - 32, 112, 115, 121, 95, 114, 100, 61, 49, 46, 48, 48, 58, 48, 46, 48, 48, 32, 109, 105, - 120, 101, 100, 95, 114, 101, 102, 61, 49, 32, 109, 101, 95, 114, 97, 110, 103, 101, 61, - 49, 54, 32, 99, 104, 114, 111, 109, 97, 95, 109, 101, 61, 49, 32, 116, 114, 101, 108, - 108, 105, 115, 61, 49, 32, 56, 120, 56, 100, 99, 116, 61, 48, 32, 99, 113, 109, 61, 48, - 32, 100, 101, 97, 100, 122, 111, 110, 101, 61, 50, 49, 44, 49, 49, 32, 102, 97, 115, - 116, - 5, 112, 115, 107, 105, 112, 61, 49, 32, 99, 104, 114, 111, 109, 97, 95, 113, 112, 95, - 111, 102, 102, 115, 101, 116, 61, 45, 50, 32, 116, 104, 114, 101, 97, 100, 115, 61, 54, - 32, 108, 111, 111, 107, 97, 104, 101, 97, 100, 95, 116, 104, 114, 101, 97, 100, 115, 61, - 49, 32, 115, 108, 105, 99, 101, 100, 95, 116, 104, 114, 101, 97, 100, 115, 61, 48, 32, - 110, 114, 61, 48, 32, 100, 101, 99, 105, 109, 97, 116, 101, 61, 49, 32, 105, 110, 116, - 101, 114, 108, 97, 99, 101, 100, 61, 48, 32, 98, 108, 117, 114, 97, 121, 95, 99, 111, - 109, 112, 97, 116, 61, 48, 32, 99, 111, 110, 115, 116, 114, 97, 105, 110, 101, 100, 95, - 105, 110, 116, 114, 97, 61, 48, 32, 98, 102, 114, 97, 109, 101, 115, 61, 48, 32, 119, - 101, 105, 103, 104, 116, 112, 61, 48, 32, 107, 101, 121, 105, 110, 116, 61, 50, 57, 57, - 32, 107, 101, 121, 105, 110, 116, 95, 109, 105, 110, 61, 50, 57, 32, 115, 99, 101, 110, - 101, 99, 117, 116, 61, 52, 48, 32, 105, 110, 116, 114, 97, 95, 114, 101, 102, 114, 101, - 115, 104, 61, 48, 32, 114, 99, 95, 108, 111, 111, 107, 97, 104, 101, 97, 100, 61, 52, - 48, 32, 114, 99, 61, 99, 98, 114, 32, 109, 98, 116, 114, 101, 101, 61, 49, 32, 98, 105, - 116, 114, 97, 116, 101, 61, 50, 48, 52, 56, 32, 114, 97, 116, 101, 116, 111, 108, 61, - 49, 46, 48, 32, 113, 99, 111, 109, 112, 61, 48, 46, 54, 48, 32, 113, 112, 109, 105, 110, - 1, 48, 32, 113, 112, 109, 97, 120, 61, 54, 57, 32, 113, 112, 115, 116, 101, 112, 61, 52, - 32, 118, 98, 118, 95, 109, 97, 120, 114, 97, 116, 101, 61, 50, 48, 52, 56, 32, 118, 98, - 118, 95, 98, 117, 102, 115, 105, 122, 101, 61, 49, 50, 50, 56, 32, 110, 97, 108, 95, - 104, 114, 100, 61, 110, 111, 110, 101, 32, 102, 105, 108, 108, 101, 114, 61, 48, 32, - 105, 112, 95, 114, 97, 116, 105, 111, 61, 49, 46, 52, 48, 32, 97, 113, 61, 49, 58, 49, - 46, 48, 48, 0, 128 - }; + std::string asString() const { return H264Stream::ppsAsString(parsed); } +}; -} +class Slice +{ + public: + nal_unit_header_t nal_header; + slice_header_t parsed; -namespace H265 { - typedef struct nal_unit_header { - uint8_t forbidden_zero_bit: 1; - uint8_t nal_unit_type: 6; - uint8_t nuh_layer_id: 6; - uint8_t nuh_temporal_id_plus1: 3; - }__attribute__ ((packed)) nal_unit_header_t; - static_assert(sizeof(nal_unit_header_t) == 2); + public: + // data buffer= NALU data with prefix + Slice(const uint8_t* nalu_data, size_t data_len) + { + memcpy(&nal_header, &nalu_data[4], 1); + assert(nal_header.forbidden_zero_bit == 0); + assert( + nal_header.nal_unit_type == NAL_UNIT_TYPE_CODED_SLICE_IDR || + nal_header.nal_unit_type == NAL_UNIT_TYPE_CODED_SLICE_NON_IDR); + auto rbsp_buf = RBSPHelper::unescapeRbsp(&nalu_data[5], data_len - 5); + BitStream b(rbsp_buf); + // parsing + parsed.first_mb_in_slice = b.read_ue(); + parsed.slice_type = b.read_ue(); + parsed.pic_parameter_set_id = b.read_ue(); + parsed.frame_num = b.read_ue(); + } -} + std::string asString() const + { + std::stringstream ss; + ss << "[first_mb_in_slice=" << parsed.first_mb_in_slice << ",slice_type=" << parsed.slice_type + << ",pic_parameter_set_id=" << parsed.pic_parameter_set_id << "frame_num=" << parsed.frame_num << "]"; + return ss.str(); + } +}; + +/*class SEI{ +public: + nal_unit_header_t nal_header; + slice_header_t parsed; +public: + // data buffer= NALU data with prefix + Slice(const uint8_t* nalu_data,size_t data_len){ + memcpy(&nal_header,&nalu_data[4],1); + assert(nal_header.forbidden_zero_bit==0); + assert(nal_header.nal_unit_type==NAL_UNIT_TYPE_CODED_SLICE_IDR || +nal_header.nal_unit_type==NAL_UNIT_TYPE_CODED_SLICE_NON_IDR); auto rbsp_buf= RBSPHelper::unescapeRbsp(&nalu_data[5], +data_len-5); BitStream b(rbsp_buf); + // parsing + parsed.first_mb_in_slice=b.read_ue(); + parsed.slice_type=b.read_ue(); + parsed.pic_parameter_set_id=b.read_ue(); + parsed.frame_num=b.read_ue(); + } + std::string asString()const{ + std::stringstream ss; + return ss.str(); + } +};*/ + +// this is the data for an h264 AUD unit +static std::array EXAMPLE_AUD = {0, 0, 0, 1, 9, 48}; +static std::array EXAMPLE_SEI = { + 0, 0, 0, 1, 6, 5, 255, 255, 167, 220, 69, 233, 189, 230, 217, 72, 183, 150, 44, 216, 32, 217, 35, + 238, 239, 120, 50, 54, 52, 32, 45, 32, 99, 111, 114, 101, 32, 49, 52, 56, 32, 45, 32, 72, 46, 50, + 54, 52, 47, 77, 80, 69, 71, 45, 52, 32, 65, 86, 67, 32, 99, 111, 100, 101, 99, 32, 45, 32, 67, + 111, 112, 121, 108, 101, 102, 116, 32, 50, 48, 48, 51, 45, 50, 48, 49, 54, 32, 45, 32, 104, 116, 116, + 112, 58, 47, 47, 119, 119, 119, 46, 118, 105, 100, 101, 111, 108, 97, 110, 46, 111, 114, 103, 47, 120, 50, + 54, 52, 46, 104, 116, 109, 108, 32, 45, 32, 111, 112, 116, 105, 111, 110, 115, 58, 32, 99, 97, 98, 97, + 99, 61, 48, 32, 114, 101, 102, 61, 51, 32, 100, 101, 98, 108, 111, 99, 107, 61, 49, 58, 48, 58, 48, + 32, 97, 110, 97, 108, 121, 115, 101, 61, 48, 120, 49, 58, 48, 120, 49, 49, 49, 32, 109, 101, 61, 104, + 101, 120, 32, 115, 117, 98, 109, 101, 61, 55, 32, 112, 115, 121, 61, 49, 32, 112, 115, 121, 95, 114, 100, + 61, 49, 46, 48, 48, 58, 48, 46, 48, 48, 32, 109, 105, 120, 101, 100, 95, 114, 101, 102, 61, 49, 32, + 109, 101, 95, 114, 97, 110, 103, 101, 61, 49, 54, 32, 99, 104, 114, 111, 109, 97, 95, 109, 101, 61, 49, + 32, 116, 114, 101, 108, 108, 105, 115, 61, 49, 32, 56, 120, 56, 100, 99, 116, 61, 48, 32, 99, 113, 109, + 61, 48, 32, 100, 101, 97, 100, 122, 111, 110, 101, 61, 50, 49, 44, 49, 49, 32, 102, 97, 115, 116, 5, + 112, 115, 107, 105, 112, 61, 49, 32, 99, 104, 114, 111, 109, 97, 95, 113, 112, 95, 111, 102, 102, 115, 101, + 116, 61, 45, 50, 32, 116, 104, 114, 101, 97, 100, 115, 61, 54, 32, 108, 111, 111, 107, 97, 104, 101, 97, + 100, 95, 116, 104, 114, 101, 97, 100, 115, 61, 49, 32, 115, 108, 105, 99, 101, 100, 95, 116, 104, 114, 101, + 97, 100, 115, 61, 48, 32, 110, 114, 61, 48, 32, 100, 101, 99, 105, 109, 97, 116, 101, 61, 49, 32, 105, + 110, 116, 101, 114, 108, 97, 99, 101, 100, 61, 48, 32, 98, 108, 117, 114, 97, 121, 95, 99, 111, 109, 112, + 97, 116, 61, 48, 32, 99, 111, 110, 115, 116, 114, 97, 105, 110, 101, 100, 95, 105, 110, 116, 114, 97, 61, + 48, 32, 98, 102, 114, 97, 109, 101, 115, 61, 48, 32, 119, 101, 105, 103, 104, 116, 112, 61, 48, 32, 107, + 101, 121, 105, 110, 116, 61, 50, 57, 57, 32, 107, 101, 121, 105, 110, 116, 95, 109, 105, 110, 61, 50, 57, + 32, 115, 99, 101, 110, 101, 99, 117, 116, 61, 52, 48, 32, 105, 110, 116, 114, 97, 95, 114, 101, 102, 114, + 101, 115, 104, 61, 48, 32, 114, 99, 95, 108, 111, 111, 107, 97, 104, 101, 97, 100, 61, 52, 48, 32, 114, + 99, 61, 99, 98, 114, 32, 109, 98, 116, 114, 101, 101, 61, 49, 32, 98, 105, 116, 114, 97, 116, 101, 61, + 50, 48, 52, 56, 32, 114, 97, 116, 101, 116, 111, 108, 61, 49, 46, 48, 32, 113, 99, 111, 109, 112, 61, + 48, 46, 54, 48, 32, 113, 112, 109, 105, 110, 1, 48, 32, 113, 112, 109, 97, 120, 61, 54, 57, 32, 113, + 112, 115, 116, 101, 112, 61, 52, 32, 118, 98, 118, 95, 109, 97, 120, 114, 97, 116, 101, 61, 50, 48, 52, + 56, 32, 118, 98, 118, 95, 98, 117, 102, 115, 105, 122, 101, 61, 49, 50, 50, 56, 32, 110, 97, 108, 95, + 104, 114, 100, 61, 110, 111, 110, 101, 32, 102, 105, 108, 108, 101, 114, 61, 48, 32, 105, 112, 95, 114, 97, + 116, 105, 111, 61, 49, 46, 52, 48, 32, 97, 113, 61, 49, 58, 49, 46, 48, 48, 0, 128}; + +} // namespace H264 + +namespace H265 +{ +typedef struct nal_unit_header +{ + uint8_t forbidden_zero_bit : 1; + uint8_t nal_unit_type : 6; + uint8_t nuh_layer_id : 6; + uint8_t nuh_temporal_id_plus1 : 3; +} __attribute__((packed)) nal_unit_header_t; +static_assert(sizeof(nal_unit_header_t) == 2); + +} // namespace H265 -#endif //FPVUE_H264_H +#endif // FPVUE_H264_H diff --git a/app/videonative/src/main/cpp/NALU/KeyFrameFinder.hpp b/app/videonative/src/main/cpp/NALU/KeyFrameFinder.hpp index b65433c..8f8d1e0 100644 --- a/app/videonative/src/main/cpp/NALU/KeyFrameFinder.hpp +++ b/app/videonative/src/main/cpp/NALU/KeyFrameFinder.hpp @@ -5,74 +5,89 @@ #ifndef FPVUE_KEYFRAMEFINDER_HPP #define FPVUE_KEYFRAMEFINDER_HPP -#include "NALU.hpp" +#include #include #include "../helper/AndroidLogger.hpp" -#include +#include "NALU.hpp" // Takes a continuous stream of NALUs and save SPS / PPS data // For later use -class KeyFrameFinder { -private: +class KeyFrameFinder +{ + private: std::unique_ptr SPS = nullptr; std::unique_ptr PPS = nullptr; // VPS are only used in H265 std::unique_ptr VPS = nullptr; -public: - bool saveIfKeyFrame(const NALU &nalu) { - if (nalu.getSize() <= 0)return false; - if (nalu.isSPS()) { + + public: + bool saveIfKeyFrame(const NALU& nalu) + { + if (nalu.getSize() <= 0) return false; + if (nalu.isSPS()) + { SPS = std::make_unique(nalu); - //MLOGD<<"SPS found"; - //MLOGD<(nalu); - //MLOGD<<"PPS found"; + // MLOGD<<"PPS found"; return true; - } else if (nalu.IS_H265_PACKET && nalu.isVPS()) { + } + else if (nalu.IS_H265_PACKET && nalu.isVPS()) + { VPS = std::make_unique(nalu); - //MLOGD<<"VPS found"; + // MLOGD<<"VPS found"; return true; } - //qDebug()<<"not a keyframe"<<(int)nalu.getDataWithoutPrefix()[0]; + // qDebug()<<"not a keyframe"<<(int)nalu.getDataWithoutPrefix()[0]; return false; } // H264 needs sps and pps // H265 needs sps,pps and vps - bool allKeyFramesAvailable(const bool IS_H265 = false) { - if (IS_H265) { + bool allKeyFramesAvailable(const bool IS_H265 = false) + { + if (IS_H265) + { return SPS != nullptr && PPS != nullptr && VPS != nullptr; } return SPS != nullptr && PPS != nullptr; } - //SPS - const NALU &getCSD0() const { + // SPS + const NALU& getCSD0() const + { assert(SPS); return SPS->get_nal(); } - const NALU &getCSD1() const { + const NALU& getCSD1() const + { assert(PPS); return PPS->get_nal(); } - const NALU &getVPS() const { + const NALU& getVPS() const + { assert(VPS); return VPS->get_nal(); } - static void appendNaluData(std::vector &buff, const NALU &nalu) { + static void appendNaluData(std::vector& buff, const NALU& nalu) + { buff.insert(buff.begin(), nalu.getData(), nalu.getData() + nalu.getSize()); } - void reset() { + void reset() + { SPS = nullptr; PPS = nullptr; VPS = nullptr; } }; -#endif //FPVUE_KEYFRAMEFINDER_HPP +#endif // FPVUE_KEYFRAMEFINDER_HPP diff --git a/app/videonative/src/main/cpp/NALU/NALU.hpp b/app/videonative/src/main/cpp/NALU/NALU.hpp index b1196bf..3a2573a 100644 --- a/app/videonative/src/main/cpp/NALU/NALU.hpp +++ b/app/videonative/src/main/cpp/NALU/NALU.hpp @@ -5,44 +5,47 @@ #ifndef FPVUE_ANDROID_NALU_H #define FPVUE_ANDROID_NALU_H -//https://github.com/Dash-Industry-Forum/Conformance-and-reference-source/blob/master/conformance/TSValidator/h264bitstream/h264_stream.h +// https://github.com/Dash-Industry-Forum/Conformance-and-reference-source/blob/master/conformance/TSValidator/h264bitstream/h264_stream.h -#include -#include -#include -#include -#include -#include -#include -#include #include -#include -#include +#include +#include +#include // for uint8_t +#include #include +#include +#include +#include #include -#include // for uint8_t +#include +#include +#include #include "NALUnitType.hpp" // dependency could be easily removed again #include -#include #include +#include #include "NALUnitType.hpp" /** - * NOTE: NALU only takes a c-style data pointer - it does not do any memory management. Use NALUBuffer if you need to store a NALU. - * Since H264 and H265 are that similar, we use this class for both (make sure to not call methds only supported on h265 with a h264 nalu,though) - * The constructor of the NALU does some really basic validation - make sure the parser never produces a NALU where this validation would fail + * NOTE: NALU only takes a c-style data pointer - it does not do any memory management. Use NALUBuffer if you need to + * store a NALU. Since H264 and H265 are that similar, we use this class for both (make sure to not call methds only + * supported on h265 with a h264 nalu,though) The constructor of the NALU does some really basic validation - make sure + * the parser never produces a NALU where this validation would fail */ -class NALU { -public: - NALU(const uint8_t *data1, size_t data_len1, const bool IS_H265_PACKET1 = false, - const std::chrono::steady_clock::time_point creationTime = std::chrono::steady_clock::now()) - : - m_data(data1), m_data_len(data_len1), IS_H265_PACKET(IS_H265_PACKET1), - creationTime{creationTime} { +class NALU +{ + public: + NALU( + const uint8_t* data1, + size_t data_len1, + const bool IS_H265_PACKET1 = false, + const std::chrono::steady_clock::time_point creationTime = std::chrono::steady_clock::now()) + : m_data(data1), m_data_len(data_len1), IS_H265_PACKET(IS_H265_PACKET1), creationTime{creationTime} + { assert(hasValidPrefix()); assert(getSize() >= getMinimumNaluSize(IS_H265_PACKET1)); m_nalu_prefix_size = get_nalu_prefix_size(); @@ -55,147 +58,157 @@ class NALU { static constexpr const auto NALU_MAXLEN = 1024 * 1024; // Application should re-use NALU_BUFFER to avoid memory allocations using NALU_BUFFER = std::array; -private: - const uint8_t *m_data; - const size_t m_data_len; - int m_nalu_prefix_size; -public: + + private: + const uint8_t* m_data; + const size_t m_data_len; + int m_nalu_prefix_size; + + public: const bool IS_H265_PACKET; // creation time is used to measure latency const std::chrono::steady_clock::time_point creationTime; -public: + + public: // returns true if starts with 0001, false otherwise - bool hasValidPrefixLong() const { - return m_data[0] == 0 && m_data[1] == 0 && m_data[2] == 0 && m_data[3] == 1; - } + bool hasValidPrefixLong() const { return m_data[0] == 0 && m_data[1] == 0 && m_data[2] == 0 && m_data[3] == 1; } // returns true if starts with 001 (short prefix), false otherwise - bool hasValidPrefixShort() const { - return m_data[0] == 0 && m_data[1] == 0 && m_data[2] == 1; - } + bool hasValidPrefixShort() const { return m_data[0] == 0 && m_data[1] == 0 && m_data[2] == 1; } - bool hasValidPrefix() const { - return hasValidPrefixLong() || hasValidPrefixShort(); - } + bool hasValidPrefix() const { return hasValidPrefixLong() || hasValidPrefixShort(); } - int get_nalu_prefix_size() const { - if (hasValidPrefixLong())return 4; + int get_nalu_prefix_size() const + { + if (hasValidPrefixLong()) return 4; return 3; } - static std::size_t getMinimumNaluSize(const bool isH265) { + static std::size_t getMinimumNaluSize(const bool isH265) + { // 4 bytes prefix, 1 byte header for h264, 2 byte header for h265 return isH265 ? 6 : 5; } -public: + public: // pointer to the NALU data with 0001 prefix - const uint8_t *getData() const { - return m_data; - } + const uint8_t* getData() const { return m_data; } // size of the NALU data with 0001 prefix - size_t getSize() const { - return m_data_len; - } + size_t getSize() const { return m_data_len; } - //pointer to the NALU data without 0001 prefix - const uint8_t *getDataWithoutPrefix() const { - return &getData()[m_nalu_prefix_size]; - } + // pointer to the NALU data without 0001 prefix + const uint8_t* getDataWithoutPrefix() const { return &getData()[m_nalu_prefix_size]; } - //size of the NALU data without 0001 prefix - ssize_t getDataSizeWithoutPrefix() const { - return getSize() - m_nalu_prefix_size; - } + // size of the NALU data without 0001 prefix + ssize_t getDataSizeWithoutPrefix() const { return getSize() - m_nalu_prefix_size; } // return the nal unit type (quick) - int get_nal_unit_type() const { - if (IS_H265_PACKET) { + int get_nal_unit_type() const + { + if (IS_H265_PACKET) + { return (getDataWithoutPrefix()[0] & 0x7E) >> 1; } return getDataWithoutPrefix()[0] & 0x1f; } - std::string get_nal_unit_type_as_string() const { - if (IS_H265_PACKET) { + std::string get_nal_unit_type_as_string() const + { + if (IS_H265_PACKET) + { return NALUnitType::H265::unit_type_to_string(get_nal_unit_type()); } return NALUnitType::H264::unit_type_to_string(get_nal_unit_type()); } -public: - bool isSPS() const { - if (IS_H265_PACKET) { + public: + bool isSPS() const + { + if (IS_H265_PACKET) + { return get_nal_unit_type() == NALUnitType::H265::NAL_UNIT_SPS; } return (get_nal_unit_type() == NALUnitType::H264::NAL_UNIT_TYPE_SPS); } - bool isPPS() const { - if (IS_H265_PACKET) { + bool isPPS() const + { + if (IS_H265_PACKET) + { return get_nal_unit_type() == NALUnitType::H265::NAL_UNIT_PPS; } return (get_nal_unit_type() == NALUnitType::H264::NAL_UNIT_TYPE_PPS); } // VPS NALUs are only possible in H265 - bool isVPS() const { + bool isVPS() const + { assert(IS_H265_PACKET); return get_nal_unit_type() == NALUnitType::H265::NAL_UNIT_VPS; } - bool is_aud() const { - if (IS_H265_PACKET) { + bool is_aud() const + { + if (IS_H265_PACKET) + { return get_nal_unit_type() == NALUnitType::H265::NAL_UNIT_ACCESS_UNIT_DELIMITER; } return (get_nal_unit_type() == NALUnitType::H264::NAL_UNIT_TYPE_AUD); } - bool is_sei() const { - if (IS_H265_PACKET) { + bool is_sei() const + { + if (IS_H265_PACKET) + { return get_nal_unit_type() == NALUnitType::H265::NAL_UNIT_PREFIX_SEI || get_nal_unit_type() == NALUnitType::H265::NAL_UNIT_SUFFIX_SEI; } return (get_nal_unit_type() == NALUnitType::H264::NAL_UNIT_TYPE_SEI); } - bool is_dps() const { - if (IS_H265_PACKET) { + bool is_dps() const + { + if (IS_H265_PACKET) + { // doesn't exist in h265 return false; } return (get_nal_unit_type() == NALUnitType::H264::NAL_UNIT_TYPE_DPS); } - bool is_config() { - return isSPS() || isPPS() || (IS_H265_PACKET && isVPS()); - } + bool is_config() { return isSPS() || isPPS() || (IS_H265_PACKET && isVPS()); } // keyframe / IDR frame - bool is_keyframe() const { + bool is_keyframe() const + { const auto nut = get_nal_unit_type(); - if (IS_H265_PACKET) { + if (IS_H265_PACKET) + { return false; } - if (nut == NALUnitType::H264::NAL_UNIT_TYPE_CODED_SLICE_IDR) { + if (nut == NALUnitType::H264::NAL_UNIT_TYPE_CODED_SLICE_IDR) + { return true; } return false; } - bool is_frame_but_not_keyframe() const { + bool is_frame_but_not_keyframe() const + { const auto nut = get_nal_unit_type(); - if (IS_H265_PACKET)return false; + if (IS_H265_PACKET) return false; return (nut == NALUnitType::H264::NAL_UNIT_TYPE_CODED_SLICE_NON_IDR); } // XXX ----------- - std::string getDataAsHexString() const { + std::string getDataAsHexString() const + { std::stringstream ss; ss << std::hex << std::setfill('0'); - for (size_t i = 0; i < getSize(); ++i) { + for (size_t i = 0; i < getSize(); ++i) + { ss << std::setw(2) << static_cast(getData()[i]); } @@ -203,82 +216,88 @@ class NALU { } // For debugging, return the whole NALU data as a big string for logging -// std::string dataAsString()const{ -// return StringHelper::vectorAsString(std::vector(getData(),getData()+getSize())); -// } -// void debug()const{ -// if(IS_H265_PACKET){ -// if(isSPS()){ -// auto sps=h265nal::H265SpsParser::ParseSps(&getData()[6],getSize()-6); -// if(sps!=absl::nullopt){ -// MLOGD<<"SPS:"<dump(); -// }else{ -// MLOGD<<"SPS parse error"; -// } -// }else if(isPPS()){ -// auto pps=h265nal::H265PpsParser::ParsePps(&getData()[6],getSize()-6); -// if(pps!=absl::nullopt){ -// MLOGD<<"PPS:"<dump(); -// }else{ -// MLOGD<<"PPS parse error"; -// } -// } -// else if(isVPS()){ -// auto vps=h265nal::H265VpsParser::ParseVps(&getData()[6],getSize()-6); -// if(vps!=absl::nullopt){ -// MLOGD<<"VPS:"<dump(); -// }else{ -// MLOGD<<"VPS parse error"; -// } -// }else{ -// MLOGD<(getData(),getData()+getSize())); -// -// //H264::testSPSConversion(getData(),getSize()); -// //RBSPHelper::test_unescape_escape(std::vector(&getData()[5],&getData()[5]+getSize()-5)); -// }else if(isPPS()){ -// auto pps=H264::PPS(getData(),getSize()); -// MLOGD<<"PPS:"<(getData(),getData()+getSize())); -// }else if(is_aud()){ -// MLOGD<<"AUD:"<(getData(),getData()+getSize())); -// }else if(get_nal_unit_type()==NAL_UNIT_TYPE_SEI){ -// MLOGD<<"SEIData:"<(getData(),getData()+getSize())); -// }else{ -// MLOGD<(data,data+data_len); -// //MLOGD< getVideoWidthHeightSPS() const { + // std::string dataAsString()const{ + // return StringHelper::vectorAsString(std::vector(getData(),getData()+getSize())); + // } + // void debug()const{ + // if(IS_H265_PACKET){ + // if(isSPS()){ + // auto sps=h265nal::H265SpsParser::ParseSps(&getData()[6],getSize()-6); + // if(sps!=absl::nullopt){ + // MLOGD<<"SPS:"<dump(); + // }else{ + // MLOGD<<"SPS parse error"; + // } + // }else if(isPPS()){ + // auto pps=h265nal::H265PpsParser::ParsePps(&getData()[6],getSize()-6); + // if(pps!=absl::nullopt){ + // MLOGD<<"PPS:"<dump(); + // }else{ + // MLOGD<<"PPS parse error"; + // } + // } + // else if(isVPS()){ + // auto vps=h265nal::H265VpsParser::ParseVps(&getData()[6],getSize()-6); + // if(vps!=absl::nullopt){ + // MLOGD<<"VPS:"<dump(); + // }else{ + // MLOGD<<"VPS parse error"; + // } + // }else{ + // MLOGD<(getData(),getData()+getSize())); + // + // //H264::testSPSConversion(getData(),getSize()); + // //RBSPHelper::test_unescape_escape(std::vector(&getData()[5],&getData()[5]+getSize()-5)); + // }else if(isPPS()){ + // auto pps=H264::PPS(getData(),getSize()); + // MLOGD<<"PPS:"<(getData(),getData()+getSize())); + // }else if(is_aud()){ + // MLOGD<<"AUD:"<(getData(),getData()+getSize())); + // }else if(get_nal_unit_type()==NAL_UNIT_TYPE_SEI){ + // MLOGD<<"SEIData:"<(getData(),getData()+getSize())); + // }else{ + // MLOGD<(data,data+data_len); + // //MLOGD< getVideoWidthHeightSPS() const + { assert(isSPS()); - if (IS_H265_PACKET) { + if (IS_H265_PACKET) + { return {1280, 720}; -// auto sps=h265nal::H265SpsParser::ParseSps(&getData()[6],getSize()-6); -// if(sps!=absl::nullopt){ -// std::array ret={(int)sps->pic_width_in_luma_samples,(int)sps->pic_height_in_luma_samples}; -// return ret; -// } -// MLOGE<<"Couldn't parse h265 sps"; -// return {640,480}; - } else { - //const auto sps=H264::SPS(getData(),getSize()); - //return sps.getWidthHeightPx(); + // auto sps=h265nal::H265SpsParser::ParseSps(&getData()[6],getSize()-6); + // if(sps!=absl::nullopt){ + // std::array + // ret={(int)sps->pic_width_in_luma_samples,(int)sps->pic_height_in_luma_samples}; return + // ret; + // } + // MLOGE<<"Couldn't parse h265 sps"; + // return {640,480}; + } + else + { + // const auto sps=H264::SPS(getData(),getSize()); + // return sps.getWidthHeightPx(); return {640, 480}; } } @@ -286,37 +305,33 @@ class NALU { // XXX ----------- }; -typedef std::function NALU_DATA_CALLBACK; +typedef std::function NALU_DATA_CALLBACK; // Copies the nalu data into its own c++-style managed buffer. -class NALUBuffer { -public: - NALUBuffer(const uint8_t *data, int data_len, bool is_h265, - std::chrono::steady_clock::time_point creation_time) { +class NALUBuffer +{ + public: + NALUBuffer(const uint8_t* data, int data_len, bool is_h265, std::chrono::steady_clock::time_point creation_time) + { m_data = std::make_shared>(data, data + data_len); m_nalu = std::make_unique(m_data->data(), m_data->size(), is_h265, creation_time); } - NALUBuffer(const NALU &nalu) { - m_data = std::make_shared>(nalu.getData(), - nalu.getData() + nalu.getSize()); - m_nalu = std::make_unique(m_data->data(), m_data->size(), nalu.IS_H265_PACKET, - nalu.creationTime); + NALUBuffer(const NALU& nalu) + { + m_data = std::make_shared>(nalu.getData(), nalu.getData() + nalu.getSize()); + m_nalu = std::make_unique(m_data->data(), m_data->size(), nalu.IS_H265_PACKET, nalu.creationTime); } - NALUBuffer(const NALUBuffer &) = delete; + NALUBuffer(const NALUBuffer&) = delete; - NALUBuffer(const NALUBuffer &&) = delete; + NALUBuffer(const NALUBuffer&&) = delete; - const NALU &get_nal() { - return *m_nalu; - } + const NALU& get_nal() { return *m_nalu; } -private: + private: std::shared_ptr> m_data; - std::unique_ptr m_nalu; + std::unique_ptr m_nalu; }; -#endif //FPVUE_ANDROID_NALU_H - - +#endif // FPVUE_ANDROID_NALU_H diff --git a/app/videonative/src/main/cpp/NALU/NALUnitType.hpp b/app/videonative/src/main/cpp/NALU/NALUnitType.hpp index b4e04ec..9fe4412 100644 --- a/app/videonative/src/main/cpp/NALU/NALUnitType.hpp +++ b/app/videonative/src/main/cpp/NALU/NALUnitType.hpp @@ -10,384 +10,393 @@ // h264 types come from h264_stream // h265 types are declared here // This is just a lot of boilerplate code so I put it into its own .hpp file -namespace NALUnitType { +namespace NALUnitType +{ - namespace H264 { - enum { - //Table 7-1 NAL unit type codes - NAL_UNIT_TYPE_UNSPECIFIED = 0, // Unspecified - NAL_UNIT_TYPE_CODED_SLICE_NON_IDR = 1, // Coded slice of a non-IDR picture - NAL_UNIT_TYPE_CODED_SLICE_DATA_PARTITION_A = 2, // Coded slice data partition A - NAL_UNIT_TYPE_CODED_SLICE_DATA_PARTITION_B = 3, // Coded slice data partition B - NAL_UNIT_TYPE_CODED_SLICE_DATA_PARTITION_C = 4, // Coded slice data partition C - NAL_UNIT_TYPE_CODED_SLICE_IDR = 5, // Coded slice of an IDR picture - NAL_UNIT_TYPE_SEI = 6, // Supplemental enhancement information (SEI) - NAL_UNIT_TYPE_SPS = 7, // Sequence parameter set - NAL_UNIT_TYPE_PPS = 8, // Picture parameter set - NAL_UNIT_TYPE_AUD = 9, // Access unit delimiter - NAL_UNIT_TYPE_END_OF_SEQUENCE = 10, // End of sequence - NAL_UNIT_TYPE_END_OF_STREAM = 11, // End of stream - NAL_UNIT_TYPE_FILLER = 12, // Filler data - NAL_UNIT_TYPE_SPS_EXT = 13, // Sequence parameter set extension - NAL_UNIT_TYPE_PREFIX_NAL = 14, // Prefix NAL unit - NAL_UNIT_TYPE_SUBSET_SPS = 15, // Subset Sequence parameter set - NAL_UNIT_TYPE_DPS = 16, // Depth Parameter Set - // 17..18 // Reserved - NAL_UNIT_TYPE_CODED_SLICE_AUX = 19, // Coded slice of an auxiliary coded picture without partitioning - NAL_UNIT_TYPE_CODED_SLICE_SVC_EXTENSION = 20, // Coded slice of SVC extension - // 20..23 // Reserved - // 24..31 // Unspecified - }; +namespace H264 +{ +enum +{ + // Table 7-1 NAL unit type codes + NAL_UNIT_TYPE_UNSPECIFIED = 0, // Unspecified + NAL_UNIT_TYPE_CODED_SLICE_NON_IDR = 1, // Coded slice of a non-IDR picture + NAL_UNIT_TYPE_CODED_SLICE_DATA_PARTITION_A = 2, // Coded slice data partition A + NAL_UNIT_TYPE_CODED_SLICE_DATA_PARTITION_B = 3, // Coded slice data partition B + NAL_UNIT_TYPE_CODED_SLICE_DATA_PARTITION_C = 4, // Coded slice data partition C + NAL_UNIT_TYPE_CODED_SLICE_IDR = 5, // Coded slice of an IDR picture + NAL_UNIT_TYPE_SEI = 6, // Supplemental enhancement information (SEI) + NAL_UNIT_TYPE_SPS = 7, // Sequence parameter set + NAL_UNIT_TYPE_PPS = 8, // Picture parameter set + NAL_UNIT_TYPE_AUD = 9, // Access unit delimiter + NAL_UNIT_TYPE_END_OF_SEQUENCE = 10, // End of sequence + NAL_UNIT_TYPE_END_OF_STREAM = 11, // End of stream + NAL_UNIT_TYPE_FILLER = 12, // Filler data + NAL_UNIT_TYPE_SPS_EXT = 13, // Sequence parameter set extension + NAL_UNIT_TYPE_PREFIX_NAL = 14, // Prefix NAL unit + NAL_UNIT_TYPE_SUBSET_SPS = 15, // Subset Sequence parameter set + NAL_UNIT_TYPE_DPS = 16, // Depth Parameter Set + // 17..18 // Reserved + NAL_UNIT_TYPE_CODED_SLICE_AUX = 19, // Coded slice of an auxiliary coded picture without partitioning + NAL_UNIT_TYPE_CODED_SLICE_SVC_EXTENSION = 20, // Coded slice of SVC extension + // 20..23 // Reserved + // 24..31 // Unspecified +}; - static std::string unit_type_to_string(const int nal_unit_type) { - std::string nal_unit_type_name; - switch (nal_unit_type) { - case NAL_UNIT_TYPE_UNSPECIFIED : - nal_unit_type_name = "NAL_UNIT_TYPE_UNSPECIFIED"; - break;//Unspecified - case NAL_UNIT_TYPE_CODED_SLICE_NON_IDR : - nal_unit_type_name = "NAL_UNIT_TYPE_CODED_SLICE_NON_IDR"; - break;//Coded slice of a non-IDR picture - case NAL_UNIT_TYPE_CODED_SLICE_DATA_PARTITION_A : - nal_unit_type_name = "NAL_UNIT_TYPE_CODED_SLICE_DATA_PARTITION_A"; - break;//Coded slice data partition A - case NAL_UNIT_TYPE_CODED_SLICE_DATA_PARTITION_B : - nal_unit_type_name = "NAL_UNIT_TYPE_CODED_SLICE_DATA_PARTITION_B"; - break;//Coded slice data partition B - case NAL_UNIT_TYPE_CODED_SLICE_DATA_PARTITION_C : - nal_unit_type_name = "NAL_UNIT_TYPE_CODED_SLICE_DATA_PARTITION_C"; - break;//Coded slice data partition C - case NAL_UNIT_TYPE_CODED_SLICE_IDR : - nal_unit_type_name = "NAL_UNIT_TYPE_CODED_SLICE_IDR"; - break;//Coded slice of an IDR picture - case NAL_UNIT_TYPE_SEI : - nal_unit_type_name = "NAL_UNIT_TYPE_SEI"; - break;//Supplemental enhancement information (SEI) - case NAL_UNIT_TYPE_SPS : - nal_unit_type_name = "NAL_UNIT_TYPE_SPS"; - break;//Sequence parameter set - case NAL_UNIT_TYPE_PPS : - nal_unit_type_name = "NAL_UNIT_TYPE_PPS"; - break;//Picture parameter set - case NAL_UNIT_TYPE_AUD : - nal_unit_type_name = "NAL_UNIT_TYPE_AUD"; - break;//Access unit delimiter - case NAL_UNIT_TYPE_END_OF_SEQUENCE : - nal_unit_type_name = "NAL_UNIT_TYPE_END_OF_SEQUENCE"; - break;//End of sequence - case NAL_UNIT_TYPE_END_OF_STREAM : - nal_unit_type_name = "NAL_UNIT_TYPE_END_OF_STREAM"; - break;//End of stream - case NAL_UNIT_TYPE_FILLER : - nal_unit_type_name = "NAL_UNIT_TYPE_FILLER"; - break;//Filler data - case NAL_UNIT_TYPE_SPS_EXT : - nal_unit_type_name = "SNAL_UNIT_TYPE_SPS_EXT"; - break;//Sequence parameter set extension - // 14..18 // Reserved - case NAL_UNIT_TYPE_CODED_SLICE_AUX : - nal_unit_type_name = "NAL_UNIT_TYPE_CODED_SLICE_AUX"; - break; - //Coded slice of an auxiliary coded picture without partitioning - // 20..23 // Reserved - // 24..31 // Unspecified - default : - nal_unit_type_name = std::string("Unknown") + std::to_string(nal_unit_type); - break; - } - return nal_unit_type_name; - }; +static std::string unit_type_to_string(const int nal_unit_type) +{ + std::string nal_unit_type_name; + switch (nal_unit_type) + { + case NAL_UNIT_TYPE_UNSPECIFIED: + nal_unit_type_name = "NAL_UNIT_TYPE_UNSPECIFIED"; + break; // Unspecified + case NAL_UNIT_TYPE_CODED_SLICE_NON_IDR: + nal_unit_type_name = "NAL_UNIT_TYPE_CODED_SLICE_NON_IDR"; + break; // Coded slice of a non-IDR picture + case NAL_UNIT_TYPE_CODED_SLICE_DATA_PARTITION_A: + nal_unit_type_name = "NAL_UNIT_TYPE_CODED_SLICE_DATA_PARTITION_A"; + break; // Coded slice data partition A + case NAL_UNIT_TYPE_CODED_SLICE_DATA_PARTITION_B: + nal_unit_type_name = "NAL_UNIT_TYPE_CODED_SLICE_DATA_PARTITION_B"; + break; // Coded slice data partition B + case NAL_UNIT_TYPE_CODED_SLICE_DATA_PARTITION_C: + nal_unit_type_name = "NAL_UNIT_TYPE_CODED_SLICE_DATA_PARTITION_C"; + break; // Coded slice data partition C + case NAL_UNIT_TYPE_CODED_SLICE_IDR: + nal_unit_type_name = "NAL_UNIT_TYPE_CODED_SLICE_IDR"; + break; // Coded slice of an IDR picture + case NAL_UNIT_TYPE_SEI: + nal_unit_type_name = "NAL_UNIT_TYPE_SEI"; + break; // Supplemental enhancement information (SEI) + case NAL_UNIT_TYPE_SPS: + nal_unit_type_name = "NAL_UNIT_TYPE_SPS"; + break; // Sequence parameter set + case NAL_UNIT_TYPE_PPS: + nal_unit_type_name = "NAL_UNIT_TYPE_PPS"; + break; // Picture parameter set + case NAL_UNIT_TYPE_AUD: + nal_unit_type_name = "NAL_UNIT_TYPE_AUD"; + break; // Access unit delimiter + case NAL_UNIT_TYPE_END_OF_SEQUENCE: + nal_unit_type_name = "NAL_UNIT_TYPE_END_OF_SEQUENCE"; + break; // End of sequence + case NAL_UNIT_TYPE_END_OF_STREAM: + nal_unit_type_name = "NAL_UNIT_TYPE_END_OF_STREAM"; + break; // End of stream + case NAL_UNIT_TYPE_FILLER: + nal_unit_type_name = "NAL_UNIT_TYPE_FILLER"; + break; // Filler data + case NAL_UNIT_TYPE_SPS_EXT: + nal_unit_type_name = "SNAL_UNIT_TYPE_SPS_EXT"; + break; // Sequence parameter set extension + // 14..18 // Reserved + case NAL_UNIT_TYPE_CODED_SLICE_AUX: + nal_unit_type_name = "NAL_UNIT_TYPE_CODED_SLICE_AUX"; + break; + // Coded slice of an auxiliary coded picture without partitioning + // 20..23 // Reserved + // 24..31 // Unspecified + default: + nal_unit_type_name = std::string("Unknown") + std::to_string(nal_unit_type); + break; } + return nal_unit_type_name; +}; +} // namespace H264 - namespace H265 { - enum NalUnitType { - NAL_UNIT_CODED_SLICE_TRAIL_N = 0, - NAL_UNIT_CODED_SLICE_TRAIL_R, +namespace H265 +{ +enum NalUnitType +{ + NAL_UNIT_CODED_SLICE_TRAIL_N = 0, + NAL_UNIT_CODED_SLICE_TRAIL_R, - NAL_UNIT_CODED_SLICE_TSA_N, - NAL_UNIT_CODED_SLICE_TSA_R, + NAL_UNIT_CODED_SLICE_TSA_N, + NAL_UNIT_CODED_SLICE_TSA_R, - NAL_UNIT_CODED_SLICE_STSA_N, - NAL_UNIT_CODED_SLICE_STSA_R, + NAL_UNIT_CODED_SLICE_STSA_N, + NAL_UNIT_CODED_SLICE_STSA_R, - NAL_UNIT_CODED_SLICE_RADL_N, - NAL_UNIT_CODED_SLICE_RADL_R, + NAL_UNIT_CODED_SLICE_RADL_N, + NAL_UNIT_CODED_SLICE_RADL_R, - NAL_UNIT_CODED_SLICE_RASL_N, - NAL_UNIT_CODED_SLICE_RASL_R, + NAL_UNIT_CODED_SLICE_RASL_N, + NAL_UNIT_CODED_SLICE_RASL_R, - NAL_UNIT_RESERVED_VCL_N10, - NAL_UNIT_RESERVED_VCL_R11, - NAL_UNIT_RESERVED_VCL_N12, - NAL_UNIT_RESERVED_VCL_R13, - NAL_UNIT_RESERVED_VCL_N14, - NAL_UNIT_RESERVED_VCL_R15, + NAL_UNIT_RESERVED_VCL_N10, + NAL_UNIT_RESERVED_VCL_R11, + NAL_UNIT_RESERVED_VCL_N12, + NAL_UNIT_RESERVED_VCL_R13, + NAL_UNIT_RESERVED_VCL_N14, + NAL_UNIT_RESERVED_VCL_R15, - NAL_UNIT_CODED_SLICE_BLA_W_LP, - NAL_UNIT_CODED_SLICE_BLA_W_RADL, - NAL_UNIT_CODED_SLICE_BLA_N_LP, - NAL_UNIT_CODED_SLICE_IDR_W_RADL, - NAL_UNIT_CODED_SLICE_IDR_N_LP, - NAL_UNIT_CODED_SLICE_CRA, - NAL_UNIT_RESERVED_IRAP_VCL22, - NAL_UNIT_RESERVED_IRAP_VCL23, + NAL_UNIT_CODED_SLICE_BLA_W_LP, + NAL_UNIT_CODED_SLICE_BLA_W_RADL, + NAL_UNIT_CODED_SLICE_BLA_N_LP, + NAL_UNIT_CODED_SLICE_IDR_W_RADL, + NAL_UNIT_CODED_SLICE_IDR_N_LP, + NAL_UNIT_CODED_SLICE_CRA, + NAL_UNIT_RESERVED_IRAP_VCL22, + NAL_UNIT_RESERVED_IRAP_VCL23, - NAL_UNIT_RESERVED_VCL24, - NAL_UNIT_RESERVED_VCL25, - NAL_UNIT_RESERVED_VCL26, - NAL_UNIT_RESERVED_VCL27, - NAL_UNIT_RESERVED_VCL28, - NAL_UNIT_RESERVED_VCL29, - NAL_UNIT_RESERVED_VCL30, - NAL_UNIT_RESERVED_VCL31, + NAL_UNIT_RESERVED_VCL24, + NAL_UNIT_RESERVED_VCL25, + NAL_UNIT_RESERVED_VCL26, + NAL_UNIT_RESERVED_VCL27, + NAL_UNIT_RESERVED_VCL28, + NAL_UNIT_RESERVED_VCL29, + NAL_UNIT_RESERVED_VCL30, + NAL_UNIT_RESERVED_VCL31, - NAL_UNIT_VPS, - NAL_UNIT_SPS, - NAL_UNIT_PPS, - NAL_UNIT_ACCESS_UNIT_DELIMITER, - NAL_UNIT_EOS, - NAL_UNIT_EOB, - NAL_UNIT_FILLER_DATA, - NAL_UNIT_PREFIX_SEI, - NAL_UNIT_SUFFIX_SEI, - NAL_UNIT_RESERVED_NVCL41, - NAL_UNIT_RESERVED_NVCL42, - NAL_UNIT_RESERVED_NVCL43, - NAL_UNIT_RESERVED_NVCL44, - NAL_UNIT_RESERVED_NVCL45, - NAL_UNIT_RESERVED_NVCL46, - NAL_UNIT_RESERVED_NVCL47, - NAL_UNIT_UNSPECIFIED_48, - NAL_UNIT_UNSPECIFIED_49, - NAL_UNIT_UNSPECIFIED_50, - NAL_UNIT_UNSPECIFIED_51, - NAL_UNIT_UNSPECIFIED_52, - NAL_UNIT_UNSPECIFIED_53, - NAL_UNIT_UNSPECIFIED_54, - NAL_UNIT_UNSPECIFIED_55, - NAL_UNIT_UNSPECIFIED_56, - NAL_UNIT_UNSPECIFIED_57, - NAL_UNIT_UNSPECIFIED_58, - NAL_UNIT_UNSPECIFIED_59, - NAL_UNIT_UNSPECIFIED_60, - NAL_UNIT_UNSPECIFIED_61, - NAL_UNIT_UNSPECIFIED_62, - NAL_UNIT_UNSPECIFIED_63, - NAL_UNIT_INVALID, - }; + NAL_UNIT_VPS, + NAL_UNIT_SPS, + NAL_UNIT_PPS, + NAL_UNIT_ACCESS_UNIT_DELIMITER, + NAL_UNIT_EOS, + NAL_UNIT_EOB, + NAL_UNIT_FILLER_DATA, + NAL_UNIT_PREFIX_SEI, + NAL_UNIT_SUFFIX_SEI, + NAL_UNIT_RESERVED_NVCL41, + NAL_UNIT_RESERVED_NVCL42, + NAL_UNIT_RESERVED_NVCL43, + NAL_UNIT_RESERVED_NVCL44, + NAL_UNIT_RESERVED_NVCL45, + NAL_UNIT_RESERVED_NVCL46, + NAL_UNIT_RESERVED_NVCL47, + NAL_UNIT_UNSPECIFIED_48, + NAL_UNIT_UNSPECIFIED_49, + NAL_UNIT_UNSPECIFIED_50, + NAL_UNIT_UNSPECIFIED_51, + NAL_UNIT_UNSPECIFIED_52, + NAL_UNIT_UNSPECIFIED_53, + NAL_UNIT_UNSPECIFIED_54, + NAL_UNIT_UNSPECIFIED_55, + NAL_UNIT_UNSPECIFIED_56, + NAL_UNIT_UNSPECIFIED_57, + NAL_UNIT_UNSPECIFIED_58, + NAL_UNIT_UNSPECIFIED_59, + NAL_UNIT_UNSPECIFIED_60, + NAL_UNIT_UNSPECIFIED_61, + NAL_UNIT_UNSPECIFIED_62, + NAL_UNIT_UNSPECIFIED_63, + NAL_UNIT_INVALID, +}; - static std::string unit_type_to_string(const int nal_unit_type) { - std::string nal_unit_type_name = "Unimpl"; - switch (nal_unit_type) { - case NalUnitType::NAL_UNIT_CODED_SLICE_TRAIL_N: - nal_unit_type_name = "NAL_UNIT_CODED_SLICE_TRAIL_N"; - break; - case NalUnitType::NAL_UNIT_CODED_SLICE_TRAIL_R: - nal_unit_type_name = "NAL_UNIT_CODED_SLICE_TRAIL_R"; - break; +static std::string unit_type_to_string(const int nal_unit_type) +{ + std::string nal_unit_type_name = "Unimpl"; + switch (nal_unit_type) + { + case NalUnitType::NAL_UNIT_CODED_SLICE_TRAIL_N: + nal_unit_type_name = "NAL_UNIT_CODED_SLICE_TRAIL_N"; + break; + case NalUnitType::NAL_UNIT_CODED_SLICE_TRAIL_R: + nal_unit_type_name = "NAL_UNIT_CODED_SLICE_TRAIL_R"; + break; - case NalUnitType::NAL_UNIT_CODED_SLICE_TSA_N: - nal_unit_type_name = "NAL_UNIT_CODED_SLICE_TSA_N"; - break; - case NalUnitType::NAL_UNIT_CODED_SLICE_TSA_R: - nal_unit_type_name = "NAL_UNIT_CODED_SLICE_TSA_R"; - break; + case NalUnitType::NAL_UNIT_CODED_SLICE_TSA_N: + nal_unit_type_name = "NAL_UNIT_CODED_SLICE_TSA_N"; + break; + case NalUnitType::NAL_UNIT_CODED_SLICE_TSA_R: + nal_unit_type_name = "NAL_UNIT_CODED_SLICE_TSA_R"; + break; - case NalUnitType::NAL_UNIT_CODED_SLICE_STSA_N: - nal_unit_type_name = "NAL_UNIT_CODED_SLICE_STSA_N"; - break; - case NalUnitType::NAL_UNIT_CODED_SLICE_STSA_R: - nal_unit_type_name = "NAL_UNIT_CODED_SLICE_STSA_R"; - break; + case NalUnitType::NAL_UNIT_CODED_SLICE_STSA_N: + nal_unit_type_name = "NAL_UNIT_CODED_SLICE_STSA_N"; + break; + case NalUnitType::NAL_UNIT_CODED_SLICE_STSA_R: + nal_unit_type_name = "NAL_UNIT_CODED_SLICE_STSA_R"; + break; - case NalUnitType::NAL_UNIT_CODED_SLICE_RADL_N: - nal_unit_type_name = "NAL_UNIT_CODED_SLICE_RADL_N"; - break; - case NalUnitType::NAL_UNIT_CODED_SLICE_RADL_R: - nal_unit_type_name = "NAL_UNIT_CODED_SLICE_RADL_R"; - break; + case NalUnitType::NAL_UNIT_CODED_SLICE_RADL_N: + nal_unit_type_name = "NAL_UNIT_CODED_SLICE_RADL_N"; + break; + case NalUnitType::NAL_UNIT_CODED_SLICE_RADL_R: + nal_unit_type_name = "NAL_UNIT_CODED_SLICE_RADL_R"; + break; - case NalUnitType::NAL_UNIT_CODED_SLICE_RASL_N: - nal_unit_type_name = "NAL_UNIT_CODED_SLICE_RASL_N"; - break; - case NalUnitType::NAL_UNIT_CODED_SLICE_RASL_R: - nal_unit_type_name = "NAL_UNIT_CODED_SLICE_RASL_R"; - break; + case NalUnitType::NAL_UNIT_CODED_SLICE_RASL_N: + nal_unit_type_name = "NAL_UNIT_CODED_SLICE_RASL_N"; + break; + case NalUnitType::NAL_UNIT_CODED_SLICE_RASL_R: + nal_unit_type_name = "NAL_UNIT_CODED_SLICE_RASL_R"; + break; - case NalUnitType::NAL_UNIT_RESERVED_VCL_N10: - nal_unit_type_name = "NAL_UNIT_RESERVED_VCL_N10"; - break; - case NalUnitType::NAL_UNIT_RESERVED_VCL_R11: - nal_unit_type_name = "NAL_UNIT_RESERVED_VCL_R11"; - break; - case NalUnitType::NAL_UNIT_RESERVED_VCL_N12: - nal_unit_type_name = "NAL_UNIT_RESERVED_VCL_N12"; - break; - case NalUnitType::NAL_UNIT_RESERVED_VCL_R13: - nal_unit_type_name = "NAL_UNIT_RESERVED_VCL_R13"; - break; - case NalUnitType::NAL_UNIT_RESERVED_VCL_N14: - nal_unit_type_name = "NAL_UNIT_RESERVED_VCL_N14"; - break; - case NalUnitType::NAL_UNIT_RESERVED_VCL_R15: - nal_unit_type_name = "NAL_UNIT_RESERVED_VCL_R15"; - break; + case NalUnitType::NAL_UNIT_RESERVED_VCL_N10: + nal_unit_type_name = "NAL_UNIT_RESERVED_VCL_N10"; + break; + case NalUnitType::NAL_UNIT_RESERVED_VCL_R11: + nal_unit_type_name = "NAL_UNIT_RESERVED_VCL_R11"; + break; + case NalUnitType::NAL_UNIT_RESERVED_VCL_N12: + nal_unit_type_name = "NAL_UNIT_RESERVED_VCL_N12"; + break; + case NalUnitType::NAL_UNIT_RESERVED_VCL_R13: + nal_unit_type_name = "NAL_UNIT_RESERVED_VCL_R13"; + break; + case NalUnitType::NAL_UNIT_RESERVED_VCL_N14: + nal_unit_type_name = "NAL_UNIT_RESERVED_VCL_N14"; + break; + case NalUnitType::NAL_UNIT_RESERVED_VCL_R15: + nal_unit_type_name = "NAL_UNIT_RESERVED_VCL_R15"; + break; - case NalUnitType::NAL_UNIT_CODED_SLICE_BLA_W_LP: - nal_unit_type_name = "NAL_UNIT_CODED_SLICE_BLA_W_LP"; - break; - case NalUnitType::NAL_UNIT_CODED_SLICE_BLA_W_RADL: - nal_unit_type_name = "NAL_UNIT_CODED_SLICE_BLA_W_RADL"; - break; - case NalUnitType::NAL_UNIT_CODED_SLICE_BLA_N_LP: - nal_unit_type_name = "NAL_UNIT_CODED_SLICE_BLA_N_LP"; - break; - case NalUnitType::NAL_UNIT_CODED_SLICE_IDR_W_RADL: - nal_unit_type_name = "NAL_UNIT_CODED_SLICE_IDR_W_RADL"; - break; - case NalUnitType::NAL_UNIT_CODED_SLICE_IDR_N_LP: - nal_unit_type_name = "NAL_UNIT_CODED_SLICE_IDR_N_LP"; - break; - case NalUnitType::NAL_UNIT_CODED_SLICE_CRA: - nal_unit_type_name = "NAL_UNIT_CODED_SLICE_CRA"; - break; - case NalUnitType::NAL_UNIT_RESERVED_IRAP_VCL22: - nal_unit_type_name = "NAL_UNIT_RESERVED_IRAP_VCL22"; - break; - case NalUnitType::NAL_UNIT_RESERVED_IRAP_VCL23: - nal_unit_type_name = "NAL_UNIT_RESERVED_IRAP_VCL23"; - break; + case NalUnitType::NAL_UNIT_CODED_SLICE_BLA_W_LP: + nal_unit_type_name = "NAL_UNIT_CODED_SLICE_BLA_W_LP"; + break; + case NalUnitType::NAL_UNIT_CODED_SLICE_BLA_W_RADL: + nal_unit_type_name = "NAL_UNIT_CODED_SLICE_BLA_W_RADL"; + break; + case NalUnitType::NAL_UNIT_CODED_SLICE_BLA_N_LP: + nal_unit_type_name = "NAL_UNIT_CODED_SLICE_BLA_N_LP"; + break; + case NalUnitType::NAL_UNIT_CODED_SLICE_IDR_W_RADL: + nal_unit_type_name = "NAL_UNIT_CODED_SLICE_IDR_W_RADL"; + break; + case NalUnitType::NAL_UNIT_CODED_SLICE_IDR_N_LP: + nal_unit_type_name = "NAL_UNIT_CODED_SLICE_IDR_N_LP"; + break; + case NalUnitType::NAL_UNIT_CODED_SLICE_CRA: + nal_unit_type_name = "NAL_UNIT_CODED_SLICE_CRA"; + break; + case NalUnitType::NAL_UNIT_RESERVED_IRAP_VCL22: + nal_unit_type_name = "NAL_UNIT_RESERVED_IRAP_VCL22"; + break; + case NalUnitType::NAL_UNIT_RESERVED_IRAP_VCL23: + nal_unit_type_name = "NAL_UNIT_RESERVED_IRAP_VCL23"; + break; - case NalUnitType::NAL_UNIT_RESERVED_VCL24: - nal_unit_type_name = "NAL_UNIT_RESERVED_VCL24"; - break; - case NalUnitType::NAL_UNIT_RESERVED_VCL25: - nal_unit_type_name = "NAL_UNIT_RESERVED_VCL25"; - break; - case NalUnitType::NAL_UNIT_RESERVED_VCL26: - nal_unit_type_name = "NAL_UNIT_RESERVED_VCL26"; - break; - case NalUnitType::NAL_UNIT_RESERVED_VCL27: - nal_unit_type_name = "NAL_UNIT_RESERVED_VCL27"; - break; - case NalUnitType::NAL_UNIT_RESERVED_VCL28: - nal_unit_type_name = "NAL_UNIT_RESERVED_VCL28"; - break; - case NalUnitType::NAL_UNIT_RESERVED_VCL29: - nal_unit_type_name = "NAL_UNIT_RESERVED_VCL29"; - break; - case NalUnitType::NAL_UNIT_RESERVED_VCL30: - nal_unit_type_name = "NAL_UNIT_RESERVED_VCL30"; - break; - case NalUnitType::NAL_UNIT_RESERVED_VCL31: - nal_unit_type_name = "NAL_UNIT_RESERVED_VCL31"; - break; + case NalUnitType::NAL_UNIT_RESERVED_VCL24: + nal_unit_type_name = "NAL_UNIT_RESERVED_VCL24"; + break; + case NalUnitType::NAL_UNIT_RESERVED_VCL25: + nal_unit_type_name = "NAL_UNIT_RESERVED_VCL25"; + break; + case NalUnitType::NAL_UNIT_RESERVED_VCL26: + nal_unit_type_name = "NAL_UNIT_RESERVED_VCL26"; + break; + case NalUnitType::NAL_UNIT_RESERVED_VCL27: + nal_unit_type_name = "NAL_UNIT_RESERVED_VCL27"; + break; + case NalUnitType::NAL_UNIT_RESERVED_VCL28: + nal_unit_type_name = "NAL_UNIT_RESERVED_VCL28"; + break; + case NalUnitType::NAL_UNIT_RESERVED_VCL29: + nal_unit_type_name = "NAL_UNIT_RESERVED_VCL29"; + break; + case NalUnitType::NAL_UNIT_RESERVED_VCL30: + nal_unit_type_name = "NAL_UNIT_RESERVED_VCL30"; + break; + case NalUnitType::NAL_UNIT_RESERVED_VCL31: + nal_unit_type_name = "NAL_UNIT_RESERVED_VCL31"; + break; - case NalUnitType::NAL_UNIT_VPS: - nal_unit_type_name = "NAL_UNIT_VPS"; - break; - case NalUnitType::NAL_UNIT_SPS: - nal_unit_type_name = "NAL_UNIT_SPS"; - break; - case NalUnitType::NAL_UNIT_PPS: - nal_unit_type_name = "NAL_UNIT_PPS"; - break; - case NalUnitType::NAL_UNIT_ACCESS_UNIT_DELIMITER: - nal_unit_type_name = "NAL_UNIT_ACCESS_UNIT_DELIMITER"; - break; - case NalUnitType::NAL_UNIT_EOS: - nal_unit_type_name = "NAL_UNIT_EOS"; - break; - case NalUnitType::NAL_UNIT_EOB: - nal_unit_type_name = "NAL_UNIT_EOB"; - break; - case NalUnitType::NAL_UNIT_FILLER_DATA: - nal_unit_type_name = "NAL_UNIT_FILLER_DATA"; - break; - case NalUnitType::NAL_UNIT_PREFIX_SEI: - nal_unit_type_name = "NAL_UNIT_PREFIX_SEI"; - break; - case NalUnitType::NAL_UNIT_SUFFIX_SEI: - nal_unit_type_name = "NAL_UNIT_SUFFIX_SEI"; - break; - case NalUnitType::NAL_UNIT_RESERVED_NVCL41: - nal_unit_type_name = "NAL_UNIT_RESERVED_NVCL41"; - break; - case NalUnitType::NAL_UNIT_RESERVED_NVCL42: - nal_unit_type_name = "NAL_UNIT_RESERVED_NVCL42"; - break; - case NalUnitType::NAL_UNIT_RESERVED_NVCL43: - nal_unit_type_name = "NAL_UNIT_RESERVED_NVCL43"; - break; - case NalUnitType::NAL_UNIT_RESERVED_NVCL44: - nal_unit_type_name = "NAL_UNIT_RESERVED_NVCL44"; - break; - case NalUnitType::NAL_UNIT_RESERVED_NVCL45: - nal_unit_type_name = "NAL_UNIT_RESERVED_NVCL45"; - break; - case NalUnitType::NAL_UNIT_RESERVED_NVCL46: - nal_unit_type_name = "NAL_UNIT_RESERVED_NVCL46"; - break; - case NalUnitType::NAL_UNIT_RESERVED_NVCL47: - nal_unit_type_name = "NAL_UNIT_RESERVED_NVCL47"; - break; - case NalUnitType::NAL_UNIT_UNSPECIFIED_48: - nal_unit_type_name = "NAL_UNIT_UNSPECIFIED_48"; - break; - case NalUnitType::NAL_UNIT_UNSPECIFIED_49: - nal_unit_type_name = "NAL_UNIT_UNSPECIFIED_49"; - break; - case NalUnitType::NAL_UNIT_UNSPECIFIED_50: - nal_unit_type_name = "NAL_UNIT_UNSPECIFIED_50"; - break; - case NalUnitType::NAL_UNIT_UNSPECIFIED_51: - nal_unit_type_name = "NAL_UNIT_UNSPECIFIED_51"; - break; - case NalUnitType::NAL_UNIT_UNSPECIFIED_52: - nal_unit_type_name = "NAL_UNIT_UNSPECIFIED_52"; - break; - case NalUnitType::NAL_UNIT_UNSPECIFIED_53: - nal_unit_type_name = "NAL_UNIT_UNSPECIFIED_53"; - break; - case NalUnitType::NAL_UNIT_UNSPECIFIED_54: - nal_unit_type_name = "NAL_UNIT_UNSPECIFIED_54"; - break; - case NalUnitType::NAL_UNIT_UNSPECIFIED_55: - nal_unit_type_name = "NAL_UNIT_UNSPECIFIED_55"; - break; - case NalUnitType::NAL_UNIT_UNSPECIFIED_56: - nal_unit_type_name = "NAL_UNIT_UNSPECIFIED_56"; - break; - case NalUnitType::NAL_UNIT_UNSPECIFIED_57: - nal_unit_type_name = "NAL_UNIT_UNSPECIFIED_57"; - break; - case NalUnitType::NAL_UNIT_UNSPECIFIED_58: - nal_unit_type_name = "NAL_UNIT_UNSPECIFIED_58"; - break; - case NalUnitType::NAL_UNIT_UNSPECIFIED_59: - nal_unit_type_name = "NAL_UNIT_UNSPECIFIED_59"; - break; - case NalUnitType::NAL_UNIT_UNSPECIFIED_60: - nal_unit_type_name = "NAL_UNIT_UNSPECIFIED_60"; - break; - case NalUnitType::NAL_UNIT_UNSPECIFIED_61: - nal_unit_type_name = "NAL_UNIT_UNSPECIFIED_61"; - break; - case NalUnitType::NAL_UNIT_UNSPECIFIED_62: - nal_unit_type_name = "NAL_UNIT_UNSPECIFIED_62"; - break; - case NalUnitType::NAL_UNIT_UNSPECIFIED_63: - nal_unit_type_name = "NAL_UNIT_UNSPECIFIED_63"; - break; - case NalUnitType::NAL_UNIT_INVALID: - nal_unit_type_name = "NAL_UNIT_INVALID"; - break; - default : - nal_unit_type_name = std::string("Unknown") + std::to_string(nal_unit_type); - break; - } - return nal_unit_type_name; - } + case NalUnitType::NAL_UNIT_VPS: + nal_unit_type_name = "NAL_UNIT_VPS"; + break; + case NalUnitType::NAL_UNIT_SPS: + nal_unit_type_name = "NAL_UNIT_SPS"; + break; + case NalUnitType::NAL_UNIT_PPS: + nal_unit_type_name = "NAL_UNIT_PPS"; + break; + case NalUnitType::NAL_UNIT_ACCESS_UNIT_DELIMITER: + nal_unit_type_name = "NAL_UNIT_ACCESS_UNIT_DELIMITER"; + break; + case NalUnitType::NAL_UNIT_EOS: + nal_unit_type_name = "NAL_UNIT_EOS"; + break; + case NalUnitType::NAL_UNIT_EOB: + nal_unit_type_name = "NAL_UNIT_EOB"; + break; + case NalUnitType::NAL_UNIT_FILLER_DATA: + nal_unit_type_name = "NAL_UNIT_FILLER_DATA"; + break; + case NalUnitType::NAL_UNIT_PREFIX_SEI: + nal_unit_type_name = "NAL_UNIT_PREFIX_SEI"; + break; + case NalUnitType::NAL_UNIT_SUFFIX_SEI: + nal_unit_type_name = "NAL_UNIT_SUFFIX_SEI"; + break; + case NalUnitType::NAL_UNIT_RESERVED_NVCL41: + nal_unit_type_name = "NAL_UNIT_RESERVED_NVCL41"; + break; + case NalUnitType::NAL_UNIT_RESERVED_NVCL42: + nal_unit_type_name = "NAL_UNIT_RESERVED_NVCL42"; + break; + case NalUnitType::NAL_UNIT_RESERVED_NVCL43: + nal_unit_type_name = "NAL_UNIT_RESERVED_NVCL43"; + break; + case NalUnitType::NAL_UNIT_RESERVED_NVCL44: + nal_unit_type_name = "NAL_UNIT_RESERVED_NVCL44"; + break; + case NalUnitType::NAL_UNIT_RESERVED_NVCL45: + nal_unit_type_name = "NAL_UNIT_RESERVED_NVCL45"; + break; + case NalUnitType::NAL_UNIT_RESERVED_NVCL46: + nal_unit_type_name = "NAL_UNIT_RESERVED_NVCL46"; + break; + case NalUnitType::NAL_UNIT_RESERVED_NVCL47: + nal_unit_type_name = "NAL_UNIT_RESERVED_NVCL47"; + break; + case NalUnitType::NAL_UNIT_UNSPECIFIED_48: + nal_unit_type_name = "NAL_UNIT_UNSPECIFIED_48"; + break; + case NalUnitType::NAL_UNIT_UNSPECIFIED_49: + nal_unit_type_name = "NAL_UNIT_UNSPECIFIED_49"; + break; + case NalUnitType::NAL_UNIT_UNSPECIFIED_50: + nal_unit_type_name = "NAL_UNIT_UNSPECIFIED_50"; + break; + case NalUnitType::NAL_UNIT_UNSPECIFIED_51: + nal_unit_type_name = "NAL_UNIT_UNSPECIFIED_51"; + break; + case NalUnitType::NAL_UNIT_UNSPECIFIED_52: + nal_unit_type_name = "NAL_UNIT_UNSPECIFIED_52"; + break; + case NalUnitType::NAL_UNIT_UNSPECIFIED_53: + nal_unit_type_name = "NAL_UNIT_UNSPECIFIED_53"; + break; + case NalUnitType::NAL_UNIT_UNSPECIFIED_54: + nal_unit_type_name = "NAL_UNIT_UNSPECIFIED_54"; + break; + case NalUnitType::NAL_UNIT_UNSPECIFIED_55: + nal_unit_type_name = "NAL_UNIT_UNSPECIFIED_55"; + break; + case NalUnitType::NAL_UNIT_UNSPECIFIED_56: + nal_unit_type_name = "NAL_UNIT_UNSPECIFIED_56"; + break; + case NalUnitType::NAL_UNIT_UNSPECIFIED_57: + nal_unit_type_name = "NAL_UNIT_UNSPECIFIED_57"; + break; + case NalUnitType::NAL_UNIT_UNSPECIFIED_58: + nal_unit_type_name = "NAL_UNIT_UNSPECIFIED_58"; + break; + case NalUnitType::NAL_UNIT_UNSPECIFIED_59: + nal_unit_type_name = "NAL_UNIT_UNSPECIFIED_59"; + break; + case NalUnitType::NAL_UNIT_UNSPECIFIED_60: + nal_unit_type_name = "NAL_UNIT_UNSPECIFIED_60"; + break; + case NalUnitType::NAL_UNIT_UNSPECIFIED_61: + nal_unit_type_name = "NAL_UNIT_UNSPECIFIED_61"; + break; + case NalUnitType::NAL_UNIT_UNSPECIFIED_62: + nal_unit_type_name = "NAL_UNIT_UNSPECIFIED_62"; + break; + case NalUnitType::NAL_UNIT_UNSPECIFIED_63: + nal_unit_type_name = "NAL_UNIT_UNSPECIFIED_63"; + break; + case NalUnitType::NAL_UNIT_INVALID: + nal_unit_type_name = "NAL_UNIT_INVALID"; + break; + default: + nal_unit_type_name = std::string("Unknown") + std::to_string(nal_unit_type); + break; } + return nal_unit_type_name; } -#endif //FPVUE_NALUNITTYPE_H +} // namespace H265 +} // namespace NALUnitType +#endif // FPVUE_NALUNITTYPE_H diff --git a/app/videonative/src/main/cpp/UdpReceiver.cpp b/app/videonative/src/main/cpp/UdpReceiver.cpp index 6da1103..3c2e071 100644 --- a/app/videonative/src/main/cpp/UdpReceiver.cpp +++ b/app/videonative/src/main/cpp/UdpReceiver.cpp @@ -2,127 +2,155 @@ // Created by gaeta on 2024-04-01. // - #include "UdpReceiver.h" #include +#include +#include #include #include -#include -#include #include "AndroidThreadPrioValues.hpp" -#include "helper/NDKThreadHelper.hpp" #include "helper/AndroidLogger.hpp" +#include "helper/NDKThreadHelper.hpp" #include "helper/StringHelper.hpp" -UDPReceiver::UDPReceiver(JavaVM *javaVm, int port, std::string name, int CPUPriority, - DATA_CALLBACK onDataReceivedCallback, size_t WANTED_RCVBUF_SIZE) : - mPort(port), mName(std::move(name)), WANTED_RCVBUF_SIZE(WANTED_RCVBUF_SIZE), - mCPUPriority(CPUPriority), onDataReceivedCallback(std::move(onDataReceivedCallback)), - javaVm(javaVm) { +UDPReceiver::UDPReceiver( + JavaVM* javaVm, + int port, + std::string name, + int CPUPriority, + DATA_CALLBACK onDataReceivedCallback, + size_t WANTED_RCVBUF_SIZE) + : mPort(port), + mName(std::move(name)), + WANTED_RCVBUF_SIZE(WANTED_RCVBUF_SIZE), + mCPUPriority(CPUPriority), + onDataReceivedCallback(std::move(onDataReceivedCallback)), + javaVm(javaVm) +{ } -void UDPReceiver::registerOnSourceIPFound(SOURCE_IP_CALLBACK onSourceIP1) { +void UDPReceiver::registerOnSourceIPFound(SOURCE_IP_CALLBACK onSourceIP1) +{ this->onSourceIP = std::move(onSourceIP1); } -long UDPReceiver::getNReceivedBytes() const { +long UDPReceiver::getNReceivedBytes() const +{ return nReceivedBytes; } -std::string UDPReceiver::getSourceIPAddress() const { +std::string UDPReceiver::getSourceIPAddress() const +{ return senderIP; } -void UDPReceiver::startReceiving() { - receiving = true; +void UDPReceiver::startReceiving() +{ + receiving = true; mUDPReceiverThread = std::make_unique([this] { this->receiveFromUDPLoop(); }); #ifdef __ANDROID__ NDKThreadHelper::setName(mUDPReceiverThread->native_handle(), mName.c_str()); #endif } -void UDPReceiver::stopReceiving() { +void UDPReceiver::stopReceiving() +{ receiving = false; - //this stops the recvfrom even if in blocking mode + // this stops the recvfrom even if in blocking mode shutdown(mSocket, SHUT_RD); - if (mUDPReceiverThread->joinable()) { + if (mUDPReceiverThread->joinable()) + { mUDPReceiverThread->join(); } mUDPReceiverThread.reset(); } -void UDPReceiver::receiveFromUDPLoop() { +void UDPReceiver::receiveFromUDPLoop() +{ mSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); - if (mSocket == -1) { + if (mSocket == -1) + { MLOGD << "Error creating socket"; return; } int enable = 1; - if (setsockopt(mSocket, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)) < 0) { + if (setsockopt(mSocket, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)) < 0) + { MLOGD << "Error setting reuse"; } - int recvBufferSize = 0; - socklen_t len = sizeof(recvBufferSize); + int recvBufferSize = 0; + socklen_t len = sizeof(recvBufferSize); getsockopt(mSocket, SOL_SOCKET, SO_RCVBUF, &recvBufferSize, &len); MLOGD << "Default socket recv buffer is " << StringHelper::memorySizeReadable(recvBufferSize); - if (WANTED_RCVBUF_SIZE > recvBufferSize) { + if (WANTED_RCVBUF_SIZE > recvBufferSize) + { recvBufferSize = WANTED_RCVBUF_SIZE; - if (setsockopt(mSocket, SOL_SOCKET, SO_RCVBUF, &WANTED_RCVBUF_SIZE, len)) { - MLOGD << "Cannot increase buffer size to " - << StringHelper::memorySizeReadable(WANTED_RCVBUF_SIZE); + if (setsockopt(mSocket, SOL_SOCKET, SO_RCVBUF, &WANTED_RCVBUF_SIZE, len)) + { + MLOGD << "Cannot increase buffer size to " << StringHelper::memorySizeReadable(WANTED_RCVBUF_SIZE); } getsockopt(mSocket, SOL_SOCKET, SO_RCVBUF, &recvBufferSize, &len); MLOGD << "Wanted " << StringHelper::memorySizeReadable(WANTED_RCVBUF_SIZE) << " Set " << StringHelper::memorySizeReadable(recvBufferSize); } - if (javaVm != nullptr) { + if (javaVm != nullptr) + { #ifdef __ANDROID__ NDKThreadHelper::setProcessThreadPriorityAttachDetach(javaVm, mCPUPriority, mName.c_str()); #endif } struct sockaddr_in myaddr; - memset((uint8_t *) &myaddr, 0, sizeof(myaddr)); - myaddr.sin_family = AF_INET; + memset((uint8_t*) &myaddr, 0, sizeof(myaddr)); + myaddr.sin_family = AF_INET; myaddr.sin_addr.s_addr = htonl(INADDR_ANY); - myaddr.sin_port = htons(mPort); - if (bind(mSocket, (struct sockaddr *) &myaddr, sizeof(myaddr)) == -1) { + myaddr.sin_port = htons(mPort); + if (bind(mSocket, (struct sockaddr*) &myaddr, sizeof(myaddr)) == -1) + { MLOGE << "Error binding Port; " << mPort; return; } - //wrap into unique pointer to avoid running out of stack + // wrap into unique pointer to avoid running out of stack const auto buff = std::make_unique>(); MLOGD << "Listening on " << INADDR_ANY << ":" << mPort; sockaddr_in source; - socklen_t sourceLen = sizeof(sockaddr_in); - - while (receiving) { - //TODO investigate: does a big buffer size create latency with MSG_WAITALL ? - //I do not think so. recvfrom should return as soon as new data arrived,not when the buffer is full - //But with a bigger buffer we do not loose packets when the receiver thread cannot keep up for a short amount of time - // MSG_WAITALL does not wait until we have __n data, but a new UDP packet (that can be smaller than __n) - const ssize_t message_length = recvfrom(mSocket, buff->data(), UDP_PACKET_MAX_SIZE, 0, - (sockaddr *) &source, &sourceLen); - //ssize_t message_length = recv(mSocket, buff, (size_t) mBuffsize, MSG_WAITALL); - if (message_length > 0) { //else -1 was returned;timeout/No data received + socklen_t sourceLen = sizeof(sockaddr_in); + + while (receiving) + { + // TODO investigate: does a big buffer size create latency with MSG_WAITALL ? + // I do not think so. recvfrom should return as soon as new data arrived,not when the buffer is full + // But with a bigger buffer we do not loose packets when the receiver thread cannot keep up for a short amount + // of time + // MSG_WAITALL does not wait until we have __n data, but a new UDP packet (that can be smaller than __n) + const ssize_t message_length = + recvfrom(mSocket, buff->data(), UDP_PACKET_MAX_SIZE, 0, (sockaddr*) &source, &sourceLen); + // ssize_t message_length = recv(mSocket, buff, (size_t) mBuffsize, MSG_WAITALL); + if (message_length > 0) + { // else -1 was returned;timeout/No data received onDataReceivedCallback(buff->data(), (size_t) message_length); nReceivedBytes += message_length; - //The source ip stuff - const char *p = inet_ntoa(source.sin_addr); + // The source ip stuff + const char* p = inet_ntoa(source.sin_addr); std::string s1 = std::string(p); - //MLOGE << "received " << message_length << " bytes from " << s1; - if (senderIP != s1) { + // MLOGE << "received " << message_length << " bytes from " << s1; + if (senderIP != s1) + { senderIP = s1; } - if (onSourceIP != nullptr) { + if (onSourceIP != nullptr) + { onSourceIP(p); } - } else { - if (errno != EWOULDBLOCK) { + } + else + { + if (errno != EWOULDBLOCK) + { MLOGE << "Error on recvfrom. errno=" << errno << " " << strerror(errno); } } @@ -130,7 +158,7 @@ void UDPReceiver::receiveFromUDPLoop() { close(mSocket); } -int UDPReceiver::getPort() const { +int UDPReceiver::getPort() const +{ return mPort; } - diff --git a/app/videonative/src/main/cpp/UdpReceiver.h b/app/videonative/src/main/cpp/UdpReceiver.h index 3761d2f..04627a0 100644 --- a/app/videonative/src/main/cpp/UdpReceiver.h +++ b/app/videonative/src/main/cpp/UdpReceiver.h @@ -5,34 +5,42 @@ #ifndef FPVUE_UDPRECEIVER_H #define FPVUE_UDPRECEIVER_H - -#include -#include +#include #include +#include #include +#include +#include #include #include -#include -#include -//Starts a new thread that continuously checks for new data on UDP port +// Starts a new thread that continuously checks for new data on UDP port -class UDPReceiver { -public: +class UDPReceiver +{ + public: typedef std::function DATA_CALLBACK; - typedef std::function SOURCE_IP_CALLBACK; -public: + typedef std::function SOURCE_IP_CALLBACK; + + public: /** * @param javaVm used to set thread priority (attach and then detach) for android, nullptr when priority doesn't matter/not using android * @param port : The port to listen on * @param CPUPriority: The priority the receiver thread will run with if javaVm!=nullptr * @param onDataReceivedCallback: called every time new data is received - * @param WANTED_RCVBUF_SIZE: The buffer allocated by the OS might not be sufficient to buffer incoming data when receiving at a high data rate - * If @param WANTED_RCVBUF_SIZE is bigger than the size allocated by the OS a bigger buffer is requested, but it is not + * @param WANTED_RCVBUF_SIZE: The buffer allocated by the OS might not be sufficient to buffer incoming data when + receiving at a high data rate + * If @param WANTED_RCVBUF_SIZE is bigger than the size allocated by the OS a bigger buffer is requested, but it is + not * guaranteed that the size is actually increased. Use 0 to leave the buffer size untouched */ - UDPReceiver(JavaVM *javaVm, int port, std::string name, int CPUPriority, - DATA_CALLBACK onDataReceivedCallback, size_t WANTED_RCVBUF_SIZE = 0); + UDPReceiver( + JavaVM* javaVm, + int port, + std::string name, + int CPUPriority, + DATA_CALLBACK onDataReceivedCallback, + size_t WANTED_RCVBUF_SIZE = 0); /** * Register a callback that is called once and contains the IP address of the first received packet's sender @@ -49,33 +57,33 @@ class UDPReceiver { */ void stopReceiving(); - //Get function(s) for private member variables + // Get function(s) for private member variables long getNReceivedBytes() const; std::string getSourceIPAddress() const; int getPort() const; -private: + private: void receiveFromUDPLoop(); const DATA_CALLBACK onDataReceivedCallback = nullptr; - SOURCE_IP_CALLBACK onSourceIP = nullptr; - const int mPort; - const int mCPUPriority; + SOURCE_IP_CALLBACK onSourceIP = nullptr; + const int mPort; + const int mCPUPriority; // Hmm.... - const size_t WANTED_RCVBUF_SIZE; + const size_t WANTED_RCVBUF_SIZE; const std::string mName; - ///We need this reference to stop the receiving thread - int mSocket = 0; - std::string senderIP = "0.0.0.0"; - std::atomic receiving = false; - std::atomic nReceivedBytes = 0; + /// We need this reference to stop the receiving thread + int mSocket = 0; + std::string senderIP = "0.0.0.0"; + std::atomic receiving = false; + std::atomic nReceivedBytes = 0; std::unique_ptr mUDPReceiverThread; - //https://en.wikipedia.org/wiki/User_Datagram_Protocol - //65,507 bytes (65,535 − 8 byte UDP header − 20 byte IP header). + // https://en.wikipedia.org/wiki/User_Datagram_Protocol + // 65,507 bytes (65,535 − 8 byte UDP header − 20 byte IP header). static constexpr const size_t UDP_PACKET_MAX_SIZE = 65507; - JavaVM *javaVm; + JavaVM* javaVm; }; -#endif //FPVUE_UDPRECEIVER_H +#endif // FPVUE_UDPRECEIVER_H diff --git a/app/videonative/src/main/cpp/VideoDecoder.cpp b/app/videonative/src/main/cpp/VideoDecoder.cpp index 71b06d1..160b880 100644 --- a/app/videonative/src/main/cpp/VideoDecoder.cpp +++ b/app/videonative/src/main/cpp/VideoDecoder.cpp @@ -3,11 +3,11 @@ // #include "VideoDecoder.h" -#include "AndroidThreadPrioValues.hpp" -#include "helper/NDKThreadHelper.hpp" #include #include +#include "AndroidThreadPrioValues.hpp" #include "helper/AndroidMediaFormatHelper.h" +#include "helper/NDKThreadHelper.hpp" #include @@ -16,40 +16,49 @@ using namespace std::chrono; -VideoDecoder::VideoDecoder(JNIEnv *env) { +VideoDecoder::VideoDecoder(JNIEnv* env) +{ env->GetJavaVM(&javaVm); resetStatistics(); } -void VideoDecoder::setOutputSurface(JNIEnv *env, jobject surface, jint idx) { - if (surface == nullptr) { +void VideoDecoder::setOutputSurface(JNIEnv* env, jobject surface, jint idx) +{ + if (surface == nullptr) + { MLOGD << "Set output null surface idx: " << idx; - //assert(decoder.window!=nullptr); - if (decoder.window[idx] == nullptr && decoder.codec[idx] == nullptr) { - //MLOGD<<"Decoder window is already null"; + // assert(decoder.window!=nullptr); + if (decoder.window[idx] == nullptr && decoder.codec[idx] == nullptr) + { + // MLOGD<<"Decoder window is already null"; return; } std::lock_guard lock(mMutexInputPipe); inputPipeClosed = true; - if (decoder.configured[idx]) { + if (decoder.configured[idx]) + { AMediaCodec_stop(decoder.codec[idx]); AMediaCodec_delete(decoder.codec[idx]); decoder.codec[idx] = nullptr; MLOGD << "Set decoder.codec null idx: " << idx; mKeyFrameFinder.reset(); decoder.configured[idx] = false; - if (mCheckOutputThread[idx]->joinable()) { + if (mCheckOutputThread[idx]->joinable()) + { mCheckOutputThread[idx]->join(); mCheckOutputThread[idx].reset(); } } - if (decoder.window[idx]) { + if (decoder.window[idx]) + { ANativeWindow_release(decoder.window[idx]); decoder.window[idx] = nullptr; MLOGD << "Set decoder.window null idx: " << idx; } resetStatistics(); - } else { + } + else + { MLOGD << "Set output non-null surface idx :" << idx; // Throw warning if the surface is set without clearing it first assert(decoder.window[idx] == nullptr); @@ -59,50 +68,57 @@ void VideoDecoder::setOutputSurface(JNIEnv *env, jobject surface, jint idx) { } } -void -VideoDecoder::registerOnDecoderRatioChangedCallback(DECODER_RATIO_CHANGED decoderRatioChangedC) { +void VideoDecoder::registerOnDecoderRatioChangedCallback(DECODER_RATIO_CHANGED decoderRatioChangedC) +{ onDecoderRatioChangedCallback = std::move(decoderRatioChangedC); } -void VideoDecoder::registerOnDecodingInfoChangedCallback( - DECODING_INFO_CHANGED_CALLBACK decodingInfoChangedCallback) { +void VideoDecoder::registerOnDecodingInfoChangedCallback(DECODING_INFO_CHANGED_CALLBACK decodingInfoChangedCallback) +{ onDecodingInfoChangedCallback = std::move(decodingInfoChangedCallback); } -void VideoDecoder::interpretNALU(const NALU &nalu) { +void VideoDecoder::interpretNALU(const NALU& nalu) +{ // TODO: RN switching between h264 / h265 requires re-setting the surface - IS_H265 = nalu.IS_H265_PACKET; + IS_H265 = nalu.IS_H265_PACKET; decodingInfo.nCodec = IS_H265; - //we need this lock, since the receiving/parsing/feeding does not run on the same thread who sets the input surface + // we need this lock, since the receiving/parsing/feeding does not run on the same thread who sets the input surface std::lock_guard lock(mMutexInputPipe); decodingInfo.nNALU++; - if (nalu.getSize() <= 4) { - //No data in NALU (e.g at the beginning of a stream) + if (nalu.getSize() <= 4) + { + // No data in NALU (e.g at the beginning of a stream) return; } nNALUBytesFed.add(nalu.getSize()); - if (inputPipeClosed) { + if (inputPipeClosed) + { MLOGD << "inputPipeClosed."; - //A feedD thread (e.g. file or udp) thread might be running even tough no output surface was set - //But at least we can buffer the sps/pps data + // A feedD thread (e.g. file or udp) thread might be running even tough no output surface was set + // But at least we can buffer the sps/pps data mKeyFrameFinder.saveIfKeyFrame(nalu); return; } - if (decoder.configured[0] || decoder.configured[1]) { + if (decoder.configured[0] || decoder.configured[1]) + { feedDecoder(nalu, 0); feedDecoder(nalu, 1); decodingInfo.nNALUSFeeded++; // manually feeding AUDs doesn't seem to change anything for high latency streams // Only for the x264 sw encoded example stream it might improve latency slightly - //if(!nalu.IS_H265_PACKET && nalu.get_nal_unit_type()==NAL_UNIT_TYPE_CODED_SLICE_NON_IDR){ - //MLOGD<<"Feeding special AUD"; - //feedDecoder(NALU::createExampleH264_AUD()); + // if(!nalu.IS_H265_PACKET && nalu.get_nal_unit_type()==NAL_UNIT_TYPE_CODED_SLICE_NON_IDR){ + // MLOGD<<"Feeding special AUD"; + // feedDecoder(NALU::createExampleH264_AUD()); //} - } else { + } + else + { // Store sps,pps, vps(H265 only) // As soon as enough data has been buffered to initialize the decoder,do so. mKeyFrameFinder.saveIfKeyFrame(nalu); - if (mKeyFrameFinder.allKeyFramesAvailable(IS_H265)) { + if (mKeyFrameFinder.allKeyFramesAvailable(IS_H265)) + { MLOGD << "Configuring decoder..."; configureStartDecoder(0); configureStartDecoder(1); @@ -110,27 +126,30 @@ void VideoDecoder::interpretNALU(const NALU &nalu) { } } -void VideoDecoder::configureStartDecoder(int idx) { - if(decoder.window[idx] == nullptr) - return; +void VideoDecoder::configureStartDecoder(int idx) +{ + if (decoder.window[idx] == nullptr) return; const std::string MIME = IS_H265 ? "video/hevc" : "video/avc"; - decoder.codec[idx] = AMediaCodec_createDecoderByType(MIME.c_str()); + decoder.codec[idx] = AMediaCodec_createDecoderByType(MIME.c_str()); - AMediaFormat *format = AMediaFormat_new(); + AMediaFormat* format = AMediaFormat_new(); AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, MIME.c_str()); - //AMediaFormat_setInt32(format, "low-latency", 1); - //AMediaFormat_setInt32(format, "vendor.low-latency.enable", 1); - //AMediaFormat_setInt32(format, "vendor.qti-ext-dec-low-latency.enable", 1); - //AMediaFormat_setInt32(format, "vendor.hisi-ext-low-latency-video-dec.video-scene-for-low-latency-req", 1); - //AMediaFormat_setInt32(format, "vendor.rtc-ext-dec-low-latency.enable", 1); + // AMediaFormat_setInt32(format, "low-latency", 1); + // AMediaFormat_setInt32(format, "vendor.low-latency.enable", 1); + // AMediaFormat_setInt32(format, "vendor.qti-ext-dec-low-latency.enable", 1); + // AMediaFormat_setInt32(format, "vendor.hisi-ext-low-latency-video-dec.video-scene-for-low-latency-req", 1); + // AMediaFormat_setInt32(format, "vendor.rtc-ext-dec-low-latency.enable", 1); // MediaCodec supports two priorities: 0 - realtime, 1 - best effort - //AMediaFormat_setInt32(format, "priority", 0); + // AMediaFormat_setInt32(format, "priority", 0); - if (IS_H265) { + if (IS_H265) + { h265_configureAMediaFormat(mKeyFrameFinder, format); - } else { + } + else + { h264_configureAMediaFormat(mKeyFrameFinder, format); } @@ -139,41 +158,49 @@ void VideoDecoder::configureStartDecoder(int idx) { auto status = AMediaCodec_configure(decoder.codec[idx], format, decoder.window[idx], nullptr, 0); AMediaFormat_delete(format); - switch (status) { - case AMEDIA_OK: { + switch (status) + { + case AMEDIA_OK: + { MLOGD << "AMediaCodec_configure: OK"; break; } - case AMEDIA_ERROR_UNKNOWN: { + case AMEDIA_ERROR_UNKNOWN: + { MLOGD << "AMediaCodec_configure: AMEDIA_ERROR_UNKNOWN"; break; } - case AMEDIA_ERROR_MALFORMED: { + case AMEDIA_ERROR_MALFORMED: + { MLOGD << "AMediaCodec_configure: AMEDIA_ERROR_MALFORMED"; break; } - case AMEDIA_ERROR_UNSUPPORTED: { + case AMEDIA_ERROR_UNSUPPORTED: + { MLOGD << "AMediaCodec_configure: AMEDIA_ERROR_UNSUPPORTED"; break; } - case AMEDIA_ERROR_INVALID_OBJECT: { + case AMEDIA_ERROR_INVALID_OBJECT: + { MLOGD << "AMediaCodec_configure: AMEDIA_ERROR_INVALID_OBJECT"; break; } - case AMEDIA_ERROR_INVALID_PARAMETER: { + case AMEDIA_ERROR_INVALID_PARAMETER: + { MLOGD << "AMediaCodec_configure: AMEDIA_ERROR_INVALID_PARAMETER"; break; } - default: { + default: + { break; } } - - if (decoder.codec[idx] == nullptr) { + if (decoder.codec[idx] == nullptr) + { MLOGD << "Cannot configure decoder"; - //set csd-0 and csd-1 back to 0, maybe they were just faulty but we have better luck with the next ones - //mKeyFrameFinder.reset(); + // set csd-0 and csd-1 back to 0, maybe they were just faulty but we have better luck with the next ones + // mKeyFrameFinder.reset(); return; } AMediaCodec_start(decoder.codec[idx]); @@ -182,123 +209,143 @@ void VideoDecoder::configureStartDecoder(int idx) { decoder.configured[idx] = true; } -void VideoDecoder::feedDecoder(const NALU &nalu, int idx) { - if(!decoder.codec[idx]) - return; - const auto now = std::chrono::steady_clock::now(); +void VideoDecoder::feedDecoder(const NALU& nalu, int idx) +{ + if (!decoder.codec[idx]) return; + const auto now = std::chrono::steady_clock::now(); const auto deltaParsing = now - nalu.creationTime; - while (true) { + while (true) + { const auto index = AMediaCodec_dequeueInputBuffer(decoder.codec[idx], BUFFER_TIMEOUT_US); - if (index >= 0) { - size_t inputBufferSize; - uint8_t *buf = AMediaCodec_getInputBuffer(decoder.codec[idx], (size_t) index, - &inputBufferSize); + if (index >= 0) + { + size_t inputBufferSize; + uint8_t* buf = AMediaCodec_getInputBuffer(decoder.codec[idx], (size_t) index, &inputBufferSize); // I have not seen any case where the input buffer returned by MediaCodec is too small to hold the NALU // But better be safe than crashing with a memory exception - if (nalu.getSize() > inputBufferSize) { + if (nalu.getSize() > inputBufferSize) + { MLOGD << "Nalu too big" << nalu.getSize(); return; } - int flag = (IS_H265 && (nalu.isSPS() || nalu.isPPS() || nalu.isVPS())) - ? AMEDIACODEC_BUFFER_FLAG_CODEC_CONFIG : 0; + int flag = + (IS_H265 && (nalu.isSPS() || nalu.isPPS() || nalu.isVPS())) ? AMEDIACODEC_BUFFER_FLAG_CODEC_CONFIG : 0; std::memcpy(buf, nalu.getData(), (size_t) nalu.getSize()); - const uint64_t presentationTimeUS = (uint64_t) duration_cast( - steady_clock::now().time_since_epoch()).count(); - AMediaCodec_queueInputBuffer(decoder.codec[idx], (size_t) index, 0, (size_t) nalu.getSize(), - presentationTimeUS, flag); + const uint64_t presentationTimeUS = + (uint64_t) duration_cast(steady_clock::now().time_since_epoch()).count(); + AMediaCodec_queueInputBuffer( + decoder.codec[idx], (size_t) index, 0, (size_t) nalu.getSize(), presentationTimeUS, flag); waitForInputB.add(steady_clock::now() - now); parsingTime.add(deltaParsing); return; - } else if (index == AMEDIACODEC_INFO_TRY_AGAIN_LATER) { - //just try again. But if we had no success in the last 1 second,log a warning and return. + } + else if (index == AMEDIACODEC_INFO_TRY_AGAIN_LATER) + { + // just try again. But if we had no success in the last 1 second,log a warning and return. const auto elapsedTimeTryingForBuffer = std::chrono::steady_clock::now() - now; - if (elapsedTimeTryingForBuffer > std::chrono::seconds(1)) { - // Since OpenHD provides a lossy link it is really unlikely, but possible that we somehow 'break' the codec by feeding corrupt data. - // It will probably recover itself as soon as we feed enough valid data though; + if (elapsedTimeTryingForBuffer > std::chrono::seconds(1)) + { + // Since OpenHD provides a lossy link it is really unlikely, but possible that we somehow 'break' the + // codec by feeding corrupt data. It will probably recover itself as soon as we feed enough valid data + // though; MLOGE << "AMEDIACODEC_INFO_TRY_AGAIN_LATER for more than 1 second " << MyTimeHelper::R(elapsedTimeTryingForBuffer) << "return."; return; } - } else { - //Something went wrong. But we will feed the next NALU soon anyways + } + else + { + // Something went wrong. But we will feed the next NALU soon anyways MLOGD << "dequeueInputBuffer idx " << (int) index << "return."; return; } } } -void VideoDecoder::checkOutputLoop(int idx) { +void VideoDecoder::checkOutputLoop(int idx) +{ NDKThreadHelper::setProcessThreadPriorityAttachDetach(javaVm, -16, "DecoderCheckOutput"); AMediaCodecBufferInfo info; - bool decoderSawEOS = false; - bool decoderProducedUnknown = false; - while (!decoderSawEOS && !decoderProducedUnknown) { - if(!decoder.codec[idx]) - break; - const ssize_t index = AMediaCodec_dequeueOutputBuffer(decoder.codec[idx], &info, - BUFFER_TIMEOUT_US); - if (index >= 0) { - const auto now = steady_clock::now(); - const int64_t nowUS = (int64_t) duration_cast( - now.time_since_epoch()).count(); - //the timestamp for releasing the buffer is in NS, just release as fast as possible (e.g. now) - //https://android.googlesource.com/platform/frameworks/av/+/master/media/ndk/NdkMediaCodec.cpp - //-> renderOutputBufferAndRelease which is in https://android.googlesource.com/platform/frameworks/av/+/3fdb405/media/libstagefright/MediaCodec.cpp + bool decoderSawEOS = false; + bool decoderProducedUnknown = false; + while (!decoderSawEOS && !decoderProducedUnknown) + { + if (!decoder.codec[idx]) break; + const ssize_t index = AMediaCodec_dequeueOutputBuffer(decoder.codec[idx], &info, BUFFER_TIMEOUT_US); + if (index >= 0) + { + const auto now = steady_clock::now(); + const int64_t nowUS = (int64_t) duration_cast(now.time_since_epoch()).count(); + // the timestamp for releasing the buffer is in NS, just release as fast as possible (e.g. now) + // https://android.googlesource.com/platform/frameworks/av/+/master/media/ndk/NdkMediaCodec.cpp + //-> renderOutputBufferAndRelease which is in + //https://android.googlesource.com/platform/frameworks/av/+/3fdb405/media/libstagefright/MediaCodec.cpp //-> Message kWhatReleaseOutputBuffer -> onReleaseOutputBuffer - // also https://android.googlesource.com/platform/frameworks/native/+/5c1139f/libs/gui/SurfaceTexture.cpp - if(!decoder.codec[idx]) - break; + // also https://android.googlesource.com/platform/frameworks/native/+/5c1139f/libs/gui/SurfaceTexture.cpp + if (!decoder.codec[idx]) break; AMediaCodec_releaseOutputBuffer(decoder.codec[idx], (size_t) index, true); - //but the presentationTime is in US - if(idx == 0) + // but the presentationTime is in US + if (idx == 0) { decodingTime.add(std::chrono::microseconds(nowUS - info.presentationTimeUs)); nDecodedFrames.add(1); } - if (info.flags & AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM) { + if (info.flags & AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM) + { MLOGD << "Decoder saw EOS"; decoderSawEOS = true; continue; } - } else if (index == AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED) { + } + else if (index == AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED) + { auto format = AMediaCodec_getOutputFormat(decoder.codec[idx]); - int width = 0, height = 0; + int width = 0, height = 0; AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_WIDTH, &width); AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_HEIGHT, &height); MLOGD << "Actual Width and Height in output " << width << "," << height; - if (idx == 0 && onDecoderRatioChangedCallback != nullptr && width != 0 && height != 0) { + if (idx == 0 && onDecoderRatioChangedCallback != nullptr && width != 0 && height != 0) + { onDecoderRatioChangedCallback({width, height}); } MLOGD << "AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED " << width << " " << height << " " << AMediaFormat_toString(format); - } else if (index == AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED) { + } + else if (index == AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED) + { MLOGD << "AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED"; - } else if (index == AMEDIACODEC_INFO_TRY_AGAIN_LATER) { - //MLOGD<<"AMEDIACODEC_INFO_TRY_AGAIN_LATER"; - } else { + } + else if (index == AMEDIACODEC_INFO_TRY_AGAIN_LATER) + { + // MLOGD<<"AMEDIACODEC_INFO_TRY_AGAIN_LATER"; + } + else + { // Most like AMediaCodec_stop() was called MLOGD << "dequeueOutputBuffer idx: " << (int) index << " .Exit."; decoderProducedUnknown = true; continue; } - //every 2 seconds recalculate the current fps and bitrate - const auto now = steady_clock::now(); + // every 2 seconds recalculate the current fps and bitrate + const auto now = steady_clock::now(); const auto delta = now - decodingInfo.lastCalculation; - if (idx == 0 && delta > DECODING_INFO_RECALCULATION_INTERVAL) { + if (idx == 0 && delta > DECODING_INFO_RECALCULATION_INTERVAL) + { decodingInfo.lastCalculation = steady_clock::now(); - decodingInfo.currentFPS = (float) nDecodedFrames.getDeltaSinceLastCall() / - (float) duration_cast(delta).count(); - decodingInfo.currentKiloBitsPerSecond = ((float) nNALUBytesFed.getDeltaSinceLastCall() / - duration_cast(delta).count()) / - 1024.0f * 8.0f; - //and recalculate the avg latencies. If needed,also print the log. - decodingInfo.avgDecodingTime_ms = decodingTime.getAvg_ms(); - decodingInfo.avgParsingTime_ms = parsingTime.getAvg_ms(); + decodingInfo.currentFPS = + (float) nDecodedFrames.getDeltaSinceLastCall() / (float) duration_cast(delta).count(); + decodingInfo.currentKiloBitsPerSecond = + ((float) nNALUBytesFed.getDeltaSinceLastCall() / duration_cast(delta).count()) / 1024.0f * + 8.0f; + // and recalculate the avg latencies. If needed,also print the log. + decodingInfo.avgDecodingTime_ms = decodingTime.getAvg_ms(); + decodingInfo.avgParsingTime_ms = parsingTime.getAvg_ms(); decodingInfo.avgWaitForInputBTime_ms = waitForInputB.getAvg_ms(); - decodingInfo.nDecodedFrames = nDecodedFrames.getAbsolute(); + decodingInfo.nDecodedFrames = nDecodedFrames.getAbsolute(); printAvgLog(); - if (onDecodingInfoChangedCallback != nullptr) { + if (onDecodingInfoChangedCallback != nullptr) + { onDecodingInfoChangedCallback(decodingInfo); } } @@ -306,32 +353,33 @@ void VideoDecoder::checkOutputLoop(int idx) { MLOGD << "Exit CheckOutputLoop"; } -void VideoDecoder::printAvgLog() { - if (PRINT_DEBUG_INFO) { +void VideoDecoder::printAvgLog() +{ + if (PRINT_DEBUG_INFO) + { auto now = steady_clock::now(); - if ((now - lastLog) > TIME_BETWEEN_LOGS) { + if ((now - lastLog) > TIME_BETWEEN_LOGS) + { lastLog = now; std::ostringstream frameLog; frameLog << std::fixed; float avgDecodingLatencySum = - decodingInfo.avgParsingTime_ms + decodingInfo.avgWaitForInputBTime_ms + - decodingInfo.avgDecodingTime_ms; - frameLog << "......................Decoding Latency Averages......................" << - "\nParsing:" << decodingInfo.avgParsingTime_ms + decodingInfo.avgParsingTime_ms + decodingInfo.avgWaitForInputBTime_ms + decodingInfo.avgDecodingTime_ms; + frameLog << "......................Decoding Latency Averages......................" + << "\nParsing:" << decodingInfo.avgParsingTime_ms << " | WaitInputBuffer:" << decodingInfo.avgWaitForInputBTime_ms << " | Decoding:" << decodingInfo.avgDecodingTime_ms - << " | Decoding Latency Sum:" << avgDecodingLatencySum << - "\nN NALUS:" << decodingInfo.nNALU - << " | N NALUES feeded:" << decodingInfo.nNALUSFeeded << " | N Decoded Frames:" - << nDecodedFrames.getAbsolute() << - "\nFPS:" << decodingInfo.currentFPS + << " | Decoding Latency Sum:" << avgDecodingLatencySum << "\nN NALUS:" << decodingInfo.nNALU + << " | N NALUES feeded:" << decodingInfo.nNALUSFeeded + << " | N Decoded Frames:" << nDecodedFrames.getAbsolute() << "\nFPS:" << decodingInfo.currentFPS << " | Codec:" << (decodingInfo.nCodec ? "H265" : "H264"); MLOGD << frameLog.str(); } } } -void VideoDecoder::resetStatistics() { +void VideoDecoder::resetStatistics() +{ nDecodedFrames.reset(); nNALUBytesFed.reset(); parsingTime.reset(); diff --git a/app/videonative/src/main/cpp/VideoDecoder.h b/app/videonative/src/main/cpp/VideoDecoder.h index 34fd92a..bf815a2 100644 --- a/app/videonative/src/main/cpp/VideoDecoder.h +++ b/app/videonative/src/main/cpp/VideoDecoder.h @@ -5,139 +5,134 @@ #ifndef FPVUE_VIDEODECODER_H #define FPVUE_VIDEODECODER_H -#include -#include #include +#include #include +#include +#include #include #include -#include -#include "helper/TimeHelper.hpp" -#include "NALU/NALU.hpp" #include "NALU/KeyFrameFinder.hpp" +#include "NALU/NALU.hpp" +#include "helper/TimeHelper.hpp" -struct DecodingInfo { - std::chrono::steady_clock::time_point lastCalculation = std::chrono::steady_clock::now(); - long nNALU = 0; - long nNALUSFeeded = 0; - long nDecodedFrames = 0; - long nCodec = 0; - float currentFPS = 0; - float currentKiloBitsPerSecond = 0; - float avgParsingTime_ms = 0; - float avgWaitForInputBTime_ms = 0; - float avgDecodingTime_ms = 0; - - bool operator==(const DecodingInfo &d2) const { - return nNALU == d2.nNALU && nNALUSFeeded == d2.nNALUSFeeded && - currentFPS == d2.currentFPS && - currentKiloBitsPerSecond == d2.currentKiloBitsPerSecond && - avgParsingTime_ms == d2.avgParsingTime_ms && - avgWaitForInputBTime_ms == d2.avgWaitForInputBTime_ms && - avgDecodingTime_ms == d2.avgDecodingTime_ms; +struct DecodingInfo +{ + std::chrono::steady_clock::time_point lastCalculation = std::chrono::steady_clock::now(); + long nNALU = 0; + long nNALUSFeeded = 0; + long nDecodedFrames = 0; + long nCodec = 0; + float currentFPS = 0; + float currentKiloBitsPerSecond = 0; + float avgParsingTime_ms = 0; + float avgWaitForInputBTime_ms = 0; + float avgDecodingTime_ms = 0; + + bool operator==(const DecodingInfo& d2) const + { + return nNALU == d2.nNALU && nNALUSFeeded == d2.nNALUSFeeded && currentFPS == d2.currentFPS && + currentKiloBitsPerSecond == d2.currentKiloBitsPerSecond && avgParsingTime_ms == d2.avgParsingTime_ms && + avgWaitForInputBTime_ms == d2.avgWaitForInputBTime_ms && avgDecodingTime_ms == d2.avgDecodingTime_ms; } - bool operator!=(const DecodingInfo &d2) const { - return !(*this == d2); - } + bool operator!=(const DecodingInfo& d2) const { return !(*this == d2); } }; -struct VideoRatio { - int width = 0; +struct VideoRatio +{ + int width = 0; int height = 0; - bool operator==(const VideoRatio &b) const { - return width == b.width && height == b.height; - } + bool operator==(const VideoRatio& b) const { return width == b.width && height == b.height; } - bool operator!=(const VideoRatio &b) const { - return !(*this == b); - } + bool operator!=(const VideoRatio& b) const { return !(*this == b); } }; // Handles decoding of .h264 and .h265 video // with low latency. Uses the AMediaCodec api -class VideoDecoder { -private: - struct Decoder { - bool configured[2] = {false, false}; - AMediaCodec *codec[2] = {nullptr, nullptr}; - ANativeWindow *window[2] = {nullptr, nullptr}; +class VideoDecoder +{ + private: + struct Decoder + { + bool configured[2] = {false, false}; + AMediaCodec* codec[2] = {nullptr, nullptr}; + ANativeWindow* window[2] = {nullptr, nullptr}; }; -public: - //Make sure to do no heavy lifting on this callback, since it is called from the low-latency mCheckOutputThread thread (best to copy values and leave processing to another thread) - //The decoding info callback is called every DECODING_INFO_RECALCULATION_INTERVAL_MS + + public: + // Make sure to do no heavy lifting on this callback, since it is called from the low-latency mCheckOutputThread + // thread (best to copy values and leave processing to another thread) The decoding info callback is called every + // DECODING_INFO_RECALCULATION_INTERVAL_MS typedef std::function DECODING_INFO_CHANGED_CALLBACK; - //The decoder ratio callback is called every time the output format changes + // The decoder ratio callback is called every time the output format changes typedef std::function DECODER_RATIO_CHANGED; -public: - //We cannot initialize the Decoder until we have SPS and PPS data - - //when streaming this data will be available at some point in future - //Therefore we don't allocate the MediaCodec resources here - VideoDecoder(JNIEnv *env); + + public: + // We cannot initialize the Decoder until we have SPS and PPS data - + // when streaming this data will be available at some point in future + // Therefore we don't allocate the MediaCodec resources here + VideoDecoder(JNIEnv* env); // This call acquires or releases the output surface // After acquiring the surface, the decoder will be started as soon as enough configuration data was passed to it // When releasing the surface, the decoder will be stopped if running and any resources will be freed // After releasing the surface it is safe for the android os to delete it - void setOutputSurface(JNIEnv *env, jobject surface, jint idx); + void setOutputSurface(JNIEnv* env, jobject surface, jint idx); - //register the specified callbacks. Only one can be registered at a time + // register the specified callbacks. Only one can be registered at a time void registerOnDecoderRatioChangedCallback(DECODER_RATIO_CHANGED decoderRatioChangedC); - void registerOnDecodingInfoChangedCallback( - DECODING_INFO_CHANGED_CALLBACK decodingInfoChangedCallback); + void registerOnDecodingInfoChangedCallback(DECODING_INFO_CHANGED_CALLBACK decodingInfoChangedCallback); - //If the decoder has been configured, feed NALU. Else search for configuration data and - //configure as soon as possible - // If the input pipe was closed (surface has been removed or is not set yet), only buffer key frames - void interpretNALU(const NALU &nalu); + // If the decoder has been configured, feed NALU. Else search for configuration data and + // configure as soon as possible + // If the input pipe was closed (surface has been removed or is not set yet), only buffer key frames + void interpretNALU(const NALU& nalu); -private: - //Initialize decoder with SPS / PPS data from KeyFrameFinder - //Set Decoder.configured to true on success + private: + // Initialize decoder with SPS / PPS data from KeyFrameFinder + // Set Decoder.configured to true on success void configureStartDecoder(int idx); - //Wait for input buffer to become available before feeding NALU - void feedDecoder(const NALU &nalu, int idx); + // Wait for input buffer to become available before feeding NALU + void feedDecoder(const NALU& nalu, int idx); - //Runs until EOS arrives at output buffer or decoder is stopped + // Runs until EOS arrives at output buffer or decoder is stopped void checkOutputLoop(int idx); - //Debug log + // Debug log void printAvgLog(); void resetStatistics(); - std::unique_ptr mCheckOutputThread[2] = {nullptr, nullptr}; - bool USE_SW_DECODER_INSTEAD = false; - //Holds the AMediaCodec instance, as well as the state (configured or not configured) - Decoder decoder{}; + std::unique_ptr mCheckOutputThread[2] = {nullptr, nullptr}; + bool USE_SW_DECODER_INSTEAD = false; + // Holds the AMediaCodec instance, as well as the state (configured or not configured) + Decoder decoder{}; DecodingInfo decodingInfo; // The input pipe is closed until we set a valid surface - bool inputPipeClosed = true; - std::mutex mMutexInputPipe; - DECODER_RATIO_CHANGED onDecoderRatioChangedCallback = nullptr; + bool inputPipeClosed = true; + std::mutex mMutexInputPipe; + DECODER_RATIO_CHANGED onDecoderRatioChangedCallback = nullptr; DECODING_INFO_CHANGED_CALLBACK onDecodingInfoChangedCallback = nullptr; // So we can temporarily attach the output thread to the vm and make ndk calls - JavaVM *javaVm = nullptr; + JavaVM* javaVm = nullptr; std::chrono::steady_clock::time_point lastLog = std::chrono::steady_clock::now(); - RelativeCalculator nDecodedFrames; - RelativeCalculator nNALUBytesFed; - AvgCalculator parsingTime; - AvgCalculator waitForInputB; - AvgCalculator decodingTime; - //Every n ms re-calculate the Decoding info - static const constexpr auto DECODING_INFO_RECALCULATION_INTERVAL = std::chrono::milliseconds( - 1000); - static constexpr const bool PRINT_DEBUG_INFO = true; - static constexpr auto TIME_BETWEEN_LOGS = std::chrono::seconds(5); - static constexpr int64_t BUFFER_TIMEOUT_US = - 17 * 1000; // 17ms (a little bit more than 17 ms (==60 fps)) -private: + RelativeCalculator nDecodedFrames; + RelativeCalculator nNALUBytesFed; + AvgCalculator parsingTime; + AvgCalculator waitForInputB; + AvgCalculator decodingTime; + // Every n ms re-calculate the Decoding info + static const constexpr auto DECODING_INFO_RECALCULATION_INTERVAL = std::chrono::milliseconds(1000); + static constexpr const bool PRINT_DEBUG_INFO = true; + static constexpr auto TIME_BETWEEN_LOGS = std::chrono::seconds(5); + static constexpr int64_t BUFFER_TIMEOUT_US = 17 * 1000; // 17ms (a little bit more than 17 ms (==60 fps)) + private: KeyFrameFinder mKeyFrameFinder; - bool IS_H265 = false; + bool IS_H265 = false; }; - -#endif //FPVUE_VIDEODECODER_H +#endif // FPVUE_VIDEODECODER_H diff --git a/app/videonative/src/main/cpp/VideoPlayer.cpp b/app/videonative/src/main/cpp/VideoPlayer.cpp index a60921f..58bfbbc 100644 --- a/app/videonative/src/main/cpp/VideoPlayer.cpp +++ b/app/videonative/src/main/cpp/VideoPlayer.cpp @@ -1,90 +1,104 @@ -#include #include "VideoPlayer.h" -#include "AndroidThreadPrioValues.hpp" -#include "helper/NDKThreadHelper.hpp" -#include "helper/NDKHelper.hpp" -#include -#include #include #include +#include +#include +#include #include +#include "AndroidThreadPrioValues.hpp" +#include "helper/NDKHelper.hpp" +#include "helper/NDKThreadHelper.hpp" #define TAG "pixelpilot" -VideoPlayer::VideoPlayer(JNIEnv *env, jobject context) : - mParser{std::bind(&VideoPlayer::onNewNALU, this, std::placeholders::_1)}, - videoDecoder(env) { +VideoPlayer::VideoPlayer(JNIEnv* env, jobject context) + : mParser{std::bind(&VideoPlayer::onNewNALU, this, std::placeholders::_1)}, videoDecoder(env) +{ env->GetJavaVM(&javaVm); - videoDecoder.registerOnDecoderRatioChangedCallback([this](const VideoRatio ratio) { - const bool changed = ratio != this->latestVideoRatio; - this->latestVideoRatio = ratio; - latestVideoRatioChanged = changed; - }); - videoDecoder.registerOnDecodingInfoChangedCallback([this](const DecodingInfo info) { - const bool changed = info != this->latestDecodingInfo; - this->latestDecodingInfo = info; - latestDecodingInfoChanged = changed; - }); + videoDecoder.registerOnDecoderRatioChangedCallback( + [this](const VideoRatio ratio) + { + const bool changed = ratio != this->latestVideoRatio; + this->latestVideoRatio = ratio; + latestVideoRatioChanged = changed; + }); + videoDecoder.registerOnDecodingInfoChangedCallback( + [this](const DecodingInfo info) + { + const bool changed = info != this->latestDecodingInfo; + this->latestDecodingInfo = info; + latestDecodingInfoChanged = changed; + }); } -static int write_callback(int64_t offset, const void *buffer, size_t size, void *token) { - FILE *f = (FILE *) token; +static int write_callback(int64_t offset, const void* buffer, size_t size, void* token) +{ + FILE* f = (FILE*) token; fseek(f, offset, SEEK_SET); return fwrite(buffer, 1, size, f) != size; } -void VideoPlayer::processQueue() { - ::FILE *fout = fdopen(dvr_fd, "wb"); - MP4E_mux_t *mux = MP4E_open(0 /*sequential_mode*/, dvr_mp4_fragmentation, fout, write_callback); +void VideoPlayer::processQueue() +{ + ::FILE* fout = fdopen(dvr_fd, "wb"); + MP4E_mux_t* mux = MP4E_open(0 /*sequential_mode*/, dvr_mp4_fragmentation, fout, write_callback); mp4_h26x_writer_t mp4wr; - float framerate = 0; - if(mux == nullptr) + float framerate = 0; + if (mux == nullptr) { - __android_log_print(ANDROID_LOG_ERROR, TAG, - "dvr open failed"); + __android_log_print(ANDROID_LOG_ERROR, TAG, "dvr open failed"); return; } - while (true) { + while (true) + { last_dvr_write = get_time_ms(); std::unique_lock lock(mtx); cv.wait(lock, [this] { return !naluQueue.empty() || stopFlag; }); - if (stopFlag) { + if (stopFlag) + { break; } - if (!naluQueue.empty()) { + if (!naluQueue.empty()) + { NALU nalu = naluQueue.front(); - if (framerate == 0) { - if (latestDecodingInfo.currentFPS <= 0) { + if (framerate == 0) + { + if (latestDecodingInfo.currentFPS <= 0) + { continue; } - if (MP4E_STATUS_OK != mp4_h26x_write_init(&mp4wr, mux, latestVideoRatio.width, - latestVideoRatio.height, - nalu.IS_H265_PACKET)) { - __android_log_print(ANDROID_LOG_DEBUG, TAG, - "error: mp4_h26x_write_init failed"); + if (MP4E_STATUS_OK != + mp4_h26x_write_init( + &mp4wr, mux, latestVideoRatio.width, latestVideoRatio.height, nalu.IS_H265_PACKET)) + { + __android_log_print(ANDROID_LOG_DEBUG, TAG, "error: mp4_h26x_write_init failed"); } framerate = latestDecodingInfo.currentFPS; - __android_log_print(ANDROID_LOG_DEBUG, TAG, - "mp4 init with fps=%.2f, res=%dx%d, hevc=%d", framerate, - latestVideoRatio.width, latestVideoRatio.height, - nalu.IS_H265_PACKET); + __android_log_print( + ANDROID_LOG_DEBUG, + TAG, + "mp4 init with fps=%.2f, res=%dx%d, hevc=%d", + framerate, + latestVideoRatio.width, + latestVideoRatio.height, + nalu.IS_H265_PACKET); } naluQueue.pop(); lock.unlock(); // Process the NALU - auto res = mp4_h26x_write_nal(&mp4wr, nalu.getData(), nalu.getSize(), - 90000 / framerate); - if (MP4E_STATUS_OK != res) { - __android_log_print(ANDROID_LOG_DEBUG, TAG, - "mp4_h26x_write_nal failed with %d", res); + auto res = mp4_h26x_write_nal(&mp4wr, nalu.getData(), nalu.getSize(), 90000 / framerate); + if (MP4E_STATUS_OK != res) + { + __android_log_print(ANDROID_LOG_DEBUG, TAG, "mp4_h26x_write_nal failed with %d", res); } } } MP4E_close(mux); mp4_h26x_write_close(&mp4wr); - if (fout) { + if (fout) + { fclose(fout); fout = NULL; } @@ -92,18 +106,22 @@ void VideoPlayer::processQueue() { __android_log_print(ANDROID_LOG_DEBUG, TAG, "dvr thread done"); } -//Not yet parsed bit stream (e.g. raw h264 or rtp data) -void VideoPlayer::onNewRTPData(const uint8_t* data, const std::size_t data_length) { +// Not yet parsed bit stream (e.g. raw h264 or rtp data) +void VideoPlayer::onNewRTPData(const uint8_t* data, const std::size_t data_length) +{ // Parse the RTP packet const RTP::RTPPacket rtpPacket(data, data_length); - uint16_t idx = rtpPacket.header.getSequence(); + uint16_t idx = rtpPacket.header.getSequence(); // Define the callback based on payload type - auto callback = [&](const uint8_t* packet_data, std::size_t packet_length) { - if (rtpPacket.header.payload == RTP_PAYLOAD_TYPE_AUDIO) { + auto callback = [&](const uint8_t* packet_data, std::size_t packet_length) + { + if (rtpPacket.header.payload == RTP_PAYLOAD_TYPE_AUDIO) + { audioDecoder.enqueueAudio(packet_data, packet_length); } - else { + else + { mParser.parse_rtp_stream(packet_data, packet_length); } }; @@ -112,41 +130,48 @@ void VideoPlayer::onNewRTPData(const uint8_t* data, const std::size_t data_lengt mBufferedPacketQueue.processPacket(idx, data, data_length, callback); } -void VideoPlayer::onNewNALU(const NALU &nalu) { +void VideoPlayer::onNewNALU(const NALU& nalu) +{ videoDecoder.interpretNALU(nalu); - if (dvr_fd <= 0 || latestDecodingInfo.currentFPS <= 0) { + if (dvr_fd <= 0 || latestDecodingInfo.currentFPS <= 0) + { return; } // Copy data to write if from a different thread. - uint8_t *m_data_copy = new uint8_t[nalu.getSize()]; + uint8_t* m_data_copy = new uint8_t[nalu.getSize()]; memcpy(m_data_copy, nalu.getData(), nalu.getSize()); NALU nalu_(m_data_copy, nalu.getSize(), nalu.IS_H265_PACKET); enqueueNALU(nalu_); } -void VideoPlayer::setVideoSurface(JNIEnv *env, jobject surface, jint i) { - //reset the parser so the statistics start again from 0 - // mParser.reset(); - //set the jni object for settings +void VideoPlayer::setVideoSurface(JNIEnv* env, jobject surface, jint i) +{ + // reset the parser so the statistics start again from 0 + // mParser.reset(); + // set the jni object for settings videoDecoder.setOutputSurface(env, surface, i); } - -void VideoPlayer::start(JNIEnv *env, jobject androidContext) { - AAssetManager *assetManager = NDKHelper::getAssetManagerFromContext2(env, androidContext); - //mParser.setLimitFPS(-1); //Default: Real time ! +void VideoPlayer::start(JNIEnv* env, jobject androidContext) +{ + AAssetManager* assetManager = NDKHelper::getAssetManagerFromContext2(env, androidContext); + // mParser.setLimitFPS(-1); //Default: Real time ! const int VS_PORT = 5600; mUDPReceiver.release(); - mUDPReceiver = std::make_unique(javaVm, VS_PORT, "UdpReceiver", -16, - [this](const uint8_t *data, - size_t data_length) { - onNewRTPData(data, data_length); - }, WANTED_UDP_RCVBUF_SIZE); + mUDPReceiver = std::make_unique( + javaVm, + VS_PORT, + "UdpReceiver", + -16, + [this](const uint8_t* data, size_t data_length) { onNewRTPData(data, data_length); }, + WANTED_UDP_RCVBUF_SIZE); mUDPReceiver->startReceiving(); } -void VideoPlayer::stop(JNIEnv *env, jobject androidContext) { - if (mUDPReceiver) { +void VideoPlayer::stop(JNIEnv* env, jobject androidContext) +{ + if (mUDPReceiver) + { mUDPReceiver->stopReceiving(); mUDPReceiver.reset(); } @@ -154,197 +179,211 @@ void VideoPlayer::stop(JNIEnv *env, jobject androidContext) { audioDecoder.stopAudio(); } -std::string VideoPlayer::getInfoString() const { +std::string VideoPlayer::getInfoString() const +{ std::stringstream ss; - if (mUDPReceiver) { + if (mUDPReceiver) + { ss << "Listening for video on port " << mUDPReceiver->getPort(); ss << "\nReceived: " << mUDPReceiver->getNReceivedBytes() << "B" << " | parsed frames: "; // << mParser.nParsedNALUs << " | key frames: " << mParser.nParsedKonfigurationFrames; - } else { + } + else + { ss << "Not receiving udp raw / rtp / rtsp"; } return ss.str(); } - -void VideoPlayer::startDvr(JNIEnv *env, jint fd, jint dvr_fmp4_enabled) { - dvr_fd = dup(fd); +void VideoPlayer::startDvr(JNIEnv* env, jint fd, jint dvr_fmp4_enabled) +{ + dvr_fd = dup(fd); dvr_mp4_fragmentation = dvr_fmp4_enabled; __android_log_print(ANDROID_LOG_DEBUG, TAG, "dvr_fd=%d", dvr_fd); - if (dvr_fd == -1) { - __android_log_print(ANDROID_LOG_DEBUG, TAG, - "Failed to duplicate dvr file descriptor"); + if (dvr_fd == -1) + { + __android_log_print(ANDROID_LOG_DEBUG, TAG, "Failed to duplicate dvr file descriptor"); return; } startProcessing(); } -void VideoPlayer::stopDvr() { +void VideoPlayer::stopDvr() +{ __android_log_print(ANDROID_LOG_DEBUG, TAG, "Stop dvr"); stopProcessing(); } -//----------------------------------------------------JAVA bindings--------------------------------------------------------------- +//----------------------------------------------------JAVA +//bindings--------------------------------------------------------------- #define JNI_METHOD(return_type, method_name) \ - JNIEXPORT return_type JNICALL \ - Java_com_openipc_videonative_VideoPlayer_##method_name + JNIEXPORT return_type JNICALL Java_com_openipc_videonative_VideoPlayer_##method_name -inline jlong jptr(VideoPlayer *videoPlayerN) { +inline jlong jptr(VideoPlayer* videoPlayerN) +{ return reinterpret_cast(videoPlayerN); } -inline VideoPlayer *native(jlong ptr) { - return reinterpret_cast(ptr); +inline VideoPlayer* native(jlong ptr) +{ + return reinterpret_cast(ptr); } - -extern "C" { - - extern "C" -JNIEXPORT jlong JNICALL -Java_com_openipc_videonative_VideoPlayer_nativeInitialize(JNIEnv *env, jclass clazz, - jobject context) { - auto *p = new VideoPlayer(env, context); - return jptr(p); -} - -JNI_METHOD(void, nativeFinalize) -(JNIEnv *env, jclass jclass1, jlong videoPlayerN) { - VideoPlayer *p = native(videoPlayerN); - delete (p); -} +{ + extern "C" JNIEXPORT jlong JNICALL + Java_com_openipc_videonative_VideoPlayer_nativeInitialize(JNIEnv* env, jclass clazz, jobject context) + { + auto* p = new VideoPlayer(env, context); + return jptr(p); + } -JNI_METHOD(void, nativeStart) -(JNIEnv *env, jclass jclass1, jlong videoPlayerN, jobject androidContext) { - native(videoPlayerN)->start(env, androidContext); -} + JNI_METHOD(void, nativeFinalize) + (JNIEnv* env, jclass jclass1, jlong videoPlayerN) + { + VideoPlayer* p = native(videoPlayerN); + delete (p); + } -JNI_METHOD(void, nativeStop) -(JNIEnv *env, jclass jclass1, jlong videoPlayerN, jobject androidContext) { - native(videoPlayerN)->stop(env, androidContext); -} + JNI_METHOD(void, nativeStart) + (JNIEnv* env, jclass jclass1, jlong videoPlayerN, jobject androidContext) + { + native(videoPlayerN)->start(env, androidContext); + } -JNI_METHOD(void, nativeSetVideoSurface) -(JNIEnv *env, jclass jclass1, jlong videoPlayerN, jobject surface, jint index) { - native(videoPlayerN)->setVideoSurface(env, surface, index); -} + JNI_METHOD(void, nativeStop) + (JNIEnv* env, jclass jclass1, jlong videoPlayerN, jobject androidContext) + { + native(videoPlayerN)->stop(env, androidContext); + } -JNI_METHOD(jstring, getVideoInfoString) -(JNIEnv *env, jclass jclass1, jlong testReceiverN) { - VideoPlayer *p = native(testReceiverN); - jstring ret = env->NewStringUTF(p->getInfoString().c_str()); - return ret; -} + JNI_METHOD(void, nativeSetVideoSurface) + (JNIEnv* env, jclass jclass1, jlong videoPlayerN, jobject surface, jint index) + { + native(videoPlayerN)->setVideoSurface(env, surface, index); + } -JNI_METHOD(jboolean, anyVideoDataReceived) -(JNIEnv *env, jclass jclass1, jlong testReceiverN) { - VideoPlayer *p = native(testReceiverN); - if (p->mUDPReceiver == nullptr) { - return (jboolean) false; + JNI_METHOD(jstring, getVideoInfoString) + (JNIEnv* env, jclass jclass1, jlong testReceiverN) + { + VideoPlayer* p = native(testReceiverN); + jstring ret = env->NewStringUTF(p->getInfoString().c_str()); + return ret; } - bool ret = (p->mUDPReceiver->getNReceivedBytes() > 0); - return (jboolean) ret; -} -JNI_METHOD(jboolean, receivingVideoButCannotParse) -(JNIEnv *env, jclass jclass1, jlong testReceiverN) { - VideoPlayer *p = native(testReceiverN); -// if(p->mUDPReceiver){ -// return (jboolean) (p->mUDPReceiver->getNReceivedBytes() > 1024 * 1024 && p->mParser.nParsedNALUs == 0); -// } - return (jboolean) false; -} + JNI_METHOD(jboolean, anyVideoDataReceived) + (JNIEnv* env, jclass jclass1, jlong testReceiverN) + { + VideoPlayer* p = native(testReceiverN); + if (p->mUDPReceiver == nullptr) + { + return (jboolean) false; + } + bool ret = (p->mUDPReceiver->getNReceivedBytes() > 0); + return (jboolean) ret; + } -JNI_METHOD(jboolean, anyVideoBytesParsedSinceLastCall) -(JNIEnv *env, jclass jclass1, jlong testReceiverN) { - VideoPlayer *p = native(testReceiverN); - long nalusSinceLast = 0;// p->mParser.nParsedNALUs - p->nNALUsAtLastCall; - p->nNALUsAtLastCall += nalusSinceLast; - return (jboolean) (nalusSinceLast > 0); -} + JNI_METHOD(jboolean, receivingVideoButCannotParse) + (JNIEnv* env, jclass jclass1, jlong testReceiverN) + { + VideoPlayer* p = native(testReceiverN); + // if(p->mUDPReceiver){ + // return (jboolean) (p->mUDPReceiver->getNReceivedBytes() > 1024 * 1024 && p->mParser.nParsedNALUs == + // 0); + // } + return (jboolean) false; + } + JNI_METHOD(jboolean, anyVideoBytesParsedSinceLastCall) + (JNIEnv* env, jclass jclass1, jlong testReceiverN) + { + VideoPlayer* p = native(testReceiverN); + long nalusSinceLast = 0; // p->mParser.nParsedNALUs - p->nNALUsAtLastCall; + p->nNALUsAtLastCall += nalusSinceLast; + return (jboolean) (nalusSinceLast > 0); + } -JNI_METHOD(void, nativeCallBack) -(JNIEnv *env, jclass jclass1, jobject videoParamsChangedI, jlong testReceiverN) { - VideoPlayer *p = native(testReceiverN); - //Update all java stuff - if (p->latestDecodingInfoChanged || p->latestVideoRatioChanged) { - jclass jClassExtendsIVideoParamsChanged = env->GetObjectClass(videoParamsChangedI); - if (p->latestVideoRatioChanged) { - jmethodID onVideoRatioChangedJAVA = env->GetMethodID(jClassExtendsIVideoParamsChanged, - "onVideoRatioChanged", "(II)V"); - env->CallVoidMethod(videoParamsChangedI, onVideoRatioChangedJAVA, - (jint) p->latestVideoRatio.width, - (jint) p->latestVideoRatio.height); - p->latestVideoRatioChanged = false; - } - if (p->latestDecodingInfoChanged) { - jclass jcDecodingInfo = env->FindClass("com/openipc/videonative/DecodingInfo"); - assert(jcDecodingInfo != nullptr); - jmethodID jcDecodingInfoConstructor = env->GetMethodID(jcDecodingInfo, "", - "(FFFFFIIII)V"); - assert(jcDecodingInfoConstructor != nullptr); - const auto info = p->latestDecodingInfo; - auto decodingInfo = env->NewObject(jcDecodingInfo, jcDecodingInfoConstructor, - (jfloat) info.currentFPS, - (jfloat) info.currentKiloBitsPerSecond, - (jfloat) info.avgParsingTime_ms, - (jfloat) info.avgWaitForInputBTime_ms, - (jfloat) info.avgDecodingTime_ms, (jint) info.nNALU, - (jint) info.nNALUSFeeded, - (jint) info.nDecodedFrames, - (jint) info.nCodec); - assert(decodingInfo != nullptr); - jmethodID onDecodingInfoChangedJAVA = env->GetMethodID(jClassExtendsIVideoParamsChanged, - "onDecodingInfoChanged", - "(Lcom/openipc/videonative/DecodingInfo;)V"); - assert(onDecodingInfoChangedJAVA != nullptr); - env->CallVoidMethod(videoParamsChangedI, onDecodingInfoChangedJAVA, decodingInfo); - p->latestDecodingInfoChanged = false; + JNI_METHOD(void, nativeCallBack) + (JNIEnv* env, jclass jclass1, jobject videoParamsChangedI, jlong testReceiverN) + { + VideoPlayer* p = native(testReceiverN); + // Update all java stuff + if (p->latestDecodingInfoChanged || p->latestVideoRatioChanged) + { + jclass jClassExtendsIVideoParamsChanged = env->GetObjectClass(videoParamsChangedI); + if (p->latestVideoRatioChanged) + { + jmethodID onVideoRatioChangedJAVA = + env->GetMethodID(jClassExtendsIVideoParamsChanged, "onVideoRatioChanged", "(II)V"); + env->CallVoidMethod( + videoParamsChangedI, + onVideoRatioChangedJAVA, + (jint) p->latestVideoRatio.width, + (jint) p->latestVideoRatio.height); + p->latestVideoRatioChanged = false; + } + if (p->latestDecodingInfoChanged) + { + jclass jcDecodingInfo = env->FindClass("com/openipc/videonative/DecodingInfo"); + assert(jcDecodingInfo != nullptr); + jmethodID jcDecodingInfoConstructor = env->GetMethodID(jcDecodingInfo, "", "(FFFFFIIII)V"); + assert(jcDecodingInfoConstructor != nullptr); + const auto info = p->latestDecodingInfo; + auto decodingInfo = env->NewObject( + jcDecodingInfo, + jcDecodingInfoConstructor, + (jfloat) info.currentFPS, + (jfloat) info.currentKiloBitsPerSecond, + (jfloat) info.avgParsingTime_ms, + (jfloat) info.avgWaitForInputBTime_ms, + (jfloat) info.avgDecodingTime_ms, + (jint) info.nNALU, + (jint) info.nNALUSFeeded, + (jint) info.nDecodedFrames, + (jint) info.nCodec); + assert(decodingInfo != nullptr); + jmethodID onDecodingInfoChangedJAVA = env->GetMethodID( + jClassExtendsIVideoParamsChanged, + "onDecodingInfoChanged", + "(Lcom/openipc/videonative/DecodingInfo;)V"); + assert(onDecodingInfoChangedJAVA != nullptr); + env->CallVoidMethod(videoParamsChangedI, onDecodingInfoChangedJAVA, decodingInfo); + p->latestDecodingInfoChanged = false; + } } } } - -} - -extern "C" -JNIEXPORT void JNICALL -Java_com_openipc_videonative_VideoPlayer_nativeStartDvr(JNIEnv *env, jclass clazz, - jlong native_instance, - jint fd, jint fmp4_enabled) { +extern "C" JNIEXPORT void JNICALL Java_com_openipc_videonative_VideoPlayer_nativeStartDvr( + JNIEnv* env, jclass clazz, jlong native_instance, jint fd, jint fmp4_enabled) +{ native(native_instance)->startDvr(env, fd, fmp4_enabled); } -extern "C" -JNIEXPORT void JNICALL -Java_com_openipc_videonative_VideoPlayer_nativeStopDvr(JNIEnv *env, jclass clazz, - jlong native_instance) { +extern "C" JNIEXPORT void JNICALL +Java_com_openipc_videonative_VideoPlayer_nativeStopDvr(JNIEnv* env, jclass clazz, jlong native_instance) +{ native(native_instance)->stopDvr(); } -extern "C" -JNIEXPORT jboolean JNICALL -Java_com_openipc_videonative_VideoPlayer_nativeIsRecording(JNIEnv *env, jclass clazz, - jlong native_instance) { +extern "C" JNIEXPORT jboolean JNICALL +Java_com_openipc_videonative_VideoPlayer_nativeIsRecording(JNIEnv* env, jclass clazz, jlong native_instance) +{ return native(native_instance)->isRecording(); } -extern "C" -JNIEXPORT void JNICALL -Java_com_openipc_videonative_VideoPlayer_nativeStartAudio(JNIEnv *env, jclass clazz, - jlong native_instance) { - if(!native(native_instance)->audioDecoder.isInit) +extern "C" JNIEXPORT void JNICALL +Java_com_openipc_videonative_VideoPlayer_nativeStartAudio(JNIEnv* env, jclass clazz, jlong native_instance) +{ + if (!native(native_instance)->audioDecoder.isInit) { native(native_instance)->audioDecoder.initAudio(); } native(native_instance)->audioDecoder.stopAudioProcessing(); native(native_instance)->audioDecoder.startAudioProcessing(); } -extern "C" -JNIEXPORT void JNICALL -Java_com_openipc_videonative_VideoPlayer_nativeStopAudio(JNIEnv *env, jclass clazz, - jlong native_instance) { +extern "C" JNIEXPORT void JNICALL +Java_com_openipc_videonative_VideoPlayer_nativeStopAudio(JNIEnv* env, jclass clazz, jlong native_instance) +{ native(native_instance)->audioDecoder.stopAudioProcessing(); } \ No newline at end of file diff --git a/app/videonative/src/main/cpp/VideoPlayer.h b/app/videonative/src/main/cpp/VideoPlayer.h index 2b87844..645fe0d 100644 --- a/app/videonative/src/main/cpp/VideoPlayer.h +++ b/app/videonative/src/main/cpp/VideoPlayer.h @@ -4,82 +4,85 @@ #ifndef FPV_VR_VIDEOPLAYERN_H #define FPV_VR_VIDEOPLAYERN_H +#include #include -#include "VideoDecoder.h" +#include +#include +#include #include "AudioDecoder.h" #include "BufferedPacketQueue.h" #include "UdpReceiver.h" -#include "parser/H26XParser.h" +#include "VideoDecoder.h" #include "minimp4.h" -#include -#include -#include -#include +#include "parser/H26XParser.h" #include "time_util.h" -class VideoPlayer { +class VideoPlayer +{ + public: + VideoPlayer(JNIEnv* env, jobject context); -public: - VideoPlayer(JNIEnv *env, jobject context); - - void onNewRTPData(const uint8_t *data, const std::size_t data_length); + void onNewRTPData(const uint8_t* data, const std::size_t data_length); /* * Set the surface the decoder can be configured with. When @param surface==nullptr * It is guaranteed that the surface is not used by the decoder anymore when this call returns */ - void setVideoSurface(JNIEnv *env, jobject surface, jint i); + void setVideoSurface(JNIEnv* env, jobject surface, jint i); /* * Start the receiver and ground recorder if enabled */ - void start(JNIEnv *env, jobject androidContext); + void start(JNIEnv* env, jobject androidContext); /** * Stop the receiver and ground recorder if enabled */ - void stop(JNIEnv *env, jobject androidContext); + void stop(JNIEnv* env, jobject androidContext); /* * Returns a string with the current configuration for debugging */ std::string getInfoString() const; - void startDvr(JNIEnv *env, jint fd, jint fmp4_enabled); + void startDvr(JNIEnv* env, jint fd, jint fmp4_enabled); void stopDvr(); - bool isRecording() { - return (get_time_ms() - last_dvr_write) <= 500; - } + bool isRecording() { return (get_time_ms() - last_dvr_write) <= 500; } -private: - void onNewNALU(const NALU &nalu); + private: + void onNewNALU(const NALU& nalu); - //Assumptions: Max bitrate: 40 MBit/s, Max time to buffer: 500ms - //25 MB should be plenty ! + // Assumptions: Max bitrate: 40 MBit/s, Max time to buffer: 500ms + // 25 MB should be plenty ! static constexpr const size_t WANTED_UDP_RCVBUF_SIZE = 1024 * 1024 * 25; // Retrieve settings from shared preferences - enum SOURCE_TYPE_OPTIONS { - UDP, FILE, ASSETS, VIA_FFMPEG_URL, EXTERNAL + enum SOURCE_TYPE_OPTIONS + { + UDP, + FILE, + ASSETS, + VIA_FFMPEG_URL, + EXTERNAL }; - const std::string GROUND_RECORDING_DIRECTORY; - JavaVM *javaVm = nullptr; - H26XParser mParser; + const std::string GROUND_RECORDING_DIRECTORY; + JavaVM* javaVm = nullptr; + H26XParser mParser; BufferedPacketQueue mBufferedPacketQueue; - // DVR attributes - int dvr_fd; - std::queue naluQueue; - std::mutex mtx; + int dvr_fd; + std::queue naluQueue; + std::mutex mtx; std::condition_variable cv; - bool stopFlag = false; - std::thread processingThread; - int dvr_mp4_fragmentation = 0; - uint64_t last_dvr_write = 0; + bool stopFlag = false; + std::thread processingThread; + int dvr_mp4_fragmentation = 0; + uint64_t last_dvr_write = 0; - void enqueueNALU(const NALU &nalu) { + void enqueueNALU(const NALU& nalu) + { { std::lock_guard lock(mtx); naluQueue.push(nalu); @@ -87,37 +90,40 @@ class VideoPlayer { cv.notify_one(); } - void startProcessing() { - stopFlag = false; + void startProcessing() + { + stopFlag = false; processingThread = std::thread(&VideoPlayer::processQueue, this); } - void stopProcessing() { + void stopProcessing() + { { std::lock_guard lock(mtx); stopFlag = true; } cv.notify_all(); - if (processingThread.joinable()) { + if (processingThread.joinable()) + { processingThread.join(); } } void processQueue(); -public: - AudioDecoder audioDecoder; - VideoDecoder videoDecoder; + public: + AudioDecoder audioDecoder; + VideoDecoder videoDecoder; std::unique_ptr mUDPReceiver; - long nNALUsAtLastCall = 0; + long nNALUsAtLastCall = 0; -public: - DecodingInfo latestDecodingInfo{}; + public: + DecodingInfo latestDecodingInfo{}; std::atomic latestDecodingInfoChanged = false; - VideoRatio latestVideoRatio{}; + VideoRatio latestVideoRatio{}; std::atomic latestVideoRatioChanged = false; bool lastFrameWasAUD = false; }; -#endif //FPV_VR_VIDEOPLAYERN_H +#endif // FPV_VR_VIDEOPLAYERN_H diff --git a/app/videonative/src/main/cpp/helper/AndroidLogger.hpp b/app/videonative/src/main/cpp/helper/AndroidLogger.hpp index a7e5f54..378eea7 100644 --- a/app/videonative/src/main/cpp/helper/AndroidLogger.hpp +++ b/app/videonative/src/main/cpp/helper/AndroidLogger.hpp @@ -5,10 +5,10 @@ #ifndef FPV_VR_PRIVATE_MDEBUG_H #define FPV_VR_PRIVATE_MDEBUG_H -#include "android/log.h" #include -#include #include +#include +#include "android/log.h" // remove any old c style definitions that might have slip trough some header files #ifdef LOGD @@ -20,162 +20,177 @@ // See https://medium.com/@geierconstantinabc/best-way-to-log-in-android-native-code-c-style-7461005610f6 // Handles logging in all my android studio projects with native code -// inspired by https://android.googlesource.com/platform/system/core/+/refs/heads/master/base/include/android-base/logging.h -// Most notable difference : I have LOGD(TAG) they have LOG(SEVERITY) -class AndroidLogger { -public: +// inspired by +// https://android.googlesource.com/platform/system/core/+/refs/heads/master/base/include/android-base/logging.h Most +// notable difference : I have LOGD(TAG) they have LOG(SEVERITY) +class AndroidLogger +{ + public: // Chrome university https://www.youtube.com/watch?v=UNJrgsQXvCA // 'New style C++ ' https://google.github.io/styleguide/cppguide.html - AndroidLogger(const android_LogPriority priority, const std::string TAG) : M_PRIORITY(priority), - M_TAG(std::move( - TAG)) {} - - ~AndroidLogger() { - logBigMessage(stream.str()); + AndroidLogger(const android_LogPriority priority, const std::string TAG) + : M_PRIORITY(priority), M_TAG(std::move(TAG)) + { } - AndroidLogger(const AndroidLogger &other) = delete; + ~AndroidLogger() { logBigMessage(stream.str()); } -private: - std::stringstream stream; - const std::string M_TAG; + AndroidLogger(const AndroidLogger& other) = delete; + + private: + std::stringstream stream; + const std::string M_TAG; const android_LogPriority M_PRIORITY; // taken from https://android.googlesource.com/platform/system/core/+/android-2.1_r1/liblog/logd_write.c static constexpr const auto ANDROID_LOG_BUFF_SIZE = 1024; - //Splits debug messages that exceed the android log maximum length into smaller log(s) - //Recursive declaration - void logBigMessage(const std::string &message) { - if (message.length() > ANDROID_LOG_BUFF_SIZE) { - __android_log_print(M_PRIORITY, M_TAG.c_str(), "%s", - message.substr(0, ANDROID_LOG_BUFF_SIZE).c_str()); + // Splits debug messages that exceed the android log maximum length into smaller log(s) + // Recursive declaration + void logBigMessage(const std::string& message) + { + if (message.length() > ANDROID_LOG_BUFF_SIZE) + { + __android_log_print(M_PRIORITY, M_TAG.c_str(), "%s", message.substr(0, ANDROID_LOG_BUFF_SIZE).c_str()); logBigMessage(message.substr(ANDROID_LOG_BUFF_SIZE)); - } else { + } + else + { __android_log_print(M_PRIORITY, M_TAG.c_str(), "%s", message.c_str()); } } // the non-member function operator<< will now have access to private members - template - friend AndroidLogger &operator<<(AndroidLogger &record, T &&t); + template + friend AndroidLogger& operator<<(AndroidLogger& record, T&& t); }; -template -AndroidLogger &operator<<(AndroidLogger &record, T &&t) { +template +AndroidLogger& operator<<(AndroidLogger& record, T&& t) +{ record.stream << std::forward(t); return record; } -template -AndroidLogger &operator<<(AndroidLogger &&record, T &&t) { +template +AndroidLogger& operator<<(AndroidLogger&& record, T&& t) +{ return record << std::forward(t); } - // taken from https://stackoverflow.com/questions/1666802/is-there-a-class-macro-in-c // also see https://gcc.gnu.org/onlinedocs/gcc/Function-Names.html -namespace PrettyFunctionHelper { - static constexpr const auto UNKNOWN_CLASS_NAME = "UnknownClassName"; - - /** - * @param prettyFunction as obtained by the macro __PRETTY_FUNCTION__ - * @param function as obtained by the macro __FUNCTION__ - * @return a string containing the class name at the end, optionally prefixed by the namespace(s). - * Example return values: "MyNamespace1::MyNamespace2::MyClassName","MyNamespace1::MyClassName" "MyClassName" - */ - static std::string - namespaceAndClassName(const std::string &function, const std::string &prettyFunction) { - //AndroidLogger(ANDROID_LOG_DEBUG,"NoT")< ", "static std::string " - // See how the 3rd example return type also contains a " ". - // However, it is guaranteed that the area NamespaceAndClassName does not contain an empty space - const size_t begin1 = returnTypeAndNamespaceAndClassName.rfind(" "); - if (begin1 == std::string::npos)return UNKNOWN_CLASS_NAME; - const std::string namespaceAndClassName = returnTypeAndNamespaceAndClassName.substr( - begin1 + 1); - return namespaceAndClassName; +namespace PrettyFunctionHelper +{ +static constexpr const auto UNKNOWN_CLASS_NAME = "UnknownClassName"; + +/** + * @param prettyFunction as obtained by the macro __PRETTY_FUNCTION__ + * @param function as obtained by the macro __FUNCTION__ + * @return a string containing the class name at the end, optionally prefixed by the namespace(s). + * Example return values: "MyNamespace1::MyNamespace2::MyClassName","MyNamespace1::MyClassName" "MyClassName" + */ +static std::string namespaceAndClassName(const std::string& function, const std::string& prettyFunction) +{ + // AndroidLogger(ANDROID_LOG_DEBUG,"NoT")< ", "static std::string " + // See how the 3rd example return type also contains a " ". + // However, it is guaranteed that the area NamespaceAndClassName does not contain an empty space + const size_t begin1 = returnTypeAndNamespaceAndClassName.rfind(" "); + if (begin1 == std::string::npos) return UNKNOWN_CLASS_NAME; + const std::string namespaceAndClassName = returnTypeAndNamespaceAndClassName.substr(begin1 + 1); + return namespaceAndClassName; +} + +/** + * @param namespaceAndClassName value obtained by namespaceAndClassName() + * @return the class name only (without namespace prefix if existing) + */ +static std::string className(const std::string& namespaceAndClassName) +{ + const size_t end = namespaceAndClassName.rfind("::"); + if (end != std::string::npos) + { + return namespaceAndClassName.substr(end + 2); } + return namespaceAndClassName; +} - /** - * @param namespaceAndClassName value obtained by namespaceAndClassName() - * @return the class name only (without namespace prefix if existing) - */ - static std::string className(const std::string &namespaceAndClassName) { - const size_t end = namespaceAndClassName.rfind("::"); - if (end != std::string::npos) { - return namespaceAndClassName.substr(end + 2); - } - return namespaceAndClassName; +class Test +{ + public: + static std::string testMacro(std::string unused) + { + const auto namespaceAndClassName = + PrettyFunctionHelper::namespaceAndClassName(__FUNCTION__, __PRETTY_FUNCTION__); + // AndroidLogger(ANDROID_LOG_DEBUG,"NoT2")< +#include "../NALU/KeyFrameFinder.hpp" // Some of these params are only supported on the latest Android versions // However,writing them has no negative affect on devices with older Android versions // Note that for example the low-latency key cannot fix any issues like the 'VUI' issue -void writeAndroidPerformanceParams(AMediaFormat *format) { +void writeAndroidPerformanceParams(AMediaFormat* format) +{ // I think: KEY_LOW_LATENCY is for decoder. But it doesn't really make a difference anyways static const auto PARAMETER_KEY_LOW_LATENCY = "low-latency"; AMediaFormat_setInt32(format, PARAMETER_KEY_LOW_LATENCY, 1); @@ -17,37 +18,39 @@ void writeAndroidPerformanceParams(AMediaFormat *format) { static const auto AMEDIAFORMAT_KEY_PRIORITY = "priority"; AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_PRIORITY, 0); // set operating rate ? - doesn't make a difference - //static const auto AMEDIAFORMAT_KEY_OPERATING_RATE="operating-rate"; - //AMediaFormat_setInt32(format,AMEDIAFORMAT_KEY_OPERATING_RATE,60); + // static const auto AMEDIAFORMAT_KEY_OPERATING_RATE="operating-rate"; + // AMediaFormat_setInt32(format,AMEDIAFORMAT_KEY_OPERATING_RATE,60); // - //AMEDIAFORMAT_KEY_LOW_LATENCY; - //AMEDIAFORMAT_KEY_LATENCY; - //AMediaFormat_setInt32(format,AMEDIAFORMAT_KEY_LATENCY,0); - //AMediaFormat_setInt32(format,AMEDIAFORMAT_KEY_OPERATING_RATE,0); + // AMEDIAFORMAT_KEY_LOW_LATENCY; + // AMEDIAFORMAT_KEY_LATENCY; + // AMediaFormat_setInt32(format,AMEDIAFORMAT_KEY_LATENCY,0); + // AMediaFormat_setInt32(format,AMEDIAFORMAT_KEY_OPERATING_RATE,0); } -static void h264_configureAMediaFormat(KeyFrameFinder &kff, AMediaFormat *format) { - const auto sps = kff.getCSD0(); - const auto pps = kff.getCSD1(); +static void h264_configureAMediaFormat(KeyFrameFinder& kff, AMediaFormat* format) +{ + const auto sps = kff.getCSD0(); + const auto pps = kff.getCSD1(); const auto videoWH = sps.getVideoWidthHeightSPS(); AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_WIDTH, videoWH[0]); AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_HEIGHT, videoWH[1]); AMediaFormat_setBuffer(format, "csd-0", sps.getData(), (size_t) sps.getSize()); AMediaFormat_setBuffer(format, "csd-1", pps.getData(), (size_t) pps.getSize()); MLOGD << "Video WH:" << videoWH[0] << " H:" << videoWH[1]; - //AMediaFormat_setInt32(format,AMEDIAFORMAT_KEY_BIT_RATE,5*1024*1024); - //AMediaFormat_setInt32(format,AMEDIAFORMAT_KEY_FRAME_RATE,60); - //AVCProfileBaseline==1 - //AMediaFormat_setInt32(decoder.format,AMEDIAFORMAT_KEY_PROFILE,1); - //AMediaFormat_setInt32(decoder.format,AMEDIAFORMAT_KEY_PRIORITY,0); - //writeAndroidPerformanceParams(format); + // AMediaFormat_setInt32(format,AMEDIAFORMAT_KEY_BIT_RATE,5*1024*1024); + // AMediaFormat_setInt32(format,AMEDIAFORMAT_KEY_FRAME_RATE,60); + // AVCProfileBaseline==1 + // AMediaFormat_setInt32(decoder.format,AMEDIAFORMAT_KEY_PROFILE,1); + // AMediaFormat_setInt32(decoder.format,AMEDIAFORMAT_KEY_PRIORITY,0); + // writeAndroidPerformanceParams(format); } -static void h265_configureAMediaFormat(KeyFrameFinder &kff, AMediaFormat *format) { +static void h265_configureAMediaFormat(KeyFrameFinder& kff, AMediaFormat* format) +{ std::vector buff = {}; - const auto sps = kff.getCSD0(); - const auto pps = kff.getCSD1(); - const auto vps = kff.getVPS(); + const auto sps = kff.getCSD0(); + const auto pps = kff.getCSD1(); + const auto vps = kff.getVPS(); buff.reserve(sps.getSize() + pps.getSize() + vps.getSize()); KeyFrameFinder::appendNaluData(buff, vps); KeyFrameFinder::appendNaluData(buff, sps); @@ -57,7 +60,7 @@ static void h265_configureAMediaFormat(KeyFrameFinder &kff, AMediaFormat *format AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_HEIGHT, videoWH[1]); AMediaFormat_setBuffer(format, "csd-0", buff.data(), buff.size()); MLOGD << "Video WH:" << videoWH[0] << " H:" << videoWH[1]; - //writeAndroidPerformanceParams(format); + // writeAndroidPerformanceParams(format); } -#endif //FPVUE_ANDROIDMEDIAFORMATHELPER_H +#endif // FPVUE_ANDROIDMEDIAFORMATHELPER_H diff --git a/app/videonative/src/main/cpp/helper/NDKArrayHelper.hpp b/app/videonative/src/main/cpp/helper/NDKArrayHelper.hpp index 8d5be09..9425c25 100644 --- a/app/videonative/src/main/cpp/helper/NDKArrayHelper.hpp +++ b/app/videonative/src/main/cpp/helper/NDKArrayHelper.hpp @@ -6,83 +6,97 @@ #define RENDERINGX_NDKARRAYHELPER_H #include -#include -#include -#include #include +#include +#include +#include // // The purpose of this namespace is to make it easier to transfer arrays of generic data types // (Data types that are both used by cpp and java, like int, float ) via the NDK from java to cpp // Only dependencies are standard libraries and the android java NDK // -namespace NDKArrayHelper { - // workaround from https://en.cppreference.com/w/cpp/language/if#Constexpr_If - // To have compile time type safety - template - struct always_false : std::false_type { - }; +namespace NDKArrayHelper +{ +// workaround from https://en.cppreference.com/w/cpp/language/if#Constexpr_If +// To have compile time type safety +template +struct always_false : std::false_type +{}; - /** - * Returns a std::vector whose size depends on the size of the java array - * and which owns the underlying memory. Most generic and memory safe. - * Function has compile time type safety, see example() below - * @param TCpp basic cpp type like int, float. - * @param TJava java array like jintArray, jfloatArray - has to match the cpp type. For example, - * T==int => T2==jintArray - * If TCpp==int and TJava==jintArray this method returns std::vector - */ - template - static std::vector DynamicSizeArray(JNIEnv *env, TJava jArray) { - const size_t size = (size_t) env->GetArrayLength(jArray); - std::vector ret(size); - if constexpr (std::is_same_v && std::is_same_v) { - env->GetBooleanArrayRegion(jArray, 0, size, ret.data()); - } else if constexpr (std::is_same_v && std::is_same_v) { - env->GetFloatArrayRegion(jArray, 0, size, ret.data()); - } else if constexpr (std::is_same_v && std::is_same_v) { - env->GetIntArrayRegion(jArray, 0, size, ret.data()); - } else if constexpr (std::is_same_v && std::is_same_v) { - env->GetDoubleArrayRegion(jArray, 0, size, ret.data()); - } else { - // a) Make sure you use the right combination. - // For example, if you want a std::vector pass a jfloatArray as second parameter - // b) Make sure you use a supported type. (e.g. one that appears in the above if - else) - static_assert(always_false::value, "Unsupported Combination / Type"); - } - return ret; +/** + * Returns a std::vector whose size depends on the size of the java array + * and which owns the underlying memory. Most generic and memory safe. + * Function has compile time type safety, see example() below + * @param TCpp basic cpp type like int, float. + * @param TJava java array like jintArray, jfloatArray - has to match the cpp type. For example, + * T==int => T2==jintArray + * If TCpp==int and TJava==jintArray this method returns std::vector + */ +template +static std::vector DynamicSizeArray(JNIEnv* env, TJava jArray) +{ + const size_t size = (size_t) env->GetArrayLength(jArray); + std::vector ret(size); + if constexpr (std::is_same_v && std::is_same_v) + { + env->GetBooleanArrayRegion(jArray, 0, size, ret.data()); } - - /** - * Whenever size is already known at compile time u can use this one - * but note that it is impossible to check at compile time if the java array has the same size - * Assert at run time if size!=array size - */ - template - static std::array FixedSizeArray(JNIEnv *env, T2 array) { - const auto data = DynamicSizeArray(env, array); - std::array ret; - assert(data.size() == S); - std::memcpy(ret.data(), data.data(), data.size() * sizeof(T)); - return ret; + else if constexpr (std::is_same_v && std::is_same_v) + { + env->GetFloatArrayRegion(jArray, 0, size, ret.data()); } - - static std::string DynamicSizeString(JNIEnv *env, jstring jstring1) { - const char *valueP = env->GetStringUTFChars(jstring1, nullptr); - const std::string ret = std::string(valueP); - env->ReleaseStringUTFChars(jstring1, valueP); - return ret; + else if constexpr (std::is_same_v && std::is_same_v) + { + env->GetIntArrayRegion(jArray, 0, size, ret.data()); } - - // Demonstrate the type safety of DynamicSizeArray: - // X Compiles, but Y does not (which is exactly what we want) - static void example() { - JNIEnv *env = nullptr; - jfloatArray jFloatArray = nullptr; - std::vector X = DynamicSizeArray(env, jFloatArray); - // This one does not compile - we cannot get a int array from a java float array - //std::vector Y=DynamicSizeArray(env,jFloatArray); + else if constexpr (std::is_same_v && std::is_same_v) + { + env->GetDoubleArrayRegion(jArray, 0, size, ret.data()); + } + else + { + // a) Make sure you use the right combination. + // For example, if you want a std::vector pass a jfloatArray as second parameter + // b) Make sure you use a supported type. (e.g. one that appears in the above if - else) + static_assert(always_false::value, "Unsupported Combination / Type"); } + return ret; +} + +/** + * Whenever size is already known at compile time u can use this one + * but note that it is impossible to check at compile time if the java array has the same size + * Assert at run time if size!=array size + */ +template +static std::array FixedSizeArray(JNIEnv* env, T2 array) +{ + const auto data = DynamicSizeArray(env, array); + std::array ret; + assert(data.size() == S); + std::memcpy(ret.data(), data.data(), data.size() * sizeof(T)); + return ret; +} + +static std::string DynamicSizeString(JNIEnv* env, jstring jstring1) +{ + const char* valueP = env->GetStringUTFChars(jstring1, nullptr); + const std::string ret = std::string(valueP); + env->ReleaseStringUTFChars(jstring1, valueP); + return ret; +} + +// Demonstrate the type safety of DynamicSizeArray: +// X Compiles, but Y does not (which is exactly what we want) +static void example() +{ + JNIEnv* env = nullptr; + jfloatArray jFloatArray = nullptr; + std::vector X = DynamicSizeArray(env, jFloatArray); + // This one does not compile - we cannot get a int array from a java float array + // std::vector Y=DynamicSizeArray(env,jFloatArray); } +} // namespace NDKArrayHelper -#endif //RENDERINGX_NDKARRAYHELPER_H +#endif // RENDERINGX_NDKARRAYHELPER_H diff --git a/app/videonative/src/main/cpp/helper/NDKHelper.hpp b/app/videonative/src/main/cpp/helper/NDKHelper.hpp index a096a67..bf49296 100644 --- a/app/videonative/src/main/cpp/helper/NDKHelper.hpp +++ b/app/videonative/src/main/cpp/helper/NDKHelper.hpp @@ -5,134 +5,131 @@ #ifndef RENDERINGX_NDKHELPER_H #define RENDERINGX_NDKHELPER_H -#include "NDKArrayHelper.hpp" +#include #include -#include -#include #include -#include +#include #include +#include +#include "NDKArrayHelper.hpp" // The purpose of this namespace is to provide utility functions // That help using the android NDK -namespace NDKHelper { - //Obtain the asset manager instance from the provided android context object - static jobject getAssetManagerFromContext(JNIEnv *env, jobject android_context) { - jclass context_class = - env->FindClass("android/content/Context"); - jmethodID get_asset_manager_method = env->GetMethodID( - context_class, "getAssets", "()Landroid/content/res/AssetManager;"); - jobject java_asset_mgr = env->CallObjectMethod(android_context, get_asset_manager_method); - assert(java_asset_mgr != nullptr); - return java_asset_mgr; - } +namespace NDKHelper +{ +// Obtain the asset manager instance from the provided android context object +static jobject getAssetManagerFromContext(JNIEnv* env, jobject android_context) +{ + jclass context_class = env->FindClass("android/content/Context"); + jmethodID get_asset_manager_method = + env->GetMethodID(context_class, "getAssets", "()Landroid/content/res/AssetManager;"); + jobject java_asset_mgr = env->CallObjectMethod(android_context, get_asset_manager_method); + assert(java_asset_mgr != nullptr); + return java_asset_mgr; +} - // Return the AAssetManager object instead of the general 'jobject' - static AAssetManager *getAssetManagerFromContext2(JNIEnv *env, jobject androidContext) { - jobject jobject1 = getAssetManagerFromContext(env, androidContext); - return AAssetManager_fromJava(env, jobject1); - } +// Return the AAssetManager object instead of the general 'jobject' +static AAssetManager* getAssetManagerFromContext2(JNIEnv* env, jobject androidContext) +{ + jobject jobject1 = getAssetManagerFromContext(env, androidContext); + return AAssetManager_fromJava(env, jobject1); +} - // Returns a java 'InputStream' instance by opening the Asset specified at path - // If the specified file does not exist, java throws an exception. - // In this case,the exception is cleared and nullptr is returned - // @param java_asset_mgr valid java asset manager - // @param path path to the asset - static jobject - createInputStreamFromAsset(JNIEnv *env, jobject java_asset_mgr, const std::string &path) { - jclass asset_manager_class = - env->FindClass("android/content/res/AssetManager"); - jmethodID open_method = env->GetMethodID( - asset_manager_class, "open", "(Ljava/lang/String;)Ljava/io/InputStream;"); - jstring j_path = env->NewStringUTF(path.c_str()); - jobject input_stream = env->CallObjectMethod(java_asset_mgr, open_method, j_path); - if (env->ExceptionOccurred() != nullptr) { - //MLOGE<<"Java exception in createInputStreamFromAsset for file:"<ExceptionClear(); - env->DeleteLocalRef(j_path); - return nullptr; - } +// Returns a java 'InputStream' instance by opening the Asset specified at path +// If the specified file does not exist, java throws an exception. +// In this case,the exception is cleared and nullptr is returned +// @param java_asset_mgr valid java asset manager +// @param path path to the asset +static jobject createInputStreamFromAsset(JNIEnv* env, jobject java_asset_mgr, const std::string& path) +{ + jclass asset_manager_class = env->FindClass("android/content/res/AssetManager"); + jmethodID open_method = env->GetMethodID(asset_manager_class, "open", "(Ljava/lang/String;)Ljava/io/InputStream;"); + jstring j_path = env->NewStringUTF(path.c_str()); + jobject input_stream = env->CallObjectMethod(java_asset_mgr, open_method, j_path); + if (env->ExceptionOccurred() != nullptr) + { + // MLOGE<<"Java exception in createInputStreamFromAsset for file:"<ExceptionClear(); env->DeleteLocalRef(j_path); - return input_stream; + return nullptr; } + env->DeleteLocalRef(j_path); + return input_stream; +} - // Inspired by gvr_util/util.cc - // Loads a png file from assets folder and then assigns it to the OpenGL target. - // This method must be called from the renderer thread since it will result in - // OpenGL calls to assign the image to the texture target. - // - // @param env The JNIEnv to use. - // @param target, OpenGL texture target to load the image into. - // @param path, path to the file, relative to the assets folder. - // @param extractAlpha, upload texture as alpha only (needed for text) - // @return true if png is loaded correctly, otherwise false. - static bool LoadPngFromAssetManager(JNIEnv *env, jobject java_asset_mgr, int target, - const std::string &path, const bool extractAlpha = false) { - jobject image_stream = createInputStreamFromAsset(env, java_asset_mgr, path); - if (image_stream == nullptr) { - return false; - } - jclass bitmap_class = - env->FindClass("android/graphics/Bitmap"); - jclass bitmap_factory_class = - env->FindClass("android/graphics/BitmapFactory"); - jclass gl_utils_class = env->FindClass("android/opengl/GLUtils"); - jmethodID extract_alpha_method = env->GetMethodID( - bitmap_class, "extractAlpha", "()Landroid/graphics/Bitmap;"); - jmethodID decode_stream_method = env->GetStaticMethodID( - bitmap_factory_class, "decodeStream", - "(Ljava/io/InputStream;)Landroid/graphics/Bitmap;"); - jmethodID tex_image_2d_method = env->GetStaticMethodID( - gl_utils_class, "texImage2D", "(IILandroid/graphics/Bitmap;I)V"); - jobject image_obj = env->CallStaticObjectMethod( - bitmap_factory_class, decode_stream_method, image_stream); - if (extractAlpha) { - image_obj = env->CallObjectMethod(image_obj, extract_alpha_method); - } - if (env->ExceptionOccurred() != nullptr) { - //MLOGE<<"Java exception while loading image"; - env->ExceptionClear(); - image_obj = nullptr; - return false; - } - env->CallStaticVoidMethod(gl_utils_class, tex_image_2d_method, target, 0, - image_obj, 0); - return true; +// Inspired by gvr_util/util.cc +// Loads a png file from assets folder and then assigns it to the OpenGL target. +// This method must be called from the renderer thread since it will result in +// OpenGL calls to assign the image to the texture target. +// +// @param env The JNIEnv to use. +// @param target, OpenGL texture target to load the image into. +// @param path, path to the file, relative to the assets folder. +// @param extractAlpha, upload texture as alpha only (needed for text) +// @return true if png is loaded correctly, otherwise false. +static bool LoadPngFromAssetManager( + JNIEnv* env, jobject java_asset_mgr, int target, const std::string& path, const bool extractAlpha = false) +{ + jobject image_stream = createInputStreamFromAsset(env, java_asset_mgr, path); + if (image_stream == nullptr) + { + return false; } - - // Only pass java context instead of asset manager - static bool LoadPngFromAssetManager2(JNIEnv *env, jobject android_context, int target, - const std::string &path, const bool extractAlpha = false) { - jobject asset_mgr = getAssetManagerFromContext(env, android_context); - return LoadPngFromAssetManager(env, asset_mgr, target, path, extractAlpha); + jclass bitmap_class = env->FindClass("android/graphics/Bitmap"); + jclass bitmap_factory_class = env->FindClass("android/graphics/BitmapFactory"); + jclass gl_utils_class = env->FindClass("android/opengl/GLUtils"); + jmethodID extract_alpha_method = env->GetMethodID(bitmap_class, "extractAlpha", "()Landroid/graphics/Bitmap;"); + jmethodID decode_stream_method = env->GetStaticMethodID( + bitmap_factory_class, "decodeStream", "(Ljava/io/InputStream;)Landroid/graphics/Bitmap;"); + jmethodID tex_image_2d_method = + env->GetStaticMethodID(gl_utils_class, "texImage2D", "(IILandroid/graphics/Bitmap;I)V"); + jobject image_obj = env->CallStaticObjectMethod(bitmap_factory_class, decode_stream_method, image_stream); + if (extractAlpha) + { + image_obj = env->CallObjectMethod(image_obj, extract_alpha_method); } - - //Load a java float array that has been serialized from the specified asset location - //and return its data in cpp-style std::array - template - static std::array - getFloatArrayFromAssets2(JNIEnv *env, jobject android_context, const std::string &path) { - jobject asset_mgr = getAssetManagerFromContext(env, android_context); - jobject input_stream = createInputStreamFromAsset(env, asset_mgr, path); - if (input_stream == nullptr) { - return std::array(); - } - jclass object_input_stream_class = - env->FindClass("java/io/ObjectInputStream"); - jmethodID object_input_stream_constructor = env->GetMethodID(object_input_stream_class, - "", - "(Ljava/io/InputStream;)V"); - jmethodID read_object_method = env->GetMethodID(object_input_stream_class, "readObject", - "()Ljava/lang/Object;"); - //Create the Object input stream - jobject object_input_stream = env->NewObject(object_input_stream_class, - object_input_stream_constructor, input_stream); - //read its underlying instance (float array in our case) then cast - jobject object_float_array = env->CallObjectMethod(object_input_stream, read_object_method); - jfloatArray array = (jfloatArray) object_float_array; - return NDKArrayHelper::FixedSizeArray(env, array); + if (env->ExceptionOccurred() != nullptr) + { + // MLOGE<<"Java exception while loading image"; + env->ExceptionClear(); + image_obj = nullptr; + return false; } -}; + env->CallStaticVoidMethod(gl_utils_class, tex_image_2d_method, target, 0, image_obj, 0); + return true; +} + +// Only pass java context instead of asset manager +static bool LoadPngFromAssetManager2( + JNIEnv* env, jobject android_context, int target, const std::string& path, const bool extractAlpha = false) +{ + jobject asset_mgr = getAssetManagerFromContext(env, android_context); + return LoadPngFromAssetManager(env, asset_mgr, target, path, extractAlpha); +} +// Load a java float array that has been serialized from the specified asset location +// and return its data in cpp-style std::array +template +static std::array getFloatArrayFromAssets2(JNIEnv* env, jobject android_context, const std::string& path) +{ + jobject asset_mgr = getAssetManagerFromContext(env, android_context); + jobject input_stream = createInputStreamFromAsset(env, asset_mgr, path); + if (input_stream == nullptr) + { + return std::array(); + } + jclass object_input_stream_class = env->FindClass("java/io/ObjectInputStream"); + jmethodID object_input_stream_constructor = + env->GetMethodID(object_input_stream_class, "", "(Ljava/io/InputStream;)V"); + jmethodID read_object_method = env->GetMethodID(object_input_stream_class, "readObject", "()Ljava/lang/Object;"); + // Create the Object input stream + jobject object_input_stream = + env->NewObject(object_input_stream_class, object_input_stream_constructor, input_stream); + // read its underlying instance (float array in our case) then cast + jobject object_float_array = env->CallObjectMethod(object_input_stream, read_object_method); + jfloatArray array = (jfloatArray) object_float_array; + return NDKArrayHelper::FixedSizeArray(env, array); +} +}; // namespace NDKHelper -#endif //RENDERINGX_NDKHELPER_H +#endif // RENDERINGX_NDKHELPER_H diff --git a/app/videonative/src/main/cpp/helper/NDKThreadHelper.hpp b/app/videonative/src/main/cpp/helper/NDKThreadHelper.hpp index 3d8da7b..a37f566 100644 --- a/app/videonative/src/main/cpp/helper/NDKThreadHelper.hpp +++ b/app/videonative/src/main/cpp/helper/NDKThreadHelper.hpp @@ -14,173 +14,179 @@ // // Direct mapping from java Thread method names to c++ // -class JThread { -private: - JNIEnv *env; - jclass jcThread; +class JThread +{ + private: + JNIEnv* env; + jclass jcThread; jmethodID jmCurrentThread; jmethodID jmIsInterrupted; jmethodID jmGetPriority; jmethodID jmSetPriority; - jobject joCurrentThread; -public: - JThread(JNIEnv *env) { - this->env = env; - jcThread = env->FindClass("java/lang/Thread"); + jobject joCurrentThread; + + public: + JThread(JNIEnv* env) + { + this->env = env; + jcThread = env->FindClass("java/lang/Thread"); jmCurrentThread = env->GetStaticMethodID(jcThread, "currentThread", "()Ljava/lang/Thread;"); jmIsInterrupted = env->GetMethodID(jcThread, "isInterrupted", "()Z"); - jmGetPriority = env->GetMethodID(jcThread, "getPriority", "()I"); - jmSetPriority = env->GetMethodID(jcThread, "setPriority", "(I)V"); + jmGetPriority = env->GetMethodID(jcThread, "getPriority", "()I"); + jmSetPriority = env->GetMethodID(jcThread, "setPriority", "(I)V"); joCurrentThread = env->CallStaticObjectMethod(jcThread, jmCurrentThread); } - bool isInterrupted() { - return (bool) env->CallBooleanMethod(joCurrentThread, jmIsInterrupted); - } + bool isInterrupted() { return (bool) env->CallBooleanMethod(joCurrentThread, jmIsInterrupted); } - int getPriority() { - return (int) env->CallIntMethod(joCurrentThread, jmGetPriority); - } + int getPriority() { return (int) env->CallIntMethod(joCurrentThread, jmGetPriority); } - void setPriority(int wantedPriority) { - env->CallVoidMethod(joCurrentThread, jmSetPriority, (jint) wantedPriority); - } + void setPriority(int wantedPriority) { env->CallVoidMethod(joCurrentThread, jmSetPriority, (jint) wantedPriority); } - static bool isInterrupted(JNIEnv *env) { - return JThread(env).isInterrupted(); - } + static bool isInterrupted(JNIEnv* env) { return JThread(env).isInterrupted(); } - static int getPriority(JNIEnv *env) { - return JThread(env).getPriority(); - } + static int getPriority(JNIEnv* env) { return JThread(env).getPriority(); } - static void setPriority(JNIEnv *env, int wantedPriority) { - JThread(env).setPriority(wantedPriority); - } + static void setPriority(JNIEnv* env, int wantedPriority) { JThread(env).setPriority(wantedPriority); } - static void printThreadPriority(JNIEnv *env) { - //MLOGD << "printThreadPriority " << getPriority(env); + static void printThreadPriority(JNIEnv* env) + { + // MLOGD << "printThreadPriority " << getPriority(env); } }; // Java android.os.Process utility methods (not java/lang/Process !) // I think process and thread is used in the same context here but you probably want to use // Process.setPriority instead -namespace JProcess { - // calls android.os.Process.setThreadPriority() - static void setThreadPriority(JNIEnv *env, int wantedPriority) { - jclass jcProcess = env->FindClass("android/os/Process"); - jmethodID jmSetThreadPriority = env->GetStaticMethodID(jcProcess, "setThreadPriority", - "(I)V"); - env->CallStaticVoidMethod(jcProcess, jmSetThreadPriority, (jint) wantedPriority); - } +namespace JProcess +{ +// calls android.os.Process.setThreadPriority() +static void setThreadPriority(JNIEnv* env, int wantedPriority) +{ + jclass jcProcess = env->FindClass("android/os/Process"); + jmethodID jmSetThreadPriority = env->GetStaticMethodID(jcProcess, "setThreadPriority", "(I)V"); + env->CallStaticVoidMethod(jcProcess, jmSetThreadPriority, (jint) wantedPriority); +} - // calls android.os.Process.getThreadPriority(android.os.Process.myTid()) - static int getThreadPriority(JNIEnv *env) { - jclass jcProcess = env->FindClass("android/os/Process"); - jmethodID jmGetThreadPriority = env->GetStaticMethodID(jcProcess, "getThreadPriority", - "(I)I"); - jmethodID jmMyTid = env->GetStaticMethodID(jcProcess, "myTid", "()I"); - jint myTid = env->CallStaticIntMethod(jcProcess, jmMyTid); - return (int) env->CallStaticIntMethod(jcProcess, jmGetThreadPriority, (jint) myTid); - } +// calls android.os.Process.getThreadPriority(android.os.Process.myTid()) +static int getThreadPriority(JNIEnv* env) +{ + jclass jcProcess = env->FindClass("android/os/Process"); + jmethodID jmGetThreadPriority = env->GetStaticMethodID(jcProcess, "getThreadPriority", "(I)I"); + jmethodID jmMyTid = env->GetStaticMethodID(jcProcess, "myTid", "()I"); + jint myTid = env->CallStaticIntMethod(jcProcess, jmMyTid); + return (int) env->CallStaticIntMethod(jcProcess, jmGetThreadPriority, (jint) myTid); +} - static void printThreadPriority(JNIEnv *env) { - //MLOGD<<"printThreadPriority "<AttachCurrentThread(&myNewEnv, &args); - return myNewEnv; - } +namespace NDKThreadHelper +{ +static constexpr auto MY_DEFAULT_TAG = "NDKThreadHelper"; + +static JNIEnv* attachThread(JavaVM* jvm) +{ + JNIEnv* myNewEnv; + JavaVMAttachArgs args; + args.version = JNI_VERSION_1_6; // choose your JNI version + args.name = "Thread name"; // you might want to give the java thread a name + args.group = NULL; // you might want to assign the java thread to a ThreadGroup + jvm->AttachCurrentThread(&myNewEnv, &args); + return myNewEnv; +} - static void detachThread(JavaVM *jvm) { - jvm->DetachCurrentThread(); - } +static void detachThread(JavaVM* jvm) +{ + jvm->DetachCurrentThread(); +} - // Thread needs to be bound to Java VM - static void - setProcessThreadPriority(JNIEnv *env, int wantedPriority, const char *TAG = MY_DEFAULT_TAG) { - const int currentPriority = JProcess::getThreadPriority(env); - JProcess::setThreadPriority(env, wantedPriority); - const int newPriority = JProcess::getThreadPriority(env); -// if(newPriority==wantedPriority){ -// MLOGD2(TAG)<<"Successfully set priority from "<GetEnv((void **) &env, JNI_VERSION_1_6); - bool detachWhenDone = false; - if (env == nullptr) { - //MLOGD2(TAG)<<"Attaching thread"; - env = attachThread(vm); - detachWhenDone = true; - } - setProcessThreadPriority(env, wantedPriority, TAG); - if (detachWhenDone) { - detachThread(vm); - } +// If the current thread is already bound to the Java VM only call JProcess::setThreadPriority +// If no Java VM is attached (e.g. the thread was created via ndk std::thread or equivalent) +// attach the java vm, set priority and then DETACH again +static void setProcessThreadPriorityAttachDetach(JavaVM* vm, int wantedPriority, const char* TAG = MY_DEFAULT_TAG) +{ + JNIEnv* env = nullptr; + vm->GetEnv((void**) &env, JNI_VERSION_1_6); + bool detachWhenDone = false; + if (env == nullptr) + { + // MLOGD2(TAG)<<"Attaching thread"; + env = attachThread(vm); + detachWhenDone = true; + } + setProcessThreadPriority(env, wantedPriority, TAG); + if (detachWhenDone) + { + detachThread(vm); } +} - // set name of thread. print error on failure - static void setName(pthread_t pthread, const char *name) { - auto res = pthread_setname_np(pthread, name); - if (res != 0) { - //MLOGE<<"Cannot set thread name "< -namespace NDKTHREADHELPERTEST { - // attach java VM, set Thread priority & print it. - // Then detach, reattach JVM and print thread priority - static void doSomething(JavaVM *jvm) { - JNIEnv *env = NDKThreadHelper::attachThread(jvm); - JThread::printThreadPriority(env); - JThread::setPriority(env, 5); - JThread::printThreadPriority(env); - NDKThreadHelper::detachThread(jvm); - env = NDKThreadHelper::attachThread(jvm); - JThread::printThreadPriority(env); - NDKThreadHelper::detachThread(jvm); - } +namespace NDKTHREADHELPERTEST +{ +// attach java VM, set Thread priority & print it. +// Then detach, reattach JVM and print thread priority +static void doSomething(JavaVM* jvm) +{ + JNIEnv* env = NDKThreadHelper::attachThread(jvm); + JThread::printThreadPriority(env); + JThread::setPriority(env, 5); + JThread::printThreadPriority(env); + NDKThreadHelper::detachThread(jvm); + env = NDKThreadHelper::attachThread(jvm); + JThread::printThreadPriority(env); + NDKThreadHelper::detachThread(jvm); +} - // same as doSomething() but uses ProcessThreadPriority - static void doSomething2(JavaVM *jvm, int prio) { - JNIEnv *env = NDKThreadHelper::attachThread(jvm); - JProcess::printThreadPriority(env); - JProcess::setThreadPriority(env, prio); - JProcess::printThreadPriority(env); - NDKThreadHelper::detachThread(jvm); - env = NDKThreadHelper::attachThread(jvm); - JProcess::printThreadPriority(env); - NDKThreadHelper::detachThread(jvm); - } +// same as doSomething() but uses ProcessThreadPriority +static void doSomething2(JavaVM* jvm, int prio) +{ + JNIEnv* env = NDKThreadHelper::attachThread(jvm); + JProcess::printThreadPriority(env); + JProcess::setThreadPriority(env, prio); + JProcess::printThreadPriority(env); + NDKThreadHelper::detachThread(jvm); + env = NDKThreadHelper::attachThread(jvm); + JProcess::printThreadPriority(env); + NDKThreadHelper::detachThread(jvm); +} - static void test(JNIEnv *env) { - JavaVM *jvm; - env->GetJavaVM(&jvm); - std::thread *thread1 = new std::thread(doSomething2, jvm, -20); - } +static void test(JNIEnv* env) +{ + JavaVM* jvm; + env->GetJavaVM(&jvm); + std::thread* thread1 = new std::thread(doSomething2, jvm, -20); } +} // namespace NDKTHREADHELPERTEST -#endif //FPVUE_NDKTHREAD_H +#endif // FPVUE_NDKTHREAD_H diff --git a/app/videonative/src/main/cpp/helper/StringHelper.hpp b/app/videonative/src/main/cpp/helper/StringHelper.hpp index 462bbc4..032cf24 100644 --- a/app/videonative/src/main/cpp/helper/StringHelper.hpp +++ b/app/videonative/src/main/cpp/helper/StringHelper.hpp @@ -5,48 +5,46 @@ #ifndef OSDTESTER_STRINGHELPER_H #define OSDTESTER_STRINGHELPER_H -#include +#include +#include #include +#include #include #include "AndroidLogger.hpp" -#include -#include // Various helper functions to create / modify strings -class StringHelper { -private: +class StringHelper +{ + private: // Return the n of digits without the sign - static const size_t countDigitsWithoutSign(unsigned long n) { - return std::floor(std::log10(n) + 1); - } + static const size_t countDigitsWithoutSign(unsigned long n) { return std::floor(std::log10(n) + 1); } // Return n of digits with sign (e.g. the '-' also counts as a digit) - static const size_t countDigitsWithSign(long n) { - if (n == 0)return 1; - if (n < 0)return countDigitsWithoutSign(std::abs(n)) + 1; + static const size_t countDigitsWithSign(long n) + { + if (n == 0) return 1; + if (n < 0) return countDigitsWithoutSign(std::abs(n)) + 1; return countDigitsWithoutSign(n); } // Return n of digits with sign, slower than the one above // Only use for testing - static const size_t countDigitsWithSignSlow(long n) { - return std::to_string(n).length(); - } + static const size_t countDigitsWithSignSlow(long n) { return std::to_string(n).length(); } -public: + public: // convert std::wstring to std::string - static const std::string normalS(std::wstring &input) { - return std::string(input.begin(), input.end()); - } + static const std::string normalS(std::wstring& input) { return std::string(input.begin(), input.end()); } /** * If the value fits into a string of length @param maxLength return the value as string * Else return 'E' string */ - static const std::wstring intToWString(const int value, const size_t maxLength) { + static const std::wstring intToWString(const int value, const size_t maxLength) + { assert(maxLength >= 1); const auto asString = std::to_wstring(value); - if (asString.length() > maxLength) { + if (asString.length() > maxLength) + { return L"E"; } return asString; @@ -58,57 +56,61 @@ class StringHelper { * @param maxLength Maximum length of the string,including '-' and '.' * @param wantedPrecisionAfterCome The wanted precision after the come, if possible */ - static const std::wstring - doubleToWString(double value, int maxLength, int wantedPrecisionAfterCome) { + static const std::wstring doubleToWString(double value, int maxLength, int wantedPrecisionAfterCome) + { assert(maxLength >= 1); // 'whole number' is the part before the '.' const auto digitsWholeNumberWithSign = countDigitsWithSign((int) value); // Return error when not even the whole number fits into maxLength - if (digitsWholeNumberWithSign > maxLength) { + if (digitsWholeNumberWithSign > maxLength) + { return L"E"; } // Return the whole number when only the whole number fits (and if only the whole number and the '.' // fits, also return the whole number) - if (digitsWholeNumberWithSign >= (maxLength - 1)) { + if (digitsWholeNumberWithSign >= (maxLength - 1)) + { return std::to_wstring((int) value); } - const std::wstring valueAsStringWithDecimals = (std::wstringstream() << std::fixed - << std::setprecision( - wantedPrecisionAfterCome) - << value).str(); + const std::wstring valueAsStringWithDecimals = + (std::wstringstream() << std::fixed << std::setprecision(wantedPrecisionAfterCome) << value).str(); return valueAsStringWithDecimals.substr(0, maxLength); } - static const void - doubleToString(std::wstring &sBeforeCome, std::wstring &sAfterCome, double value, int maxLength, - int maxResAfterCome) { + static const void doubleToString( + std::wstring& sBeforeCome, std::wstring& sAfterCome, double value, int maxLength, int maxResAfterCome) + { const auto valueAsString = doubleToWString(value, maxLength, maxResAfterCome); - const auto idx = valueAsString.find(L"."); - const auto nonFractional = - idx == std::wstring::npos ? valueAsString : valueAsString.substr(0, idx); - const auto fractional = - idx == std::wstring::npos ? L"" : valueAsString.substr(idx, valueAsString.length()); - sBeforeCome = nonFractional; - sAfterCome = fractional; + const auto idx = valueAsString.find(L"."); + const auto nonFractional = idx == std::wstring::npos ? valueAsString : valueAsString.substr(0, idx); + const auto fractional = idx == std::wstring::npos ? L"" : valueAsString.substr(idx, valueAsString.length()); + sBeforeCome = nonFractional; + sAfterCome = fractional; } /** * Convert a std::vector into a nice readable representation. * Example: input std::vector{0,1,2} -> output [0,1,2] - **/ - template - static std::string vectorAsString(const std::vector &v) { + **/ + template + static std::string vectorAsString(const std::vector& v) + { std::stringstream ss; ss << "["; int count = 0; - for (const auto i: v) { - if constexpr (std::is_same_v) { + for (const auto i : v) + { + if constexpr (std::is_same_v) + { ss << (int) i; - } else { + } + else + { ss << i; } count++; - if (count != v.size()) { + if (count != v.size()) + { ss << ","; } } @@ -118,33 +120,37 @@ class StringHelper { /** * If @param sizeBytes exceeds 1 mB / 1 kB use mB / kB as unit - **/ - static std::string memorySizeReadable(const size_t sizeBytes) { + **/ + static std::string memorySizeReadable(const size_t sizeBytes) + { // more than one MB - if (sizeBytes > 1024 * 1024) { + if (sizeBytes > 1024 * 1024) + { float sizeMB = (float) sizeBytes / 1024.0 / 1024.0; return std::to_string(sizeMB) + "mB"; } // more than one KB - if (sizeBytes > 1024) { + if (sizeBytes > 1024) + { float sizeKB = (float) sizeBytes / 1024.0; return std::to_string(sizeKB) + "kB"; } return std::to_string(sizeBytes) + "B"; } -public: -// Some simple testing - static void testCountDigits() { + public: + // Some simple testing + static void testCountDigits() + { std::srand(std::time(nullptr)); MLOGD << "testCountDigits() start"; - std::vector values = { - -100, 0, 1, 9, 10, 100, 100 - }; - for (int i = 0; i < 5000; i++) { + std::vector values = {-100, 0, 1, 9, 10, 100, 100}; + for (int i = 0; i < 5000; i++) + { values.push_back(std::rand()); } - for (int value: values) { + for (int value : values) + { const size_t size1 = countDigitsWithSign(value); const size_t size2 = countDigitsWithSignSlow(value); MLOGD << "Value:" << value << " " << size1 << " " << size2; @@ -153,7 +159,8 @@ class StringHelper { MLOGD << "testCountDigits() end"; } - static void testIntToWString() { + static void testIntToWString() + { auto tmp = intToWString(100, 3); assert(tmp.compare(L"100") == 0); tmp = intToWString(1000, 3); @@ -164,7 +171,8 @@ class StringHelper { assert(tmp.compare(L"E") == 0); } - static void testDoubleToWString() { + static void testDoubleToWString() + { // MLOGD< #include +#include "AndroidLogger.hpp" #include "StringHelper.hpp" -namespace MyTimeHelper { - // R stands for readable. Convert a std::chrono::duration into a readable format - // Readable format is somewhat arbitrary, in this case readable means that for example - // 1second has 'ms' resolution since for values that big ns resolution probably isn't needed - static std::string R(const std::chrono::steady_clock::duration &dur) { - const auto durAbsolute = std::chrono::abs(dur); - if (durAbsolute >= std::chrono::seconds(1)) { - // More than one second, print as decimal with ms resolution. - const auto ms = std::chrono::duration_cast(dur).count(); - return std::to_string(ms / 1000.0f) + "s"; - } - if (durAbsolute >= std::chrono::milliseconds(1)) { - // More than one millisecond, print as decimal with us resolution - const auto us = std::chrono::duration_cast(dur).count(); - return std::to_string(us / 1000.0f) + "ms"; - } - if (durAbsolute >= std::chrono::microseconds(1)) { - // More than one microsecond, print as decimal with ns resolution - const auto ns = std::chrono::duration_cast(dur).count(); - return std::to_string(ns / 1000.0f) + "us"; - } +namespace MyTimeHelper +{ +// R stands for readable. Convert a std::chrono::duration into a readable format +// Readable format is somewhat arbitrary, in this case readable means that for example +// 1second has 'ms' resolution since for values that big ns resolution probably isn't needed +static std::string R(const std::chrono::steady_clock::duration& dur) +{ + const auto durAbsolute = std::chrono::abs(dur); + if (durAbsolute >= std::chrono::seconds(1)) + { + // More than one second, print as decimal with ms resolution. + const auto ms = std::chrono::duration_cast(dur).count(); + return std::to_string(ms / 1000.0f) + "s"; + } + if (durAbsolute >= std::chrono::milliseconds(1)) + { + // More than one millisecond, print as decimal with us resolution + const auto us = std::chrono::duration_cast(dur).count(); + return std::to_string(us / 1000.0f) + "ms"; + } + if (durAbsolute >= std::chrono::microseconds(1)) + { + // More than one microsecond, print as decimal with ns resolution const auto ns = std::chrono::duration_cast(dur).count(); - return std::to_string(ns) + "ns"; + return std::to_string(ns / 1000.0f) + "us"; } + const auto ns = std::chrono::duration_cast(dur).count(); + return std::to_string(ns) + "ns"; +} - static std::string ReadableNS(uint64_t nanoseconds) { - return R(std::chrono::nanoseconds(nanoseconds)); - } +static std::string ReadableNS(uint64_t nanoseconds) +{ + return R(std::chrono::nanoseconds(nanoseconds)); +} - static std::string timeSamplesAsString(const std::vector &samples) { - std::stringstream ss; - int counter = 0; - for (const auto &sample: samples) { - ss << "," << MyTimeHelper::R(sample); - counter++; - if (counter % 10 == 0) { - ss << "\n"; - } +static std::string timeSamplesAsString(const std::vector& samples) +{ + std::stringstream ss; + int counter = 0; + for (const auto& sample : samples) + { + ss << "," << MyTimeHelper::R(sample); + counter++; + if (counter % 10 == 0) + { + ss << "\n"; } - return ss.str(); } -}; + return ss.str(); +} +}; // namespace MyTimeHelper // Use this class to compare many samples of the same kind // Saves the minimum,maximum and average of all the samples // The type of the samples is for example std::chrono::nanoseconds when measuring time intervalls -template -class BaseAvgCalculator { -private: +template +class BaseAvgCalculator +{ + private: // do not forget the braces to initialize with 0 - T sum{}; + T sum{}; long nSamples = 0; - T min = std::numeric_limits::max(); - T max{}; -public: + T min = std::numeric_limits::max(); + T max{}; + + public: BaseAvgCalculator() { reset(); }; - void add(const T &value) { - if (value < T(0)) { + void add(const T& value) + { + if (value < T(0)) + { MLOGE << "Cannot add negative value"; return; } sum += value; nSamples++; - if (value < min) { + if (value < min) + { min = value; } - if (value > max) { + if (value > max) + { max = value; } } // Returns the average of all samples. // If 0 samples were recorded, return 0 - T getAvg() const { - if (nSamples == 0)return T(0); + T getAvg() const + { + if (nSamples == 0) return T(0); return sum / nSamples; } // Returns the minimum value of all samples - T getMin() const { - return min; - } + T getMin() const { return min; } // Returns the maximum value of all samples - T getMax() const { - return max; - } + T getMax() const { return max; } // Returns the n of samples that were processed - long getNSamples() const { - return nSamples; - } + long getNSamples() const { return nSamples; } // Reset everything (as if zero samples were processed) - void reset() { - sum = {}; + void reset() + { + sum = {}; nSamples = 0; // Workaround for std::numeric_limits returning 0 for std::chrono::nanoseconds - if constexpr (std::is_same_v) { + if constexpr (std::is_same_v) + { min = std::chrono::nanoseconds::max(); - } else { + } + else + { min = std::numeric_limits::max(); } max = {}; } // Merges two AvgCalculator(s) that hold the same types of samples together - BaseAvgCalculator operator+(const BaseAvgCalculator &other) { + BaseAvgCalculator operator+(const BaseAvgCalculator& other) + { BaseAvgCalculator ret; ret.add(this->getAvg()); ret.add(other.getAvg()); const auto min1 = std::min(this->getMin(), other.getMin()); const auto max1 = std::max(this->getMax(), other.getMax()); - ret.min = min1; - ret.max = max1; + ret.min = min1; + ret.max = max1; return ret; } // max delta between average and min / max - std::chrono::nanoseconds getMaxDifferenceMinMaxAvg() const { + std::chrono::nanoseconds getMaxDifferenceMinMaxAvg() const + { const auto deltaMin = std::chrono::abs(getAvg() - getMin()); const auto deltaMax = std::chrono::abs(getAvg() - getMax()); - if (deltaMin > deltaMax)return deltaMin; + if (deltaMin > deltaMax) return deltaMin; return deltaMax; } - std::string getAvgReadable(const bool averageOnly = false) const { + std::string getAvgReadable(const bool averageOnly = false) const + { std::stringstream ss; - if constexpr (std::is_same_v) { + if constexpr (std::is_same_v) + { // Class stores time samples - if (averageOnly) { + if (averageOnly) + { ss << "avg=" << MyTimeHelper::R(getAvg()); return ss.str(); } ss << "min=" << MyTimeHelper::R(getMin()) << " max=" << MyTimeHelper::R(getMax()) << " avg=" << MyTimeHelper::R(getAvg()); - } else if constexpr (std::is_same_v) { + } + else if constexpr (std::is_same_v) + { // Class stores memory sizes - if (averageOnly) { + if (averageOnly) + { ss << "avg=" << StringHelper::memorySizeReadable(getAvg()); return ss.str(); } - ss << "min=" << StringHelper::memorySizeReadable(getMin()) << " max=" - << StringHelper::memorySizeReadable(getMax()) << " avg=" - << StringHelper::memorySizeReadable(getAvg()); - } else { + ss << "min=" << StringHelper::memorySizeReadable(getMin()) + << " max=" << StringHelper::memorySizeReadable(getMax()) + << " avg=" << StringHelper::memorySizeReadable(getAvg()); + } + else + { // Class stores other type of samples - if (averageOnly) { + if (averageOnly) + { ss << "avg=" << getAvg(); return ss.str(); } @@ -167,107 +192,111 @@ class BaseAvgCalculator { return ss.str(); } - float getAvg_ms() { - return (float) (std::chrono::duration_cast(getAvg()).count()) / - 1000.0f; + float getAvg_ms() + { + return (float) (std::chrono::duration_cast(getAvg()).count()) / 1000.0f; } }; // Default is using timestamps -using AvgCalculator = BaseAvgCalculator; +using AvgCalculator = BaseAvgCalculator; using AvgCalculatorSize = BaseAvgCalculator; - // Instead of storing only the min, max and average this stores // The last n samples in a queue. However, this makes calculating the min/max/avg values much more expensive // And therefore should only be used with a small sample size. -class AvgCalculator2 { -private: - const size_t sampleSize; +class AvgCalculator2 +{ + private: + const size_t sampleSize; std::deque samples; -public: + + public: // Use zero for infinite n of recorded samples - AvgCalculator2(size_t sampleSize = 60) : sampleSize(sampleSize) {}; + AvgCalculator2(size_t sampleSize = 60) : sampleSize(sampleSize){}; // - void add(const std::chrono::nanoseconds &value) { - if (value < std::chrono::nanoseconds(0)) { + void add(const std::chrono::nanoseconds& value) + { + if (value < std::chrono::nanoseconds(0)) + { MLOGE << "Cannot add negative value"; return; } samples.push_back(value); // Remove the oldest sample if needed - if (sampleSize != 0 && samples.size() > sampleSize) { + if (sampleSize != 0 && samples.size() > sampleSize) + { samples.pop_front(); } } - std::chrono::nanoseconds getAvg() const { - if (samples.empty()) { + std::chrono::nanoseconds getAvg() const + { + if (samples.empty()) + { return std::chrono::nanoseconds(0); } std::chrono::nanoseconds sum{0}; - for (const auto sample: samples) { + for (const auto sample : samples) + { sum += sample; } return sum / samples.size(); } - std::chrono::nanoseconds getMin() const { - return *std::min_element(samples.begin(), samples.end()); - } + std::chrono::nanoseconds getMin() const { return *std::min_element(samples.begin(), samples.end()); } - std::chrono::nanoseconds getMax() const { - return *std::max_element(samples.begin(), samples.end()); - } + std::chrono::nanoseconds getMax() const { return *std::max_element(samples.begin(), samples.end()); } - void reset() { - samples.resize(0); - } + void reset() { samples.resize(0); } - std::string getAvgReadable(const bool averageOnly = false) const { + std::string getAvgReadable(const bool averageOnly = false) const + { std::stringstream ss; - if (averageOnly) { + if (averageOnly) + { ss << "avg=" << MyTimeHelper::R(getAvg()); return ss.str(); } - ss << "min=" << MyTimeHelper::R(getMin()) << " max=" << MyTimeHelper::R(getMax()) << " avg=" - << MyTimeHelper::R(getAvg()) << " N samples=" << samples.size(); + ss << "min=" << MyTimeHelper::R(getMin()) << " max=" << MyTimeHelper::R(getMax()) + << " avg=" << MyTimeHelper::R(getAvg()) << " N samples=" << samples.size(); return ss.str(); } - std::string getAllSamplesAsString() { + std::string getAllSamplesAsString() + { std::stringstream ss; - for (const auto &sample: samples) { + for (const auto& sample : samples) + { ss << " " << MyTimeHelper::R(sample); } return ss.str(); } - size_t getNSamples() const { - return samples.size(); - } + size_t getNSamples() const { return samples.size(); } // Sort all the samples from low to high - std::vector getSamplesSorted() { + std::vector getSamplesSorted() + { auto ret = std::vector(samples.begin(), samples.end()); std::sort(ret.begin(), ret.end()); return ret; } - std::string getAllSamplesSortedAsString() { + std::string getAllSamplesSortedAsString() + { const auto valuesSorted = getSamplesSorted(); return MyTimeHelper::timeSamplesAsString(valuesSorted); } - std::string getOnePercentLowHigh() { - auto valuesSorted = getSamplesSorted(); + std::string getOnePercentLowHigh() + { + auto valuesSorted = getSamplesSorted(); const auto sizeOnePercent = valuesSorted.size() / 100; - const auto onePercentLow = std::vector(valuesSorted.begin(), - valuesSorted.begin() + - sizeOnePercent); - const auto tmpBegin = valuesSorted.begin() + valuesSorted.size() - sizeOnePercent; - const auto onePercentHigh = std::vector(tmpBegin, - valuesSorted.end()); + const auto onePercentLow = + std::vector(valuesSorted.begin(), valuesSorted.begin() + sizeOnePercent); + const auto tmpBegin = valuesSorted.begin() + valuesSorted.size() - sizeOnePercent; + const auto onePercentHigh = std::vector(tmpBegin, valuesSorted.end()); std::stringstream ss; ss << "One Percent low:\n"; ss << MyTimeHelper::timeSamplesAsString(onePercentLow); @@ -278,97 +307,105 @@ class AvgCalculator2 { } }; -class Chronometer : public AvgCalculator { -public: +class Chronometer : public AvgCalculator +{ + public: explicit Chronometer(std::string name = "Unknown") : mName(std::move(name)) {} - void start() { - startTS = std::chrono::steady_clock::now(); - } + void start() { startTS = std::chrono::steady_clock::now(); } - void stop() { - const auto now = std::chrono::steady_clock::now(); + void stop() + { + const auto now = std::chrono::steady_clock::now(); const auto delta = (now - startTS); AvgCalculator::add(delta); } - void printInIntervalls(const std::chrono::steady_clock::duration &interval, - const bool avgOnly = true) { + void printInIntervalls(const std::chrono::steady_clock::duration& interval, const bool avgOnly = true) + { const auto now = std::chrono::steady_clock::now(); - if (now - lastLog > interval) { + if (now - lastLog > interval) + { lastLog = now; MLOGD2(mName) << "Avg: " << AvgCalculator::getAvgReadable(avgOnly); reset(); } } -private: - const std::string mName; + private: + const std::string mName; std::chrono::steady_clock::time_point startTS; std::chrono::steady_clock::time_point lastLog; }; -class RelativeCalculator { -private: - long sum = 0; +class RelativeCalculator +{ + private: + long sum = 0; long sumAtLastCall = 0; -public: + + public: RelativeCalculator() = default; - void add(unsigned long x) { - sum += x; - } + void add(unsigned long x) { sum += x; } - long getDeltaSinceLastCall() { - long ret = sum - sumAtLastCall; + long getDeltaSinceLastCall() + { + long ret = sum - sumAtLastCall; sumAtLastCall = sum; return ret; } - long getAbsolute() { - return sum; - } + long getAbsolute() { return sum; } - void reset() { - sum = 0; + void reset() + { + sum = 0; sumAtLastCall = 0; } }; -namespace TEST_TIME_HELPER { - static void test() { - std::vector testData = { - std::chrono::nanoseconds(1), - std::chrono::nanoseconds(100), - std::chrono::nanoseconds(5), - }; - AvgCalculator avgCalculator; - avgCalculator.reset(); - MLOGD << "XMIN" << MyTimeHelper::R(avgCalculator.getMin()) << " " - << MyTimeHelper::R(std::numeric_limits::max()) << " " - << MyTimeHelper::R(std::chrono::nanoseconds::max()); - for (const auto t: testData) { - avgCalculator.add(t); - } - assert(avgCalculator.getMin() == std::chrono::nanoseconds(1)); - assert(avgCalculator.getMax() == std::chrono::nanoseconds(100)); - } -}; +namespace TEST_TIME_HELPER +{ +static void test() +{ + std::vector testData = { + std::chrono::nanoseconds(1), + std::chrono::nanoseconds(100), + std::chrono::nanoseconds(5), + }; + AvgCalculator avgCalculator; + avgCalculator.reset(); + MLOGD << "XMIN" << MyTimeHelper::R(avgCalculator.getMin()) << " " + << MyTimeHelper::R(std::numeric_limits::max()) << " " + << MyTimeHelper::R(std::chrono::nanoseconds::max()); + for (const auto t : testData) + { + avgCalculator.add(t); + } + assert(avgCalculator.getMin() == std::chrono::nanoseconds(1)); + assert(avgCalculator.getMax() == std::chrono::nanoseconds(100)); +} +}; // namespace TEST_TIME_HELPER -class MeasureExecutionTime { -private: +class MeasureExecutionTime +{ + private: const std::chrono::steady_clock::time_point begin; - const std::string functionName; - const std::string tag; -public: - MeasureExecutionTime(const std::string &tag, const std::string &functionName) : functionName( - functionName), tag(tag), begin(std::chrono::steady_clock::now()) {} + const std::string functionName; + const std::string tag; + + public: + MeasureExecutionTime(const std::string& tag, const std::string& functionName) + : functionName(functionName), tag(tag), begin(std::chrono::steady_clock::now()) + { + } - ~MeasureExecutionTime() { + ~MeasureExecutionTime() + { const auto duration = std::chrono::steady_clock::now() - begin; MLOGD2(tag) << "Execution time for " << functionName << " is " - << std::chrono::duration_cast(duration).count() - << "ms"; + << std::chrono::duration_cast(duration).count() << "ms"; } }; @@ -376,22 +413,23 @@ class MeasureExecutionTime { // See https://stackoverflow.com/questions/22387586/measuring-execution-time-of-a-function-in-c/61886741#61886741 // Example output: ExecutionTime: For DecodeMJPEGtoANativeWindowBuffer is 54ms // __CLASS_NAME__ comes from AndroidLogger -#define MEASURE_FUNCTION_EXECUTION_TIME const MeasureExecutionTime measureExecutionTime(__CLASS_NAME__,__FUNCTION__); +#define MEASURE_FUNCTION_EXECUTION_TIME const MeasureExecutionTime measureExecutionTime(__CLASS_NAME__, __FUNCTION__); #include #include -namespace TestSleep { - //template - static void - sleep(const std::chrono::steady_clock::duration &duration, const bool print = false) { - const auto before = std::chrono::steady_clock::now(); - std::this_thread::sleep_for(duration); - const auto actualSleepTime = std::chrono::steady_clock::now() - before; - if (print) { - MLOGD << "Slept for " << MyTimeHelper::R(actualSleepTime) << " instead of " - << MyTimeHelper::R(duration); - } +namespace TestSleep +{ +// template +static void sleep(const std::chrono::steady_clock::duration& duration, const bool print = false) +{ + const auto before = std::chrono::steady_clock::now(); + std::this_thread::sleep_for(duration); + const auto actualSleepTime = std::chrono::steady_clock::now() - before; + if (print) + { + MLOGD << "Slept for " << MyTimeHelper::R(actualSleepTime) << " instead of " << MyTimeHelper::R(duration); } } -#endif //FPVUE_TIMEHELPER_HPP \ No newline at end of file +} // namespace TestSleep +#endif // FPVUE_TIMEHELPER_HPP \ No newline at end of file diff --git a/app/videonative/src/main/cpp/minimp4.h b/app/videonative/src/main/cpp/minimp4.h index 510914e..09b6690 100644 --- a/app/videonative/src/main/cpp/minimp4.h +++ b/app/videonative/src/main/cpp/minimp4.h @@ -3,550 +3,570 @@ /* https://github.com/aspt/mp4 https://github.com/lieff/minimp4 - To the extent possible under law, the author(s) have dedicated all copyright and related and neighboring rights to this software to the public domain worldwide. - This software is distributed without any warranty. - See . + To the extent possible under law, the author(s) have dedicated all copyright and related and neighboring rights to + this software to the public domain worldwide. This software is distributed without any warranty. See + . */ -#include +#include +#include #include +#include #include #include -#include -#include #ifdef __cplusplus -extern "C" { +extern "C" +{ #endif #define MINIMP4_MIN(x, y) ((x) < (y) ? (x) : (y)) -/************************************************************************/ -/* Build configuration */ -/************************************************************************/ + /************************************************************************/ + /* Build configuration */ + /************************************************************************/ -#define FIX_BAD_ANDROID_META_BOX 1 +#define FIX_BAD_ANDROID_META_BOX 1 -#define MAX_CHUNKS_DEPTH 64 // Max chunks nesting level +#define MAX_CHUNKS_DEPTH 64 // Max chunks nesting level #define MINIMP4_MAX_SPS 32 #define MINIMP4_MAX_PPS 256 -#define MINIMP4_TRANSCODE_SPS_ID 1 +#define MINIMP4_TRANSCODE_SPS_ID 1 // Support indexing of MP4 files over 4 GB. // If disabled, files with 64-bit offset fields is still supported, // but error signaled if such field contains too big offset // This switch affect return type of MP4D_frame_offset() function -#define MINIMP4_ALLOW_64BIT 1 +#define MINIMP4_ALLOW_64BIT 1 -#define MP4D_TRACE_SUPPORTED 0 // Debug trace -#define MP4D_TRACE_TIMESTAMPS 1 +#define MP4D_TRACE_SUPPORTED 0 // Debug trace +#define MP4D_TRACE_TIMESTAMPS 1 // Support parsing of supplementary information, not necessary for decoding: // duration, language, bitrate, metadata tags, etc -#define MP4D_INFO_SUPPORTED 1 +#define MP4D_INFO_SUPPORTED 1 // Enable code, which prints to stdout supplementary MP4 information: #define MP4D_PRINT_INFO_SUPPORTED 0 -#define MP4D_AVC_SUPPORTED 1 -#define MP4D_HEVC_SUPPORTED 1 +#define MP4D_AVC_SUPPORTED 1 +#define MP4D_HEVC_SUPPORTED 1 #define MP4D_TIMESTAMPS_SUPPORTED 1 // Enable TrackFragmentBaseMediaDecodeTimeBox support -#define MP4D_TFDT_SUPPORT 0 +#define MP4D_TFDT_SUPPORT 0 /************************************************************************/ /* Some values of MP4(E/D)_track_t->object_type_indication */ /************************************************************************/ // MPEG-4 AAC (all profiles) -#define MP4_OBJECT_TYPE_AUDIO_ISO_IEC_14496_3 0x40 +#define MP4_OBJECT_TYPE_AUDIO_ISO_IEC_14496_3 0x40 // MPEG-2 AAC, Main profile -#define MP4_OBJECT_TYPE_AUDIO_ISO_IEC_13818_7_MAIN_PROFILE 0x66 +#define MP4_OBJECT_TYPE_AUDIO_ISO_IEC_13818_7_MAIN_PROFILE 0x66 // MPEG-2 AAC, LC profile -#define MP4_OBJECT_TYPE_AUDIO_ISO_IEC_13818_7_LC_PROFILE 0x67 +#define MP4_OBJECT_TYPE_AUDIO_ISO_IEC_13818_7_LC_PROFILE 0x67 // MPEG-2 AAC, SSR profile -#define MP4_OBJECT_TYPE_AUDIO_ISO_IEC_13818_7_SSR_PROFILE 0x68 +#define MP4_OBJECT_TYPE_AUDIO_ISO_IEC_13818_7_SSR_PROFILE 0x68 // H.264 (AVC) video -#define MP4_OBJECT_TYPE_AVC 0x21 +#define MP4_OBJECT_TYPE_AVC 0x21 // H.265 (HEVC) video -#define MP4_OBJECT_TYPE_HEVC 0x23 +#define MP4_OBJECT_TYPE_HEVC 0x23 // http://www.mp4ra.org/object.html 0xC0-E0 && 0xE2 - 0xFE are specified as "user private" -#define MP4_OBJECT_TYPE_USER_PRIVATE 0xC0 +#define MP4_OBJECT_TYPE_USER_PRIVATE 0xC0 /************************************************************************/ /* API error codes */ /************************************************************************/ -#define MP4E_STATUS_OK 0 -#define MP4E_STATUS_BAD_ARGUMENTS -1 -#define MP4E_STATUS_NO_MEMORY -2 -#define MP4E_STATUS_FILE_WRITE_ERROR -3 -#define MP4E_STATUS_ONLY_ONE_DSI_ALLOWED -4 +#define MP4E_STATUS_OK 0 +#define MP4E_STATUS_BAD_ARGUMENTS -1 +#define MP4E_STATUS_NO_MEMORY -2 +#define MP4E_STATUS_FILE_WRITE_ERROR -3 +#define MP4E_STATUS_ONLY_ONE_DSI_ALLOWED -4 /************************************************************************/ /* Sample kind for MP4E_put_sample() */ /************************************************************************/ -#define MP4E_SAMPLE_DEFAULT 0 // (beginning of) audio or video frame -#define MP4E_SAMPLE_RANDOM_ACCESS 1 // mark sample as random access point (key frame) -#define MP4E_SAMPLE_CONTINUATION 2 // Not a sample, but continuation of previous sample (new slice) +#define MP4E_SAMPLE_DEFAULT 0 // (beginning of) audio or video frame +#define MP4E_SAMPLE_RANDOM_ACCESS 1 // mark sample as random access point (key frame) +#define MP4E_SAMPLE_CONTINUATION 2 // Not a sample, but continuation of previous sample (new slice) -/************************************************************************/ -/* Portable 64-bit type definition */ -/************************************************************************/ + /************************************************************************/ + /* Portable 64-bit type definition */ + /************************************************************************/ #if MINIMP4_ALLOW_64BIT -typedef uint64_t boxsize_t; + typedef uint64_t boxsize_t; #else typedef unsigned int boxsize_t; #endif -typedef boxsize_t MP4D_file_offset_t; + typedef boxsize_t MP4D_file_offset_t; /************************************************************************/ /* Some values of MP4D_track_t->handler_type */ /************************************************************************/ // Video track : 'vide' -#define MP4D_HANDLER_TYPE_VIDE 0x76696465 +#define MP4D_HANDLER_TYPE_VIDE 0x76696465 // Audio track : 'soun' -#define MP4D_HANDLER_TYPE_SOUN 0x736F756E +#define MP4D_HANDLER_TYPE_SOUN 0x736F756E // General MPEG-4 systems streams (without specific handler). // Used for private stream, as suggested in http://www.mp4ra.org/handler.html -#define MP4E_HANDLER_TYPE_GESM 0x6765736D - +#define MP4E_HANDLER_TYPE_GESM 0x6765736D #define HEVC_NAL_VPS 32 #define HEVC_NAL_SPS 33 #define HEVC_NAL_PPS 34 #define HEVC_NAL_BLA_W_LP 16 -#define HEVC_NAL_CRA_NUT 21 +#define HEVC_NAL_CRA_NUT 21 -/************************************************************************/ -/* Data structures */ -/************************************************************************/ + /************************************************************************/ + /* Data structures */ + /************************************************************************/ -typedef struct MP4E_mux_tag MP4E_mux_t; + typedef struct MP4E_mux_tag MP4E_mux_t; -typedef enum { - e_audio, - e_video, - e_private -} track_media_kind_t; + typedef enum + { + e_audio, + e_video, + e_private + } track_media_kind_t; -typedef struct { - // MP4 object type code, which defined codec class for the track. - // See MP4E_OBJECT_TYPE_* values for some codecs - unsigned object_type_indication; + typedef struct + { + // MP4 object type code, which defined codec class for the track. + // See MP4E_OBJECT_TYPE_* values for some codecs + unsigned object_type_indication; - // Track language: 3-char ISO 639-2T code: "und", "eng", "rus", "jpn" etc... - unsigned char language[4]; + // Track language: 3-char ISO 639-2T code: "und", "eng", "rus", "jpn" etc... + unsigned char language[4]; - track_media_kind_t track_media_kind; + track_media_kind_t track_media_kind; - // 90000 for video, sample rate for audio - unsigned time_scale; - unsigned default_duration; + // 90000 for video, sample rate for audio + unsigned time_scale; + unsigned default_duration; - union { - struct { - // number of channels in the audio track. - unsigned channelcount; - } a; + union + { + struct + { + // number of channels in the audio track. + unsigned channelcount; + } a; - struct { - int width; - int height; - } v; - } u; + struct + { + int width; + int height; + } v; + } u; -} MP4E_track_t; + } MP4E_track_t; -typedef struct MP4D_sample_to_chunk_t_tag MP4D_sample_to_chunk_t; + typedef struct MP4D_sample_to_chunk_t_tag MP4D_sample_to_chunk_t; -typedef struct { - /************************************************************************/ - /* mandatory public data */ - /************************************************************************/ - // How many 'samples' in the track - // The 'sample' is MP4 term, denoting audio or video frame - unsigned sample_count; - - // Decoder-specific info (DSI) data - unsigned char *dsi; - - // DSI data size - unsigned dsi_bytes; - - // MP4 object type code - // case 0x00: return "Forbidden"; - // case 0x01: return "Systems ISO/IEC 14496-1"; - // case 0x02: return "Systems ISO/IEC 14496-1"; - // case 0x20: return "Visual ISO/IEC 14496-2"; - // case 0x40: return "Audio ISO/IEC 14496-3"; - // case 0x60: return "Visual ISO/IEC 13818-2 Simple Profile"; - // case 0x61: return "Visual ISO/IEC 13818-2 Main Profile"; - // case 0x62: return "Visual ISO/IEC 13818-2 SNR Profile"; - // case 0x63: return "Visual ISO/IEC 13818-2 Spatial Profile"; - // case 0x64: return "Visual ISO/IEC 13818-2 High Profile"; - // case 0x65: return "Visual ISO/IEC 13818-2 422 Profile"; - // case 0x66: return "Audio ISO/IEC 13818-7 Main Profile"; - // case 0x67: return "Audio ISO/IEC 13818-7 LC Profile"; - // case 0x68: return "Audio ISO/IEC 13818-7 SSR Profile"; - // case 0x69: return "Audio ISO/IEC 13818-3"; - // case 0x6A: return "Visual ISO/IEC 11172-2"; - // case 0x6B: return "Audio ISO/IEC 11172-3"; - // case 0x6C: return "Visual ISO/IEC 10918-1"; - unsigned object_type_indication; + typedef struct + { + /************************************************************************/ + /* mandatory public data */ + /************************************************************************/ + // How many 'samples' in the track + // The 'sample' is MP4 term, denoting audio or video frame + unsigned sample_count; + + // Decoder-specific info (DSI) data + unsigned char* dsi; + + // DSI data size + unsigned dsi_bytes; + + // MP4 object type code + // case 0x00: return "Forbidden"; + // case 0x01: return "Systems ISO/IEC 14496-1"; + // case 0x02: return "Systems ISO/IEC 14496-1"; + // case 0x20: return "Visual ISO/IEC 14496-2"; + // case 0x40: return "Audio ISO/IEC 14496-3"; + // case 0x60: return "Visual ISO/IEC 13818-2 Simple Profile"; + // case 0x61: return "Visual ISO/IEC 13818-2 Main Profile"; + // case 0x62: return "Visual ISO/IEC 13818-2 SNR Profile"; + // case 0x63: return "Visual ISO/IEC 13818-2 Spatial Profile"; + // case 0x64: return "Visual ISO/IEC 13818-2 High Profile"; + // case 0x65: return "Visual ISO/IEC 13818-2 422 Profile"; + // case 0x66: return "Audio ISO/IEC 13818-7 Main Profile"; + // case 0x67: return "Audio ISO/IEC 13818-7 LC Profile"; + // case 0x68: return "Audio ISO/IEC 13818-7 SSR Profile"; + // case 0x69: return "Audio ISO/IEC 13818-3"; + // case 0x6A: return "Visual ISO/IEC 11172-2"; + // case 0x6B: return "Audio ISO/IEC 11172-3"; + // case 0x6C: return "Visual ISO/IEC 10918-1"; + unsigned object_type_indication; #if MP4D_INFO_SUPPORTED - /************************************************************************/ - /* informational public data */ - /************************************************************************/ - // handler_type when present in a media box, is an integer containing one of - // the following values, or a value from a derived specification: - // 'vide' Video track - // 'soun' Audio track - // 'hint' Hint track - unsigned handler_type; - - // Track duration: 64-bit value split into 2 variables - unsigned duration_hi; - unsigned duration_lo; - - // duration scale: duration = timescale*seconds - unsigned timescale; - - // Average bitrate, bits per second - unsigned avg_bitrate_bps; - - // Track language: 3-char ISO 639-2T code: "und", "eng", "rus", "jpn" etc... - unsigned char language[4]; - - // MP4 stream type - // case 0x00: return "Forbidden"; - // case 0x01: return "ObjectDescriptorStream"; - // case 0x02: return "ClockReferenceStream"; - // case 0x03: return "SceneDescriptionStream"; - // case 0x04: return "VisualStream"; - // case 0x05: return "AudioStream"; - // case 0x06: return "MPEG7Stream"; - // case 0x07: return "IPMPStream"; - // case 0x08: return "ObjectContentInfoStream"; - // case 0x09: return "MPEGJStream"; - unsigned stream_type; - - union { - // for handler_type == 'soun' tracks - struct { - unsigned channelcount; - unsigned samplerate_hz; - } audio; - - // for handler_type == 'vide' tracks - struct { - unsigned width; - unsigned height; - } video; - } SampleDescription; + /************************************************************************/ + /* informational public data */ + /************************************************************************/ + // handler_type when present in a media box, is an integer containing one of + // the following values, or a value from a derived specification: + // 'vide' Video track + // 'soun' Audio track + // 'hint' Hint track + unsigned handler_type; + + // Track duration: 64-bit value split into 2 variables + unsigned duration_hi; + unsigned duration_lo; + + // duration scale: duration = timescale*seconds + unsigned timescale; + + // Average bitrate, bits per second + unsigned avg_bitrate_bps; + + // Track language: 3-char ISO 639-2T code: "und", "eng", "rus", "jpn" etc... + unsigned char language[4]; + + // MP4 stream type + // case 0x00: return "Forbidden"; + // case 0x01: return "ObjectDescriptorStream"; + // case 0x02: return "ClockReferenceStream"; + // case 0x03: return "SceneDescriptionStream"; + // case 0x04: return "VisualStream"; + // case 0x05: return "AudioStream"; + // case 0x06: return "MPEG7Stream"; + // case 0x07: return "IPMPStream"; + // case 0x08: return "ObjectContentInfoStream"; + // case 0x09: return "MPEGJStream"; + unsigned stream_type; + + union + { + // for handler_type == 'soun' tracks + struct + { + unsigned channelcount; + unsigned samplerate_hz; + } audio; + + // for handler_type == 'vide' tracks + struct + { + unsigned width; + unsigned height; + } video; + } SampleDescription; #endif - /************************************************************************/ - /* private data: MP4 indexes */ - /************************************************************************/ - unsigned *entry_size; + /************************************************************************/ + /* private data: MP4 indexes */ + /************************************************************************/ + unsigned* entry_size; - unsigned sample_to_chunk_count; - struct MP4D_sample_to_chunk_t_tag *sample_to_chunk; + unsigned sample_to_chunk_count; + struct MP4D_sample_to_chunk_t_tag* sample_to_chunk; - unsigned chunk_count; - MP4D_file_offset_t *chunk_offset; + unsigned chunk_count; + MP4D_file_offset_t* chunk_offset; #if MP4D_TIMESTAMPS_SUPPORTED - unsigned *timestamp; - unsigned *duration; + unsigned* timestamp; + unsigned* duration; #endif -} MP4D_track_t; + } MP4D_track_t; -typedef struct MP4D_demux_tag { - /************************************************************************/ - /* mandatory public data */ - /************************************************************************/ - int64_t read_pos; - int64_t read_size; - MP4D_track_t *track; + typedef struct MP4D_demux_tag + { + /************************************************************************/ + /* mandatory public data */ + /************************************************************************/ + int64_t read_pos; + int64_t read_size; + MP4D_track_t* track; - int (*read_callback)(int64_t offset, void *buffer, size_t size, void *token); + int (*read_callback)(int64_t offset, void* buffer, size_t size, void* token); - void *token; + void* token; - unsigned track_count; // number of tracks in the movie + unsigned track_count; // number of tracks in the movie #if MP4D_INFO_SUPPORTED - /************************************************************************/ - /* informational public data */ - /************************************************************************/ - // Movie duration: 64-bit value split into 2 variables - unsigned duration_hi; - unsigned duration_lo; - - // duration scale: duration = timescale*seconds - unsigned timescale; - - // Metadata tag (optional) - // Tags provided 'as-is', without any re-encoding - struct { - unsigned char *title; - unsigned char *artist; - unsigned char *album; - unsigned char *year; - unsigned char *comment; - unsigned char *genre; - } tag; + /************************************************************************/ + /* informational public data */ + /************************************************************************/ + // Movie duration: 64-bit value split into 2 variables + unsigned duration_hi; + unsigned duration_lo; + + // duration scale: duration = timescale*seconds + unsigned timescale; + + // Metadata tag (optional) + // Tags provided 'as-is', without any re-encoding + struct + { + unsigned char* title; + unsigned char* artist; + unsigned char* album; + unsigned char* year; + unsigned char* comment; + unsigned char* genre; + } tag; #endif -} MP4D_demux_t; + } MP4D_demux_t; -struct MP4D_sample_to_chunk_t_tag { - unsigned first_chunk; - unsigned samples_per_chunk; -}; + struct MP4D_sample_to_chunk_t_tag + { + unsigned first_chunk; + unsigned samples_per_chunk; + }; -typedef struct { - void *sps_cache[MINIMP4_MAX_SPS]; - void *pps_cache[MINIMP4_MAX_PPS]; - int sps_bytes[MINIMP4_MAX_SPS]; - int pps_bytes[MINIMP4_MAX_PPS]; + typedef struct + { + void* sps_cache[MINIMP4_MAX_SPS]; + void* pps_cache[MINIMP4_MAX_PPS]; + int sps_bytes[MINIMP4_MAX_SPS]; + int pps_bytes[MINIMP4_MAX_PPS]; - int map_sps[MINIMP4_MAX_SPS]; - int map_pps[MINIMP4_MAX_PPS]; + int map_sps[MINIMP4_MAX_SPS]; + int map_pps[MINIMP4_MAX_PPS]; -} h264_sps_id_patcher_t; + } h264_sps_id_patcher_t; -typedef struct mp4_h26x_writer_tag { + typedef struct mp4_h26x_writer_tag + { #if MINIMP4_TRANSCODE_SPS_ID - h264_sps_id_patcher_t sps_patcher; + h264_sps_id_patcher_t sps_patcher; #endif - MP4E_mux_t *mux; - int mux_track_id, is_hevc, need_vps, need_sps, need_pps, need_idr; -} mp4_h26x_writer_t; - -int mp4_h26x_write_init(mp4_h26x_writer_t *h, MP4E_mux_t *mux, int width, int height, int is_hevc); -void mp4_h26x_write_close(mp4_h26x_writer_t *h); -int mp4_h26x_write_nal(mp4_h26x_writer_t *h, const unsigned char *nal, int length, - unsigned timeStamp90kHz_next); - -/************************************************************************/ -/* API */ -/************************************************************************/ + MP4E_mux_t* mux; + int mux_track_id, is_hevc, need_vps, need_sps, need_pps, need_idr; + } mp4_h26x_writer_t; -/** -* Parse given input stream as MP4 file. Allocate and store data indexes. -* return 1 on success, 0 on failure -* The MP4 indexes may be stored at the end of stream, so this -* function may parse all stream. -* It is guaranteed that function will read/seek sequentially, -* and will never jump back. -*/ -int MP4D_open(MP4D_demux_t *mp4, - int (*read_callback)(int64_t offset, void *buffer, size_t size, void *token), - void *token, int64_t file_size); + int mp4_h26x_write_init(mp4_h26x_writer_t* h, MP4E_mux_t* mux, int width, int height, int is_hevc); + void mp4_h26x_write_close(mp4_h26x_writer_t* h); + int mp4_h26x_write_nal(mp4_h26x_writer_t* h, const unsigned char* nal, int length, unsigned timeStamp90kHz_next); -/** -* Return position and size for given sample from given track. The 'sample' is a -* MP4 term for 'frame' -* -* frame_bytes [OUT] - return coded frame size in bytes -* timestamp [OUT] - return frame timestamp (in mp4->timescale units) -* duration [OUT] - return frame duration (in mp4->timescale units) -* -* function return offset for the frame -*/ -MP4D_file_offset_t -MP4D_frame_offset(const MP4D_demux_t *mp4, unsigned int ntrack, unsigned int nsample, - unsigned int *frame_bytes, unsigned *timestamp, unsigned *duration); + /************************************************************************/ + /* API */ + /************************************************************************/ -/** -* De-allocated memory -*/ -void MP4D_close(MP4D_demux_t *mp4); - -/** -* Helper functions to parse mp4.track[ntrack].dsi for H.264 SPS/PPS -* Return pointer to internal mp4 memory, it must not be free()-ed -* -* Example: process all SPS in MP4 file: -* while (sps = MP4D_read_sps(mp4, num_of_avc_track, sps_count, &sps_bytes)) -* { -* process(sps, sps_bytes); -* sps_count++; -* } -*/ -const void *MP4D_read_sps(const MP4D_demux_t *mp4, unsigned int ntrack, int nsps, int *sps_bytes); -const void *MP4D_read_pps(const MP4D_demux_t *mp4, unsigned int ntrack, int npps, int *pps_bytes); + /** + * Parse given input stream as MP4 file. Allocate and store data indexes. + * return 1 on success, 0 on failure + * The MP4 indexes may be stored at the end of stream, so this + * function may parse all stream. + * It is guaranteed that function will read/seek sequentially, + * and will never jump back. + */ + int MP4D_open( + MP4D_demux_t* mp4, + int (*read_callback)(int64_t offset, void* buffer, size_t size, void* token), + void* token, + int64_t file_size); + + /** + * Return position and size for given sample from given track. The 'sample' is a + * MP4 term for 'frame' + * + * frame_bytes [OUT] - return coded frame size in bytes + * timestamp [OUT] - return frame timestamp (in mp4->timescale units) + * duration [OUT] - return frame duration (in mp4->timescale units) + * + * function return offset for the frame + */ + MP4D_file_offset_t MP4D_frame_offset( + const MP4D_demux_t* mp4, + unsigned int ntrack, + unsigned int nsample, + unsigned int* frame_bytes, + unsigned* timestamp, + unsigned* duration); + + /** + * De-allocated memory + */ + void MP4D_close(MP4D_demux_t* mp4); + + /** + * Helper functions to parse mp4.track[ntrack].dsi for H.264 SPS/PPS + * Return pointer to internal mp4 memory, it must not be free()-ed + * + * Example: process all SPS in MP4 file: + * while (sps = MP4D_read_sps(mp4, num_of_avc_track, sps_count, &sps_bytes)) + * { + * process(sps, sps_bytes); + * sps_count++; + * } + */ + const void* MP4D_read_sps(const MP4D_demux_t* mp4, unsigned int ntrack, int nsps, int* sps_bytes); + const void* MP4D_read_pps(const MP4D_demux_t* mp4, unsigned int ntrack, int npps, int* pps_bytes); #if MP4D_PRINT_INFO_SUPPORTED -/** -* Print MP4 information to stdout. -* Uses printf() as well as floating-point functions -* Given as implementation example and for test purposes -*/ -void MP4D_printf_info(const MP4D_demux_t *mp4); + /** + * Print MP4 information to stdout. + * Uses printf() as well as floating-point functions + * Given as implementation example and for test purposes + */ + void MP4D_printf_info(const MP4D_demux_t* mp4); #endif -/** -* Allocates and initialize mp4 multiplexor -* Given file handler is transparent to the MP4 library, and used only as -* argument for given fwrite_callback() function. By appropriate definition -* of callback function application may use any other file output API (for -* example C++ streams, or Win32 file functions) -* -* return multiplexor handle on success; NULL on failure -*/ -MP4E_mux_t *MP4E_open(int sequential_mode_flag, int enable_fragmentation, void *token, - int (*write_callback)(int64_t offset, const void *buffer, size_t size, - void *token)); - -/** -* Add new track -* The track_data parameter does not referred by the multiplexer after function -* return, and may be allocated in short-time memory. The dsi member of -* track_data parameter is mandatory. -* -* return ID of added track, or error code MP4E_STATUS_* -*/ -int MP4E_add_track(MP4E_mux_t *mux, const MP4E_track_t *track_data); - -/** -* Add new sample to specified track -* The tracks numbered starting with 0, according to order of MP4E_add_track() calls -* 'kind' is one of MP4E_SAMPLE_... defines -* -* return error code MP4E_STATUS_* -* -* Example: -* MP4E_put_sample(mux, 0, data, data_bytes, duration, MP4E_SAMPLE_DEFAULT); -*/ -int MP4E_put_sample(MP4E_mux_t *mux, int track_num, const void *data, int data_bytes, int duration, - int kind); - -/** -* Finalize MP4 file, de-allocated memory, and closes MP4 multiplexer. -* The close operation takes a time and disk space, since it writes MP4 file -* indexes. Please note that this function does not closes file handle, -* which was passed to open function. -* -* return error code MP4E_STATUS_* -*/ -int MP4E_close(MP4E_mux_t *mux); - -/** -* Set Decoder Specific Info (DSI) -* Can be used for audio and private tracks. -* MUST be used for AAC track. -* Only one DSI can be set. It is an error to set DSI again -* -* return error code MP4E_STATUS_* -*/ -int MP4E_set_dsi(MP4E_mux_t *mux, int track_id, const void *dsi, int bytes); - -/** -* Set VPS data. MUST be used for HEVC (H.265) track. -* -* return error code MP4E_STATUS_* -*/ -int MP4E_set_vps(MP4E_mux_t *mux, int track_id, const void *vps, int bytes); - -/** -* Set SPS data. MUST be used for AVC (H.264) track. Up to 32 different SPS can be used in one track. -* -* return error code MP4E_STATUS_* -*/ -int MP4E_set_sps(MP4E_mux_t *mux, int track_id, const void *sps, int bytes); - -/** -* Set PPS data. MUST be used for AVC (H.264) track. Up to 256 different PPS can be used in one track. -* -* return error code MP4E_STATUS_* -*/ -int MP4E_set_pps(MP4E_mux_t *mux, int track_id, const void *pps, int bytes); - -/** -* Set or replace ASCII test comment for the file. Set comment to NULL to remove comment. -* -* return error code MP4E_STATUS_* -*/ -int MP4E_set_text_comment(MP4E_mux_t *mux, const char *comment); + /** + * Allocates and initialize mp4 multiplexor + * Given file handler is transparent to the MP4 library, and used only as + * argument for given fwrite_callback() function. By appropriate definition + * of callback function application may use any other file output API (for + * example C++ streams, or Win32 file functions) + * + * return multiplexor handle on success; NULL on failure + */ + MP4E_mux_t* MP4E_open( + int sequential_mode_flag, + int enable_fragmentation, + void* token, + int (*write_callback)(int64_t offset, const void* buffer, size_t size, void* token)); + + /** + * Add new track + * The track_data parameter does not referred by the multiplexer after function + * return, and may be allocated in short-time memory. The dsi member of + * track_data parameter is mandatory. + * + * return ID of added track, or error code MP4E_STATUS_* + */ + int MP4E_add_track(MP4E_mux_t* mux, const MP4E_track_t* track_data); + + /** + * Add new sample to specified track + * The tracks numbered starting with 0, according to order of MP4E_add_track() calls + * 'kind' is one of MP4E_SAMPLE_... defines + * + * return error code MP4E_STATUS_* + * + * Example: + * MP4E_put_sample(mux, 0, data, data_bytes, duration, MP4E_SAMPLE_DEFAULT); + */ + int MP4E_put_sample(MP4E_mux_t* mux, int track_num, const void* data, int data_bytes, int duration, int kind); + + /** + * Finalize MP4 file, de-allocated memory, and closes MP4 multiplexer. + * The close operation takes a time and disk space, since it writes MP4 file + * indexes. Please note that this function does not closes file handle, + * which was passed to open function. + * + * return error code MP4E_STATUS_* + */ + int MP4E_close(MP4E_mux_t* mux); + + /** + * Set Decoder Specific Info (DSI) + * Can be used for audio and private tracks. + * MUST be used for AAC track. + * Only one DSI can be set. It is an error to set DSI again + * + * return error code MP4E_STATUS_* + */ + int MP4E_set_dsi(MP4E_mux_t* mux, int track_id, const void* dsi, int bytes); + + /** + * Set VPS data. MUST be used for HEVC (H.265) track. + * + * return error code MP4E_STATUS_* + */ + int MP4E_set_vps(MP4E_mux_t* mux, int track_id, const void* vps, int bytes); + + /** + * Set SPS data. MUST be used for AVC (H.264) track. Up to 32 different SPS can be used in one track. + * + * return error code MP4E_STATUS_* + */ + int MP4E_set_sps(MP4E_mux_t* mux, int track_id, const void* sps, int bytes); + + /** + * Set PPS data. MUST be used for AVC (H.264) track. Up to 256 different PPS can be used in one track. + * + * return error code MP4E_STATUS_* + */ + int MP4E_set_pps(MP4E_mux_t* mux, int track_id, const void* pps, int bytes); + + /** + * Set or replace ASCII test comment for the file. Set comment to NULL to remove comment. + * + * return error code MP4E_STATUS_* + */ + int MP4E_set_text_comment(MP4E_mux_t* mux, const char* comment); #ifdef __cplusplus } #endif -#endif //MINIMP4_H - - -//#if defined(MINIMP4_IMPLEMENTATION) && !defined(MINIMP4_IMPLEMENTATION_GUARD) - -#define FOUR_CHAR_INT(a, b, c, d) (((uint32_t)(a) << 24) | ((b) << 16) | ((c) << 8) | (d)) -enum { - BOX_co64 = FOUR_CHAR_INT('c', 'o', '6', '4'),//ChunkLargeOffsetAtomType - BOX_stco = FOUR_CHAR_INT('s', 't', 'c', 'o'),//ChunkOffsetAtomType - BOX_crhd = FOUR_CHAR_INT('c', 'r', 'h', 'd'),//ClockReferenceMediaHeaderAtomType - BOX_ctts = FOUR_CHAR_INT('c', 't', 't', 's'),//CompositionOffsetAtomType - BOX_cprt = FOUR_CHAR_INT('c', 'p', 'r', 't'),//CopyrightAtomType - BOX_url_ = FOUR_CHAR_INT('u', 'r', 'l', ' '),//DataEntryURLAtomType - BOX_urn_ = FOUR_CHAR_INT('u', 'r', 'n', ' '),//DataEntryURNAtomType - BOX_dinf = FOUR_CHAR_INT('d', 'i', 'n', 'f'),//DataInformationAtomType - BOX_dref = FOUR_CHAR_INT('d', 'r', 'e', 'f'),//DataReferenceAtomType - BOX_stdp = FOUR_CHAR_INT('s', 't', 'd', 'p'),//DegradationPriorityAtomType - BOX_edts = FOUR_CHAR_INT('e', 'd', 't', 's'),//EditAtomType - BOX_elst = FOUR_CHAR_INT('e', 'l', 's', 't'),//EditListAtomType - BOX_uuid = FOUR_CHAR_INT('u', 'u', 'i', 'd'),//ExtendedAtomType - BOX_free = FOUR_CHAR_INT('f', 'r', 'e', 'e'),//FreeSpaceAtomType - BOX_hdlr = FOUR_CHAR_INT('h', 'd', 'l', 'r'),//HandlerAtomType - BOX_hmhd = FOUR_CHAR_INT('h', 'm', 'h', 'd'),//HintMediaHeaderAtomType - BOX_hint = FOUR_CHAR_INT('h', 'i', 'n', 't'),//HintTrackReferenceAtomType - BOX_mdia = FOUR_CHAR_INT('m', 'd', 'i', 'a'),//MediaAtomType - BOX_mdat = FOUR_CHAR_INT('m', 'd', 'a', 't'),//MediaDataAtomType - BOX_mdhd = FOUR_CHAR_INT('m', 'd', 'h', 'd'),//MediaHeaderAtomType - BOX_minf = FOUR_CHAR_INT('m', 'i', 'n', 'f'),//MediaInformationAtomType - BOX_moov = FOUR_CHAR_INT('m', 'o', 'o', 'v'),//MovieAtomType - BOX_mvhd = FOUR_CHAR_INT('m', 'v', 'h', 'd'),//MovieHeaderAtomType - BOX_stsd = FOUR_CHAR_INT('s', 't', 's', 'd'),//SampleDescriptionAtomType - BOX_stsz = FOUR_CHAR_INT('s', 't', 's', 'z'),//SampleSizeAtomType - BOX_stz2 = FOUR_CHAR_INT('s', 't', 'z', '2'),//CompactSampleSizeAtomType - BOX_stbl = FOUR_CHAR_INT('s', 't', 'b', 'l'),//SampleTableAtomType - BOX_stsc = FOUR_CHAR_INT('s', 't', 's', 'c'),//SampleToChunkAtomType - BOX_stsh = FOUR_CHAR_INT('s', 't', 's', 'h'),//ShadowSyncAtomType - BOX_skip = FOUR_CHAR_INT('s', 'k', 'i', 'p'),//SkipAtomType - BOX_smhd = FOUR_CHAR_INT('s', 'm', 'h', 'd'),//SoundMediaHeaderAtomType - BOX_stss = FOUR_CHAR_INT('s', 't', 's', 's'),//SyncSampleAtomType - BOX_stts = FOUR_CHAR_INT('s', 't', 't', 's'),//TimeToSampleAtomType - BOX_trak = FOUR_CHAR_INT('t', 'r', 'a', 'k'),//TrackAtomType - BOX_tkhd = FOUR_CHAR_INT('t', 'k', 'h', 'd'),//TrackHeaderAtomType - BOX_tref = FOUR_CHAR_INT('t', 'r', 'e', 'f'),//TrackReferenceAtomType - BOX_udta = FOUR_CHAR_INT('u', 'd', 't', 'a'),//UserDataAtomType - BOX_vmhd = FOUR_CHAR_INT('v', 'm', 'h', 'd'),//VideoMediaHeaderAtomType - BOX_url = FOUR_CHAR_INT('u', 'r', 'l', ' '), - BOX_urn = FOUR_CHAR_INT('u', 'r', 'n', ' '), - - BOX_gnrv = FOUR_CHAR_INT('g', 'n', 'r', 'v'),//GenericVisualSampleEntryAtomType - BOX_gnra = FOUR_CHAR_INT('g', 'n', 'r', 'a'),//GenericAudioSampleEntryAtomType - - //V2 atoms - BOX_ftyp = FOUR_CHAR_INT('f', 't', 'y', 'p'),//FileTypeAtomType - BOX_padb = FOUR_CHAR_INT('p', 'a', 'd', 'b'),//PaddingBitsAtomType - - //MP4 Atoms - BOX_sdhd = FOUR_CHAR_INT('s', 'd', 'h', 'd'),//SceneDescriptionMediaHeaderAtomType - BOX_dpnd = FOUR_CHAR_INT('d', 'p', 'n', 'd'),//StreamDependenceAtomType - BOX_iods = FOUR_CHAR_INT('i', 'o', 'd', 's'),//ObjectDescriptorAtomType - BOX_odhd = FOUR_CHAR_INT('o', 'd', 'h', 'd'),//ObjectDescriptorMediaHeaderAtomType - BOX_mpod = FOUR_CHAR_INT('m', 'p', 'o', 'd'),//ODTrackReferenceAtomType - BOX_nmhd = FOUR_CHAR_INT('n', 'm', 'h', 'd'),//MPEGMediaHeaderAtomType - BOX_esds = FOUR_CHAR_INT('e', 's', 'd', 's'),//ESDAtomType - BOX_sync = FOUR_CHAR_INT('s', 'y', 'n', 'c'),//OCRReferenceAtomType - BOX_ipir = FOUR_CHAR_INT('i', 'p', 'i', 'r'),//IPIReferenceAtomType - BOX_mp4s = FOUR_CHAR_INT('m', 'p', '4', 's'),//MPEGSampleEntryAtomType - BOX_mp4a = FOUR_CHAR_INT('m', 'p', '4', 'a'),//MPEGAudioSampleEntryAtomType - BOX_mp4v = FOUR_CHAR_INT('m', 'p', '4', 'v'),//MPEGVisualSampleEntryAtomType +#endif // MINIMP4_H + +// #if defined(MINIMP4_IMPLEMENTATION) && !defined(MINIMP4_IMPLEMENTATION_GUARD) + +#define FOUR_CHAR_INT(a, b, c, d) (((uint32_t) (a) << 24) | ((b) << 16) | ((c) << 8) | (d)) +enum +{ + BOX_co64 = FOUR_CHAR_INT('c', 'o', '6', '4'), // ChunkLargeOffsetAtomType + BOX_stco = FOUR_CHAR_INT('s', 't', 'c', 'o'), // ChunkOffsetAtomType + BOX_crhd = FOUR_CHAR_INT('c', 'r', 'h', 'd'), // ClockReferenceMediaHeaderAtomType + BOX_ctts = FOUR_CHAR_INT('c', 't', 't', 's'), // CompositionOffsetAtomType + BOX_cprt = FOUR_CHAR_INT('c', 'p', 'r', 't'), // CopyrightAtomType + BOX_url_ = FOUR_CHAR_INT('u', 'r', 'l', ' '), // DataEntryURLAtomType + BOX_urn_ = FOUR_CHAR_INT('u', 'r', 'n', ' '), // DataEntryURNAtomType + BOX_dinf = FOUR_CHAR_INT('d', 'i', 'n', 'f'), // DataInformationAtomType + BOX_dref = FOUR_CHAR_INT('d', 'r', 'e', 'f'), // DataReferenceAtomType + BOX_stdp = FOUR_CHAR_INT('s', 't', 'd', 'p'), // DegradationPriorityAtomType + BOX_edts = FOUR_CHAR_INT('e', 'd', 't', 's'), // EditAtomType + BOX_elst = FOUR_CHAR_INT('e', 'l', 's', 't'), // EditListAtomType + BOX_uuid = FOUR_CHAR_INT('u', 'u', 'i', 'd'), // ExtendedAtomType + BOX_free = FOUR_CHAR_INT('f', 'r', 'e', 'e'), // FreeSpaceAtomType + BOX_hdlr = FOUR_CHAR_INT('h', 'd', 'l', 'r'), // HandlerAtomType + BOX_hmhd = FOUR_CHAR_INT('h', 'm', 'h', 'd'), // HintMediaHeaderAtomType + BOX_hint = FOUR_CHAR_INT('h', 'i', 'n', 't'), // HintTrackReferenceAtomType + BOX_mdia = FOUR_CHAR_INT('m', 'd', 'i', 'a'), // MediaAtomType + BOX_mdat = FOUR_CHAR_INT('m', 'd', 'a', 't'), // MediaDataAtomType + BOX_mdhd = FOUR_CHAR_INT('m', 'd', 'h', 'd'), // MediaHeaderAtomType + BOX_minf = FOUR_CHAR_INT('m', 'i', 'n', 'f'), // MediaInformationAtomType + BOX_moov = FOUR_CHAR_INT('m', 'o', 'o', 'v'), // MovieAtomType + BOX_mvhd = FOUR_CHAR_INT('m', 'v', 'h', 'd'), // MovieHeaderAtomType + BOX_stsd = FOUR_CHAR_INT('s', 't', 's', 'd'), // SampleDescriptionAtomType + BOX_stsz = FOUR_CHAR_INT('s', 't', 's', 'z'), // SampleSizeAtomType + BOX_stz2 = FOUR_CHAR_INT('s', 't', 'z', '2'), // CompactSampleSizeAtomType + BOX_stbl = FOUR_CHAR_INT('s', 't', 'b', 'l'), // SampleTableAtomType + BOX_stsc = FOUR_CHAR_INT('s', 't', 's', 'c'), // SampleToChunkAtomType + BOX_stsh = FOUR_CHAR_INT('s', 't', 's', 'h'), // ShadowSyncAtomType + BOX_skip = FOUR_CHAR_INT('s', 'k', 'i', 'p'), // SkipAtomType + BOX_smhd = FOUR_CHAR_INT('s', 'm', 'h', 'd'), // SoundMediaHeaderAtomType + BOX_stss = FOUR_CHAR_INT('s', 't', 's', 's'), // SyncSampleAtomType + BOX_stts = FOUR_CHAR_INT('s', 't', 't', 's'), // TimeToSampleAtomType + BOX_trak = FOUR_CHAR_INT('t', 'r', 'a', 'k'), // TrackAtomType + BOX_tkhd = FOUR_CHAR_INT('t', 'k', 'h', 'd'), // TrackHeaderAtomType + BOX_tref = FOUR_CHAR_INT('t', 'r', 'e', 'f'), // TrackReferenceAtomType + BOX_udta = FOUR_CHAR_INT('u', 'd', 't', 'a'), // UserDataAtomType + BOX_vmhd = FOUR_CHAR_INT('v', 'm', 'h', 'd'), // VideoMediaHeaderAtomType + BOX_url = FOUR_CHAR_INT('u', 'r', 'l', ' '), + BOX_urn = FOUR_CHAR_INT('u', 'r', 'n', ' '), + + BOX_gnrv = FOUR_CHAR_INT('g', 'n', 'r', 'v'), // GenericVisualSampleEntryAtomType + BOX_gnra = FOUR_CHAR_INT('g', 'n', 'r', 'a'), // GenericAudioSampleEntryAtomType + + // V2 atoms + BOX_ftyp = FOUR_CHAR_INT('f', 't', 'y', 'p'), // FileTypeAtomType + BOX_padb = FOUR_CHAR_INT('p', 'a', 'd', 'b'), // PaddingBitsAtomType + + // MP4 Atoms + BOX_sdhd = FOUR_CHAR_INT('s', 'd', 'h', 'd'), // SceneDescriptionMediaHeaderAtomType + BOX_dpnd = FOUR_CHAR_INT('d', 'p', 'n', 'd'), // StreamDependenceAtomType + BOX_iods = FOUR_CHAR_INT('i', 'o', 'd', 's'), // ObjectDescriptorAtomType + BOX_odhd = FOUR_CHAR_INT('o', 'd', 'h', 'd'), // ObjectDescriptorMediaHeaderAtomType + BOX_mpod = FOUR_CHAR_INT('m', 'p', 'o', 'd'), // ODTrackReferenceAtomType + BOX_nmhd = FOUR_CHAR_INT('n', 'm', 'h', 'd'), // MPEGMediaHeaderAtomType + BOX_esds = FOUR_CHAR_INT('e', 's', 'd', 's'), // ESDAtomType + BOX_sync = FOUR_CHAR_INT('s', 'y', 'n', 'c'), // OCRReferenceAtomType + BOX_ipir = FOUR_CHAR_INT('i', 'p', 'i', 'r'), // IPIReferenceAtomType + BOX_mp4s = FOUR_CHAR_INT('m', 'p', '4', 's'), // MPEGSampleEntryAtomType + BOX_mp4a = FOUR_CHAR_INT('m', 'p', '4', 'a'), // MPEGAudioSampleEntryAtomType + BOX_mp4v = FOUR_CHAR_INT('m', 'p', '4', 'v'), // MPEGVisualSampleEntryAtomType // http://www.itscj.ipsj.or.jp/sc29/open/29view/29n7644t.doc BOX_avc1 = FOUR_CHAR_INT('a', 'v', 'c', '1'), @@ -563,111 +583,118 @@ enum { BOX_hvc1 = FOUR_CHAR_INT('h', 'v', 'c', '1'), BOX_hvcC = FOUR_CHAR_INT('h', 'v', 'c', 'C'), - //3GPP atoms - BOX_samr = FOUR_CHAR_INT('s', 'a', 'm', 'r'),//AMRSampleEntryAtomType - BOX_sawb = FOUR_CHAR_INT('s', 'a', 'w', 'b'),//WB_AMRSampleEntryAtomType - BOX_damr = FOUR_CHAR_INT('d', 'a', 'm', 'r'),//AMRConfigAtomType - BOX_s263 = FOUR_CHAR_INT('s', '2', '6', '3'),//H263SampleEntryAtomType - BOX_d263 = FOUR_CHAR_INT('d', '2', '6', '3'),//H263ConfigAtomType - - //V2 atoms - Movie Fragments - BOX_mvex = FOUR_CHAR_INT('m', 'v', 'e', 'x'),//MovieExtendsAtomType - BOX_trex = FOUR_CHAR_INT('t', 'r', 'e', 'x'),//TrackExtendsAtomType - BOX_moof = FOUR_CHAR_INT('m', 'o', 'o', 'f'),//MovieFragmentAtomType - BOX_mfhd = FOUR_CHAR_INT('m', 'f', 'h', 'd'),//MovieFragmentHeaderAtomType - BOX_traf = FOUR_CHAR_INT('t', 'r', 'a', 'f'),//TrackFragmentAtomType - BOX_tfhd = FOUR_CHAR_INT('t', 'f', 'h', 'd'),//TrackFragmentHeaderAtomType - BOX_tfdt = FOUR_CHAR_INT('t', 'f', 'd', 't'),//TrackFragmentBaseMediaDecodeTimeBox - BOX_trun = FOUR_CHAR_INT('t', 'r', 'u', 'n'),//TrackFragmentRunAtomType - BOX_mehd = FOUR_CHAR_INT('m', 'e', 'h', 'd'),//MovieExtendsHeaderBox + // 3GPP atoms + BOX_samr = FOUR_CHAR_INT('s', 'a', 'm', 'r'), // AMRSampleEntryAtomType + BOX_sawb = FOUR_CHAR_INT('s', 'a', 'w', 'b'), // WB_AMRSampleEntryAtomType + BOX_damr = FOUR_CHAR_INT('d', 'a', 'm', 'r'), // AMRConfigAtomType + BOX_s263 = FOUR_CHAR_INT('s', '2', '6', '3'), // H263SampleEntryAtomType + BOX_d263 = FOUR_CHAR_INT('d', '2', '6', '3'), // H263ConfigAtomType + + // V2 atoms - Movie Fragments + BOX_mvex = FOUR_CHAR_INT('m', 'v', 'e', 'x'), // MovieExtendsAtomType + BOX_trex = FOUR_CHAR_INT('t', 'r', 'e', 'x'), // TrackExtendsAtomType + BOX_moof = FOUR_CHAR_INT('m', 'o', 'o', 'f'), // MovieFragmentAtomType + BOX_mfhd = FOUR_CHAR_INT('m', 'f', 'h', 'd'), // MovieFragmentHeaderAtomType + BOX_traf = FOUR_CHAR_INT('t', 'r', 'a', 'f'), // TrackFragmentAtomType + BOX_tfhd = FOUR_CHAR_INT('t', 'f', 'h', 'd'), // TrackFragmentHeaderAtomType + BOX_tfdt = FOUR_CHAR_INT('t', 'f', 'd', 't'), // TrackFragmentBaseMediaDecodeTimeBox + BOX_trun = FOUR_CHAR_INT('t', 'r', 'u', 'n'), // TrackFragmentRunAtomType + BOX_mehd = FOUR_CHAR_INT('m', 'e', 'h', 'd'), // MovieExtendsHeaderBox // Object Descriptors (OD) data coding // These takes only 1 byte; this implementation translate to // + OD_BASE to keep API uniform and safe for string functions - OD_BASE = FOUR_CHAR_INT('$', '$', '$', '0'),// - OD_ESD = FOUR_CHAR_INT('$', '$', '$', '3'),//SDescriptor_Tag - OD_DCD = FOUR_CHAR_INT('$', '$', '$', '4'),//DecoderConfigDescriptor_Tag - OD_DSI = FOUR_CHAR_INT('$', '$', '$', '5'),//DecoderSpecificInfo_Tag - OD_SLC = FOUR_CHAR_INT('$', '$', '$', '6'),//SLConfigDescriptor_Tag + OD_BASE = FOUR_CHAR_INT('$', '$', '$', '0'), // + OD_ESD = FOUR_CHAR_INT('$', '$', '$', '3'), // SDescriptor_Tag + OD_DCD = FOUR_CHAR_INT('$', '$', '$', '4'), // DecoderConfigDescriptor_Tag + OD_DSI = FOUR_CHAR_INT('$', '$', '$', '5'), // DecoderSpecificInfo_Tag + OD_SLC = FOUR_CHAR_INT('$', '$', '$', '6'), // SLConfigDescriptor_Tag BOX_meta = FOUR_CHAR_INT('m', 'e', 't', 'a'), BOX_ilst = FOUR_CHAR_INT('i', 'l', 's', 't'), // Metagata tags, see http://atomicparsley.sourceforge.net/mpeg-4files.html - BOX_calb = FOUR_CHAR_INT('\xa9', 'a', 'l', 'b'), // album - BOX_cart = FOUR_CHAR_INT('\xa9', 'a', 'r', 't'), // artist - BOX_aART = FOUR_CHAR_INT('a', 'A', 'R', 'T'), // album artist - BOX_ccmt = FOUR_CHAR_INT('\xa9', 'c', 'm', 't'), // comment - BOX_cday = FOUR_CHAR_INT('\xa9', 'd', 'a', 'y'), // year (as string) - BOX_cnam = FOUR_CHAR_INT('\xa9', 'n', 'a', 'm'), // title - BOX_cgen = FOUR_CHAR_INT('\xa9', 'g', 'e', 'n'), // custom genre (as string or as byte!) - BOX_trkn = FOUR_CHAR_INT('t', 'r', 'k', 'n'), // track number (byte) - BOX_disk = FOUR_CHAR_INT('d', 'i', 's', 'k'), // disk number (byte) - BOX_cwrt = FOUR_CHAR_INT('\xa9', 'w', 'r', 't'), // composer - BOX_ctoo = FOUR_CHAR_INT('\xa9', 't', 'o', 'o'), // encoder - BOX_tmpo = FOUR_CHAR_INT('t', 'm', 'p', 'o'), // bpm (byte) - BOX_cpil = FOUR_CHAR_INT('c', 'p', 'i', 'l'), // compilation (byte) - BOX_covr = FOUR_CHAR_INT('c', 'o', 'v', 'r'), // cover art (JPEG/PNG) - BOX_rtng = FOUR_CHAR_INT('r', 't', 'n', 'g'), // rating/advisory (byte) - BOX_cgrp = FOUR_CHAR_INT('\xa9', 'g', 'r', 'p'), // grouping - BOX_stik = FOUR_CHAR_INT('s', 't', 'i', - 'k'), // stik (byte) 0 = Movie 1 = Normal 2 = Audiobook 5 = Whacked Bookmark 6 = Music Video 9 = Short Film 10 = TV Show 11 = Booklet 14 = Ringtone - BOX_pcst = FOUR_CHAR_INT('p', 'c', 's', 't'), // podcast (byte) - BOX_catg = FOUR_CHAR_INT('c', 'a', 't', 'g'), // category - BOX_keyw = FOUR_CHAR_INT('k', 'e', 'y', 'w'), // keyword - BOX_purl = FOUR_CHAR_INT('p', 'u', 'r', 'l'), // podcast URL (byte) - BOX_egid = FOUR_CHAR_INT('e', 'g', 'i', 'd'), // episode global unique ID (byte) - BOX_desc = FOUR_CHAR_INT('d', 'e', 's', 'c'), // description - BOX_clyr = FOUR_CHAR_INT('\xa9', 'l', 'y', 'r'), // lyrics (may be > 255 bytes) - BOX_tven = FOUR_CHAR_INT('t', 'v', 'e', 'n'), // tv episode number - BOX_tves = FOUR_CHAR_INT('t', 'v', 'e', 's'), // tv episode (byte) - BOX_tvnn = FOUR_CHAR_INT('t', 'v', 'n', 'n'), // tv network name - BOX_tvsh = FOUR_CHAR_INT('t', 'v', 's', 'h'), // tv show name - BOX_tvsn = FOUR_CHAR_INT('t', 'v', 's', 'n'), // tv season (byte) - BOX_purd = FOUR_CHAR_INT('p', 'u', 'r', 'd'), // purchase date - BOX_pgap = FOUR_CHAR_INT('p', 'g', 'a', 'p'), // Gapless Playback (byte) - - //BOX_aart = FOUR_CHAR_INT( 'a', 'a', 'r', 't' ), // Album artist - BOX_cART = FOUR_CHAR_INT('\xa9', 'A', 'R', 'T'), // artist + BOX_calb = FOUR_CHAR_INT('\xa9', 'a', 'l', 'b'), // album + BOX_cart = FOUR_CHAR_INT('\xa9', 'a', 'r', 't'), // artist + BOX_aART = FOUR_CHAR_INT('a', 'A', 'R', 'T'), // album artist + BOX_ccmt = FOUR_CHAR_INT('\xa9', 'c', 'm', 't'), // comment + BOX_cday = FOUR_CHAR_INT('\xa9', 'd', 'a', 'y'), // year (as string) + BOX_cnam = FOUR_CHAR_INT('\xa9', 'n', 'a', 'm'), // title + BOX_cgen = FOUR_CHAR_INT('\xa9', 'g', 'e', 'n'), // custom genre (as string or as byte!) + BOX_trkn = FOUR_CHAR_INT('t', 'r', 'k', 'n'), // track number (byte) + BOX_disk = FOUR_CHAR_INT('d', 'i', 's', 'k'), // disk number (byte) + BOX_cwrt = FOUR_CHAR_INT('\xa9', 'w', 'r', 't'), // composer + BOX_ctoo = FOUR_CHAR_INT('\xa9', 't', 'o', 'o'), // encoder + BOX_tmpo = FOUR_CHAR_INT('t', 'm', 'p', 'o'), // bpm (byte) + BOX_cpil = FOUR_CHAR_INT('c', 'p', 'i', 'l'), // compilation (byte) + BOX_covr = FOUR_CHAR_INT('c', 'o', 'v', 'r'), // cover art (JPEG/PNG) + BOX_rtng = FOUR_CHAR_INT('r', 't', 'n', 'g'), // rating/advisory (byte) + BOX_cgrp = FOUR_CHAR_INT('\xa9', 'g', 'r', 'p'), // grouping + BOX_stik = FOUR_CHAR_INT( + 's', + 't', + 'i', + 'k'), // stik (byte) 0 = Movie 1 = Normal 2 = Audiobook 5 = Whacked Bookmark 6 = Music Video 9 = Short + // Film 10 = TV Show 11 = Booklet 14 = Ringtone + BOX_pcst = FOUR_CHAR_INT('p', 'c', 's', 't'), // podcast (byte) + BOX_catg = FOUR_CHAR_INT('c', 'a', 't', 'g'), // category + BOX_keyw = FOUR_CHAR_INT('k', 'e', 'y', 'w'), // keyword + BOX_purl = FOUR_CHAR_INT('p', 'u', 'r', 'l'), // podcast URL (byte) + BOX_egid = FOUR_CHAR_INT('e', 'g', 'i', 'd'), // episode global unique ID (byte) + BOX_desc = FOUR_CHAR_INT('d', 'e', 's', 'c'), // description + BOX_clyr = FOUR_CHAR_INT('\xa9', 'l', 'y', 'r'), // lyrics (may be > 255 bytes) + BOX_tven = FOUR_CHAR_INT('t', 'v', 'e', 'n'), // tv episode number + BOX_tves = FOUR_CHAR_INT('t', 'v', 'e', 's'), // tv episode (byte) + BOX_tvnn = FOUR_CHAR_INT('t', 'v', 'n', 'n'), // tv network name + BOX_tvsh = FOUR_CHAR_INT('t', 'v', 's', 'h'), // tv show name + BOX_tvsn = FOUR_CHAR_INT('t', 'v', 's', 'n'), // tv season (byte) + BOX_purd = FOUR_CHAR_INT('p', 'u', 'r', 'd'), // purchase date + BOX_pgap = FOUR_CHAR_INT('p', 'g', 'a', 'p'), // Gapless Playback (byte) + + // BOX_aart = FOUR_CHAR_INT( 'a', 'a', 'r', 't' ), // Album artist + BOX_cART = FOUR_CHAR_INT('\xa9', 'A', 'R', 'T'), // artist BOX_gnre = FOUR_CHAR_INT('g', 'n', 'r', 'e'), // 3GPP metatags (http://cpansearch.perl.org/src/JHAR/MP4-Info-1.12/Info.pm) - BOX_auth = FOUR_CHAR_INT('a', 'u', 't', 'h'), // author - BOX_titl = FOUR_CHAR_INT('t', 'i', 't', 'l'), // title - BOX_dscp = FOUR_CHAR_INT('d', 's', 'c', 'p'), // description - BOX_perf = FOUR_CHAR_INT('p', 'e', 'r', 'f'), // performer - BOX_mean = FOUR_CHAR_INT('m', 'e', 'a', 'n'), // - BOX_name = FOUR_CHAR_INT('n', 'a', 'm', 'e'), // - BOX_data = FOUR_CHAR_INT('d', 'a', 't', 'a'), // + BOX_auth = FOUR_CHAR_INT('a', 'u', 't', 'h'), // author + BOX_titl = FOUR_CHAR_INT('t', 'i', 't', 'l'), // title + BOX_dscp = FOUR_CHAR_INT('d', 's', 'c', 'p'), // description + BOX_perf = FOUR_CHAR_INT('p', 'e', 'r', 'f'), // performer + BOX_mean = FOUR_CHAR_INT('m', 'e', 'a', 'n'), // + BOX_name = FOUR_CHAR_INT('n', 'a', 'm', 'e'), // + BOX_data = FOUR_CHAR_INT('d', 'a', 't', 'a'), // // these from http://lists.mplayerhq.hu/pipermail/ffmpeg-devel/2008-September/053151.html - BOX_albm = FOUR_CHAR_INT('a', 'l', 'b', 'm'), // album - BOX_yrrc = FOUR_CHAR_INT('y', 'r', 'r', 'c') // album + BOX_albm = FOUR_CHAR_INT('a', 'l', 'b', 'm'), // album + BOX_yrrc = FOUR_CHAR_INT('y', 'r', 'r', 'c') // album }; // Video track : 'vide' -#define MP4E_HANDLER_TYPE_VIDE 0x76696465 +#define MP4E_HANDLER_TYPE_VIDE 0x76696465 // Audio track : 'soun' -#define MP4E_HANDLER_TYPE_SOUN 0x736F756E +#define MP4E_HANDLER_TYPE_SOUN 0x736F756E // General MPEG-4 systems streams (without specific handler). // Used for private stream, as suggested in http://www.mp4ra.org/handler.html -#define MP4E_HANDLER_TYPE_GESM 0x6765736D +#define MP4E_HANDLER_TYPE_GESM 0x6765736D -typedef struct { +typedef struct +{ boxsize_t size; boxsize_t offset; - unsigned duration; - unsigned flag_random_access; + unsigned duration; + unsigned flag_random_access; } sample_t; -typedef struct { - unsigned char *data; - int bytes; - int capacity; +typedef struct +{ + unsigned char* data; + int bytes; + int capacity; } minimp4_vector_t; -typedef struct { - MP4E_track_t info; +typedef struct +{ + MP4E_track_t info; minimp4_vector_t smpl; // sample descriptor minimp4_vector_t pending_sample; @@ -677,88 +704,109 @@ typedef struct { } track_t; -typedef struct MP4E_mux_tag { +typedef struct MP4E_mux_tag +{ minimp4_vector_t tracks; int64_t write_pos; - int (*write_callback)(int64_t offset, const void *buffer, size_t size, void *token); + int (*write_callback)(int64_t offset, const void* buffer, size_t size, void* token); - void *token; - char *text_comment; + void* token; + char* text_comment; int sequential_mode_flag; - int enable_fragmentation; // flag, indicating streaming-friendly 'fragmentation' mode - int fragments_count; // # of fragments in 'fragmentation' mode + int enable_fragmentation; // flag, indicating streaming-friendly 'fragmentation' mode + int fragments_count; // # of fragments in 'fragmentation' mode } MP4E_mux_t; static const unsigned char box_ftyp[] = { #if 1 - 0, 0, 0, 0x18, 'f', 't', 'y', 'p', - 'm', 'p', '4', '2', 0, 0, 0, 0, - 'm', 'p', '4', '2', 'i', 's', 'o', 'm', + 0, 0, 0, 0x18, 'f', 't', 'y', 'p', 'm', 'p', '4', '2', 0, 0, 0, 0, 'm', 'p', '4', '2', 'i', 's', 'o', 'm', #else - // as in ffmpeg - 0,0,0,0x20,'f','t','y','p', - 'i','s','o','m',0,0,2,0, - 'm','p','4','1','i','s','o','m', - 'i','s','o','2','a','v','c','1', + // as in ffmpeg + 0, 0, 0, 0x20, 'f', 't', 'y', 'p', 'i', 's', 'o', 'm', 0, 0, 2, 0, + 'm', 'p', '4', '1', 'i', 's', 'o', 'm', 'i', 's', 'o', '2', 'a', 'v', 'c', '1', #endif }; /** -* Endian-independent byte-write macros -*/ -#define WR(x, n) *p++ = (unsigned char)((x) >> 8*n) + * Endian-independent byte-write macros + */ +#define WR(x, n) *p++ = (unsigned char) ((x) >> 8 * n) #define WRITE_1(x) WR(x, 0); -#define WRITE_2(x) WR(x, 1); WR(x, 0); -#define WRITE_3(x) WR(x, 2); WR(x, 1); WR(x, 0); -#define WRITE_4(x) WR(x, 3); WR(x, 2); WR(x, 1); WR(x, 0); -#define WR4(p, x) (p)[0] = (char)((x) >> 8*3); (p)[1] = (char)((x) >> 8*2); (p)[2] = (char)((x) >> 8*1); (p)[3] = (char)((x)); +#define WRITE_2(x) \ + WR(x, 1); \ + WR(x, 0); +#define WRITE_3(x) \ + WR(x, 2); \ + WR(x, 1); \ + WR(x, 0); +#define WRITE_4(x) \ + WR(x, 3); \ + WR(x, 2); \ + WR(x, 1); \ + WR(x, 0); +#define WR4(p, x) \ + (p)[0] = (char) ((x) >> 8 * 3); \ + (p)[1] = (char) ((x) >> 8 * 2); \ + (p)[2] = (char) ((x) >> 8 * 1); \ + (p)[3] = (char) ((x)); // Finish atom: update atom size field -#define END_ATOM --stack; WR4((unsigned char*)*stack, p - *stack); +#define END_ATOM \ + --stack; \ + WR4((unsigned char*) *stack, p - *stack); // Initiate atom: save position of size field on stack -#define ATOM(x) *stack++ = p; p += 4; WRITE_4(x); +#define ATOM(x) \ + *stack++ = p; \ + p += 4; \ + WRITE_4(x); // Atom with 'FullAtomVersionFlags' field -#define ATOM_FULL(x, flag) ATOM(x); WRITE_4(flag); - -#define ERR(func) { int err = func; if (err) return err; } +#define ATOM_FULL(x, flag) \ + ATOM(x); \ + WRITE_4(flag); + +#define ERR(func) \ + { \ + int err = func; \ + if (err) return err; \ + } /** Allocate vector with given size, return 1 on success, 0 on fail */ -static int minimp4_vector_init(minimp4_vector_t *h, int capacity) { - h->bytes = 0; +static int minimp4_vector_init(minimp4_vector_t* h, int capacity) +{ + h->bytes = 0; h->capacity = capacity; - h->data = capacity ? (unsigned char *) malloc(capacity) : NULL; + h->data = capacity ? (unsigned char*) malloc(capacity) : NULL; return !capacity || !!h->data; } /** Deallocates vector memory */ -static void minimp4_vector_reset(minimp4_vector_t *h) { - if (h->data) - free(h->data); +static void minimp4_vector_reset(minimp4_vector_t* h) +{ + if (h->data) free(h->data); memset(h, 0, sizeof(minimp4_vector_t)); } /** Reallocate vector memory to the given size */ -static int minimp4_vector_grow(minimp4_vector_t *h, int bytes) { - void *p; - int new_size = h->capacity * 2 + 1024; - if (new_size < h->capacity + bytes) - new_size = h->capacity + bytes + 1024; +static int minimp4_vector_grow(minimp4_vector_t* h, int bytes) +{ + void* p; + int new_size = h->capacity * 2 + 1024; + if (new_size < h->capacity + bytes) new_size = h->capacity + bytes + 1024; p = realloc(h->data, new_size); - if (!p) - return 0; - h->data = (unsigned char *) p; + if (!p) return 0; + h->data = (unsigned char*) p; h->capacity = new_size; return 1; } @@ -768,12 +816,11 @@ static int minimp4_vector_grow(minimp4_vector_t *h, int bytes) { vector memory if necessary. Return allocated memory. */ -static unsigned char *minimp4_vector_alloc_tail(minimp4_vector_t *h, int bytes) { - unsigned char *p; - if (!h->data && !minimp4_vector_init(h, 2 * bytes + 1024)) - return NULL; - if ((h->capacity - h->bytes) < bytes && !minimp4_vector_grow(h, bytes)) - return NULL; +static unsigned char* minimp4_vector_alloc_tail(minimp4_vector_t* h, int bytes) +{ + unsigned char* p; + if (!h->data && !minimp4_vector_init(h, 2 * bytes + 1024)) return NULL; + if ((h->capacity - h->bytes) < bytes && !minimp4_vector_grow(h, bytes)) return NULL; assert(h->data); assert((h->capacity - h->bytes) >= bytes); p = h->data + h->bytes; @@ -784,83 +831,89 @@ static unsigned char *minimp4_vector_alloc_tail(minimp4_vector_t *h, int bytes) /** Append data to the end of the vector (accumulate ot enqueue) */ -static unsigned char *minimp4_vector_put(minimp4_vector_t *h, const void *buf, int bytes) { - unsigned char *tail = minimp4_vector_alloc_tail(h, bytes); - if (tail) - memcpy(tail, buf, bytes); +static unsigned char* minimp4_vector_put(minimp4_vector_t* h, const void* buf, int bytes) +{ + unsigned char* tail = minimp4_vector_alloc_tail(h, bytes); + if (tail) memcpy(tail, buf, bytes); return tail; } /** -* Allocates and initialize mp4 multiplexer -* return multiplexor handle on success; NULL on failure -*/ -MP4E_mux_t *MP4E_open(int sequential_mode_flag, int enable_fragmentation, void *token, - int (*write_callback)(int64_t offset, const void *buffer, size_t size, - void *token)) { - if (write_callback(0, box_ftyp, sizeof(box_ftyp), token)) // Write fixed header: 'ftyp' box + * Allocates and initialize mp4 multiplexer + * return multiplexor handle on success; NULL on failure + */ +MP4E_mux_t* MP4E_open( + int sequential_mode_flag, + int enable_fragmentation, + void* token, + int (*write_callback)(int64_t offset, const void* buffer, size_t size, void* token)) +{ + if (write_callback(0, box_ftyp, sizeof(box_ftyp), token)) // Write fixed header: 'ftyp' box return 0; - MP4E_mux_t *mux = (MP4E_mux_t *) malloc(sizeof(MP4E_mux_t)); - if (!mux) - return mux; + MP4E_mux_t* mux = (MP4E_mux_t*) malloc(sizeof(MP4E_mux_t)); + if (!mux) return mux; mux->sequential_mode_flag = sequential_mode_flag || enable_fragmentation; mux->enable_fragmentation = enable_fragmentation; - mux->fragments_count = 0; - mux->write_callback = write_callback; - mux->token = token; - mux->text_comment = NULL; - mux->write_pos = sizeof(box_ftyp); - - if (!mux->sequential_mode_flag) { // Write filler, which would be updated later - if (mux->write_callback(mux->write_pos, box_ftyp, 8, mux->token)) { + mux->fragments_count = 0; + mux->write_callback = write_callback; + mux->token = token; + mux->text_comment = NULL; + mux->write_pos = sizeof(box_ftyp); + + if (!mux->sequential_mode_flag) + { // Write filler, which would be updated later + if (mux->write_callback(mux->write_pos, box_ftyp, 8, mux->token)) + { free(mux); return 0; } - mux->write_pos += 16; // box_ftyp + box_free for 32bit or 64bit size encoding + mux->write_pos += 16; // box_ftyp + box_free for 32bit or 64bit size encoding } minimp4_vector_init(&mux->tracks, 2 * sizeof(track_t)); return mux; } /** -* Add new track -*/ -int MP4E_add_track(MP4E_mux_t *mux, const MP4E_track_t *track_data) { - track_t *tr; - int ntr = mux->tracks.bytes / sizeof(track_t); + * Add new track + */ +int MP4E_add_track(MP4E_mux_t* mux, const MP4E_track_t* track_data) +{ + track_t* tr; + int ntr = mux->tracks.bytes / sizeof(track_t); - if (!mux || !track_data) - return MP4E_STATUS_BAD_ARGUMENTS; + if (!mux || !track_data) return MP4E_STATUS_BAD_ARGUMENTS; - tr = (track_t *) minimp4_vector_alloc_tail(&mux->tracks, sizeof(track_t)); - if (!tr) - return MP4E_STATUS_NO_MEMORY; + tr = (track_t*) minimp4_vector_alloc_tail(&mux->tracks, sizeof(track_t)); + if (!tr) return MP4E_STATUS_NO_MEMORY; memset(tr, 0, sizeof(track_t)); memcpy(&tr->info, track_data, sizeof(*track_data)); - if (!minimp4_vector_init(&tr->smpl, 256)) - return MP4E_STATUS_NO_MEMORY; + if (!minimp4_vector_init(&tr->smpl, 256)) return MP4E_STATUS_NO_MEMORY; minimp4_vector_init(&tr->vsps, 0); minimp4_vector_init(&tr->vpps, 0); minimp4_vector_init(&tr->pending_sample, 0); return ntr; } -static const unsigned char *next_dsi(const unsigned char *p, const unsigned char *end, int *bytes) { - if (p < end + 2) { +static const unsigned char* next_dsi(const unsigned char* p, const unsigned char* end, int* bytes) +{ + if (p < end + 2) + { *bytes = p[0] * 256 + p[1]; return p + 2; - } else + } + else return NULL; } -static int append_mem(minimp4_vector_t *v, const void *mem, int bytes) { - int i; - unsigned char size[2]; - const unsigned char *p = v->data; - for (i = 0; i + 2 < v->bytes;) { +static int append_mem(minimp4_vector_t* v, const void* mem, int bytes) +{ + int i; + unsigned char size[2]; + const unsigned char* p = v->data; + for (i = 0; i + 2 < v->bytes;) + { int cb = p[i] * 256 + p[i + 1]; - if (cb == bytes && !memcmp(p + i + 2, mem, cb)) - return 1; + if (cb == bytes && !memcmp(p + i + 2, mem, cb)) return 1; i += 2 + cb; } size[0] = bytes >> 8; @@ -868,10 +921,12 @@ static int append_mem(minimp4_vector_t *v, const void *mem, int bytes) { return minimp4_vector_put(v, size, 2) && minimp4_vector_put(v, mem, bytes); } -static int items_count(minimp4_vector_t *v) { - int i, count = 0; - const unsigned char *p = v->data; - for (i = 0; i + 2 < v->bytes;) { +static int items_count(minimp4_vector_t* v) +{ + int i, count = 0; + const unsigned char* p = v->data; + for (i = 0; i + 2 < v->bytes;) + { int cb = p[i] * 256 + p[i + 1]; count++; i += 2 + cb; @@ -879,66 +934,71 @@ static int items_count(minimp4_vector_t *v) { return count; } -int MP4E_set_dsi(MP4E_mux_t *mux, int track_id, const void *dsi, int bytes) { - track_t *tr = ((track_t *) mux->tracks.data) + track_id; - assert(tr->info.track_media_kind == e_audio || - tr->info.track_media_kind == e_private); - if (tr->vsps.bytes) - return MP4E_STATUS_ONLY_ONE_DSI_ALLOWED; // only one DSI allowed +int MP4E_set_dsi(MP4E_mux_t* mux, int track_id, const void* dsi, int bytes) +{ + track_t* tr = ((track_t*) mux->tracks.data) + track_id; + assert(tr->info.track_media_kind == e_audio || tr->info.track_media_kind == e_private); + if (tr->vsps.bytes) return MP4E_STATUS_ONLY_ONE_DSI_ALLOWED; // only one DSI allowed return append_mem(&tr->vsps, dsi, bytes) ? MP4E_STATUS_OK : MP4E_STATUS_NO_MEMORY; } -int MP4E_set_vps(MP4E_mux_t *mux, int track_id, const void *vps, int bytes) { - track_t *tr = ((track_t *) mux->tracks.data) + track_id; +int MP4E_set_vps(MP4E_mux_t* mux, int track_id, const void* vps, int bytes) +{ + track_t* tr = ((track_t*) mux->tracks.data) + track_id; assert(tr->info.track_media_kind == e_video); return append_mem(&tr->vvps, vps, bytes) ? MP4E_STATUS_OK : MP4E_STATUS_NO_MEMORY; } -int MP4E_set_sps(MP4E_mux_t *mux, int track_id, const void *sps, int bytes) { - track_t *tr = ((track_t *) mux->tracks.data) + track_id; +int MP4E_set_sps(MP4E_mux_t* mux, int track_id, const void* sps, int bytes) +{ + track_t* tr = ((track_t*) mux->tracks.data) + track_id; assert(tr->info.track_media_kind == e_video); return append_mem(&tr->vsps, sps, bytes) ? MP4E_STATUS_OK : MP4E_STATUS_NO_MEMORY; } -int MP4E_set_pps(MP4E_mux_t *mux, int track_id, const void *pps, int bytes) { - track_t *tr = ((track_t *) mux->tracks.data) + track_id; +int MP4E_set_pps(MP4E_mux_t* mux, int track_id, const void* pps, int bytes) +{ + track_t* tr = ((track_t*) mux->tracks.data) + track_id; assert(tr->info.track_media_kind == e_video); return append_mem(&tr->vpps, pps, bytes) ? MP4E_STATUS_OK : MP4E_STATUS_NO_MEMORY; } -static unsigned get_duration(const track_t *tr) { - unsigned i, sum_duration = 0; - const sample_t *s = (const sample_t *) tr->smpl.data; - for (i = 0; i < tr->smpl.bytes / sizeof(sample_t); i++) { +static unsigned get_duration(const track_t* tr) +{ + unsigned i, sum_duration = 0; + const sample_t* s = (const sample_t*) tr->smpl.data; + for (i = 0; i < tr->smpl.bytes / sizeof(sample_t); i++) + { sum_duration += s[i].duration; } return sum_duration; } -static int write_pending_data(MP4E_mux_t *mux, track_t *tr) { +static int write_pending_data(MP4E_mux_t* mux, track_t* tr) +{ // if have pending sample && have at least one sample in the index - if (tr->pending_sample.bytes > 0 && tr->smpl.bytes >= sizeof(sample_t)) { + if (tr->pending_sample.bytes > 0 && tr->smpl.bytes >= sizeof(sample_t)) + { // Complete pending sample - sample_t *smpl_desc; + sample_t* smpl_desc; unsigned char base[8], *p = base; assert(mux->sequential_mode_flag); // Write each sample to a separate atom - assert(mux->sequential_mode_flag); // Separate atom needed for sequential_mode only + assert(mux->sequential_mode_flag); // Separate atom needed for sequential_mode only WRITE_4(tr->pending_sample.bytes + 8); WRITE_4(BOX_mdat); ERR(mux->write_callback(mux->write_pos, base, p - base, mux->token)); mux->write_pos += p - base; // Update sample descriptor with size and offset - smpl_desc = ((sample_t *) minimp4_vector_alloc_tail(&tr->smpl, 0)) - 1; - smpl_desc->size = tr->pending_sample.bytes; + smpl_desc = ((sample_t*) minimp4_vector_alloc_tail(&tr->smpl, 0)) - 1; + smpl_desc->size = tr->pending_sample.bytes; smpl_desc->offset = (boxsize_t) mux->write_pos; // Write data - ERR(mux->write_callback(mux->write_pos, tr->pending_sample.data, tr->pending_sample.bytes, - mux->token)); + ERR(mux->write_callback(mux->write_pos, tr->pending_sample.data, tr->pending_sample.bytes, mux->token)); mux->write_pos += tr->pending_sample.bytes; // reset buffer @@ -947,38 +1007,45 @@ static int write_pending_data(MP4E_mux_t *mux, track_t *tr) { return MP4E_STATUS_OK; } -static int -add_sample_descriptor(MP4E_mux_t *mux, track_t *tr, int data_bytes, int duration, int kind) { +static int add_sample_descriptor(MP4E_mux_t* mux, track_t* tr, int data_bytes, int duration, int kind) +{ sample_t smp; - smp.size = data_bytes; - smp.offset = (boxsize_t) mux->write_pos; - smp.duration = (duration ? duration : tr->info.default_duration); + smp.size = data_bytes; + smp.offset = (boxsize_t) mux->write_pos; + smp.duration = (duration ? duration : tr->info.default_duration); smp.flag_random_access = (kind == MP4E_SAMPLE_RANDOM_ACCESS); return NULL != minimp4_vector_put(&tr->smpl, &smp, sizeof(sample_t)); } -static int mp4e_flush_index(MP4E_mux_t *mux); +static int mp4e_flush_index(MP4E_mux_t* mux); /** -* Write Movie Fragment: 'moof' box -*/ -static int -mp4e_write_fragment_header(MP4E_mux_t *mux, int track_num, int data_bytes, int duration, int kind + * Write Movie Fragment: 'moof' box + */ +static int mp4e_write_fragment_header( + MP4E_mux_t* mux, + int track_num, + int data_bytes, + int duration, + int kind #if MP4D_TFDT_SUPPORT - , uint64_t timestamp + , + uint64_t timestamp #endif -) { - unsigned char base[888], *p = base; - unsigned char *stack_base[20]; // atoms nesting stack - unsigned char **stack = stack_base; - unsigned char *pdata_offset; - unsigned flags; - enum { +) +{ + unsigned char base[888], *p = base; + unsigned char* stack_base[20]; // atoms nesting stack + unsigned char** stack = stack_base; + unsigned char* pdata_offset; + unsigned flags; + enum + { default_sample_duration_present = 0x000008, - default_sample_flags_present = 0x000020, + default_sample_flags_present = 0x000020, } e; - track_t *tr = ((track_t *) mux->tracks.data) + track_num; + track_t* tr = ((track_t*) mux->tracks.data) + track_num; ATOM(BOX_moof) ATOM_FULL(BOX_mfhd, 0) @@ -987,60 +1054,68 @@ mp4e_write_fragment_header(MP4E_mux_t *mux, int track_num, int data_bytes, int d ATOM(BOX_traf) flags = 0; if (tr->info.track_media_kind == e_video) - flags |= 0x20; // default-sample-flags-present + flags |= 0x20; // default-sample-flags-present else - flags |= 0x08; // default-sample-duration-present + flags |= 0x08; // default-sample-duration-present flags = (tr->info.track_media_kind == e_video) ? 0x20020 : 0x20008; ATOM_FULL(BOX_tfhd, flags) - WRITE_4(track_num + 1); // track_ID - if (tr->info.track_media_kind == e_video) { - WRITE_4(0x1010000); // default_sample_flags - } else { + WRITE_4(track_num + 1); // track_ID + if (tr->info.track_media_kind == e_video) + { + WRITE_4(0x1010000); // default_sample_flags + } + else + { WRITE_4(duration); } END_ATOM #if MP4D_TFDT_SUPPORT - ATOM_FULL(BOX_tfdt, 0x01000000) // version 1 - WRITE_4(timestamp >> 32); // upper timestamp - WRITE_4(timestamp & 0xffffffff); // lower timestamp + ATOM_FULL(BOX_tfdt, 0x01000000) // version 1 + WRITE_4(timestamp >> 32); // upper timestamp + WRITE_4(timestamp & 0xffffffff); // lower timestamp END_ATOM #endif - if (tr->info.track_media_kind == e_audio) { + if (tr->info.track_media_kind == e_audio) + { flags = 0; - flags |= 0x001; // data-offset-present - flags |= 0x200; // sample-size-present + flags |= 0x001; // data-offset-present + flags |= 0x200; // sample-size-present ATOM_FULL(BOX_trun, flags) - WRITE_4(1); // sample_count + WRITE_4(1); // sample_count pdata_offset = p; - p += 4; // save ptr to data_offset - WRITE_4(data_bytes);// sample_size + p += 4; // save ptr to data_offset + WRITE_4(data_bytes); // sample_size END_ATOM - } else if (kind == MP4E_SAMPLE_RANDOM_ACCESS) { + } + else if (kind == MP4E_SAMPLE_RANDOM_ACCESS) + { flags = 0; - flags |= 0x001; // data-offset-present - flags |= 0x004; // first-sample-flags-present - flags |= 0x100; // sample-duration-present - flags |= 0x200; // sample-size-present + flags |= 0x001; // data-offset-present + flags |= 0x004; // first-sample-flags-present + flags |= 0x100; // sample-duration-present + flags |= 0x200; // sample-size-present ATOM_FULL(BOX_trun, flags) - WRITE_4(1); // sample_count + WRITE_4(1); // sample_count pdata_offset = p; - p += 4; // save ptr to data_offset - WRITE_4(0x2000000); // first_sample_flags - WRITE_4(duration); // sample_duration - WRITE_4(data_bytes);// sample_size + p += 4; // save ptr to data_offset + WRITE_4(0x2000000); // first_sample_flags + WRITE_4(duration); // sample_duration + WRITE_4(data_bytes); // sample_size END_ATOM - } else { + } + else + { flags = 0; - flags |= 0x001; // data-offset-present - flags |= 0x100; // sample-duration-present - flags |= 0x200; // sample-size-present + flags |= 0x001; // data-offset-present + flags |= 0x100; // sample-duration-present + flags |= 0x200; // sample-size-present ATOM_FULL(BOX_trun, flags) - WRITE_4(1); // sample_count + WRITE_4(1); // sample_count pdata_offset = p; - p += 4; // save ptr to data_offset - WRITE_4(duration); // sample_duration - WRITE_4(data_bytes);// sample_size + p += 4; // save ptr to data_offset + WRITE_4(duration); // sample_duration + WRITE_4(data_bytes); // sample_size END_ATOM } END_ATOM @@ -1052,7 +1127,8 @@ mp4e_write_fragment_header(MP4E_mux_t *mux, int track_num, int data_bytes, int d return MP4E_STATUS_OK; } -static int mp4e_write_mdat_box(MP4E_mux_t *mux, uint32_t size) { +static int mp4e_write_mdat_box(MP4E_mux_t* mux, uint32_t size) +{ unsigned char base[8], *p = base; WRITE_4(size); WRITE_4(BOX_mdat); @@ -1062,23 +1138,22 @@ static int mp4e_write_mdat_box(MP4E_mux_t *mux, uint32_t size) { } /** -* Add new sample to specified track -*/ -int MP4E_put_sample(MP4E_mux_t *mux, int track_num, const void *data, int data_bytes, int duration, - int kind) { - track_t *tr; - if (!mux || !data) - return MP4E_STATUS_BAD_ARGUMENTS; - tr = ((track_t *) mux->tracks.data) + track_num; - - if (mux->enable_fragmentation) { + * Add new sample to specified track + */ +int MP4E_put_sample(MP4E_mux_t* mux, int track_num, const void* data, int data_bytes, int duration, int kind) +{ + track_t* tr; + if (!mux || !data) return MP4E_STATUS_BAD_ARGUMENTS; + tr = ((track_t*) mux->tracks.data) + track_num; + + if (mux->enable_fragmentation) + { #if MP4D_TFDT_SUPPORT // NOTE: assume a constant `duration` to calculate current timestamp - uint64_t timestamp = (uint64_t)mux->fragments_count * duration; + uint64_t timestamp = (uint64_t) mux->fragments_count * duration; #endif - if (!mux->fragments_count++) ERR( - mp4e_flush_index(mux)); // write file headers before 1st sample - // write MOOF + MDAT + sample data + if (!mux->fragments_count++) ERR(mp4e_flush_index(mux)); // write file headers before 1st sample + // write MOOF + MDAT + sample data #if MP4D_TFDT_SUPPORT ERR(mp4e_write_fragment_header(mux, track_num, data_bytes, duration, kind, timestamp)); #else @@ -1091,25 +1166,30 @@ int MP4E_put_sample(MP4E_mux_t *mux, int track_num, const void *data, int data_b return MP4E_STATUS_OK; } - if (kind != MP4E_SAMPLE_CONTINUATION) { + if (kind != MP4E_SAMPLE_CONTINUATION) + { if (mux->sequential_mode_flag) ERR(write_pending_data(mux, tr)); - if (!add_sample_descriptor(mux, tr, data_bytes, duration, kind)) - return MP4E_STATUS_NO_MEMORY; - } else { - if (!mux->sequential_mode_flag) { - sample_t *smpl_desc; + if (!add_sample_descriptor(mux, tr, data_bytes, duration, kind)) return MP4E_STATUS_NO_MEMORY; + } + else + { + if (!mux->sequential_mode_flag) + { + sample_t* smpl_desc; if (tr->smpl.bytes < sizeof(sample_t)) - return MP4E_STATUS_NO_MEMORY; // write continuation, but there are no samples in the index + return MP4E_STATUS_NO_MEMORY; // write continuation, but there are no samples in the index // Accumulate size of the continuation in the sample descriptor - smpl_desc = (sample_t *) (tr->smpl.data + tr->smpl.bytes) - 1; + smpl_desc = (sample_t*) (tr->smpl.data + tr->smpl.bytes) - 1; smpl_desc->size += data_bytes; } } - if (mux->sequential_mode_flag) { - if (!minimp4_vector_put(&tr->pending_sample, data, data_bytes)) - return MP4E_STATUS_NO_MEMORY; - } else { + if (mux->sequential_mode_flag) + { + if (!minimp4_vector_put(&tr->pending_sample, data, data_bytes)) return MP4E_STATUS_NO_MEMORY; + } + else + { ERR(mux->write_callback(mux->write_pos, data, data_bytes, mux->token)); mux->write_pos += data_bytes; } @@ -1117,41 +1197,40 @@ int MP4E_put_sample(MP4E_mux_t *mux, int track_num, const void *data, int data_b } /** -* calculate size of length field of OD box -*/ -static int od_size_of_size(int size) { + * calculate size of length field of OD box + */ +static int od_size_of_size(int size) +{ int i, size_of_size = 1; - for (i = size; i > 0x7F; i -= 0x7F) - size_of_size++; + for (i = size; i > 0x7F; i -= 0x7F) size_of_size++; return size_of_size; } /** -* Add or remove MP4 file text comment according to Apple specs: -* https://developer.apple.com/library/mac/documentation/QuickTime/QTFF/Metadata/Metadata.html#//apple_ref/doc/uid/TP40000939-CH1-SW1 -* http://atomicparsley.sourceforge.net/mpeg-4files.html -* note that ISO did not specify comment format. -*/ -int MP4E_set_text_comment(MP4E_mux_t *mux, const char *comment) { - if (!mux || !comment) - return MP4E_STATUS_BAD_ARGUMENTS; - if (mux->text_comment) - free(mux->text_comment); + * Add or remove MP4 file text comment according to Apple specs: + * https://developer.apple.com/library/mac/documentation/QuickTime/QTFF/Metadata/Metadata.html#//apple_ref/doc/uid/TP40000939-CH1-SW1 + * http://atomicparsley.sourceforge.net/mpeg-4files.html + * note that ISO did not specify comment format. + */ +int MP4E_set_text_comment(MP4E_mux_t* mux, const char* comment) +{ + if (!mux || !comment) return MP4E_STATUS_BAD_ARGUMENTS; + if (mux->text_comment) free(mux->text_comment); mux->text_comment = strdup(comment); - if (!mux->text_comment) - return MP4E_STATUS_NO_MEMORY; + if (!mux->text_comment) return MP4E_STATUS_NO_MEMORY; return MP4E_STATUS_OK; } /** -* Write file index 'moov' box with all its boxes and indexes -*/ -static int mp4e_flush_index(MP4E_mux_t *mux) { - unsigned char *stack_base[20]; // atoms nesting stack - unsigned char **stack = stack_base; - unsigned char *base, *p; - unsigned int ntr, index_bytes, ntracks = mux->tracks.bytes / sizeof(track_t); - int i, err; + * Write file index 'moov' box with all its boxes and indexes + */ +static int mp4e_flush_index(MP4E_mux_t* mux) +{ + unsigned char* stack_base[20]; // atoms nesting stack + unsigned char** stack = stack_base; + unsigned char * base, *p; + unsigned int ntr, index_bytes, ntracks = mux->tracks.bytes / sizeof(track_t); + int i, err; // How much memory needed for indexes // Experimental data: @@ -1162,11 +1241,11 @@ static int mp4e_flush_index(MP4E_mux_t *mux) { #define FILE_HEADER_BYTES 256 #define TRACK_HEADER_BYTES 512 index_bytes = FILE_HEADER_BYTES; - if (mux->text_comment) - index_bytes += 128 + strlen(mux->text_comment); - for (ntr = 0; ntr < ntracks; ntr++) { - track_t *tr = ((track_t *) mux->tracks.data) + ntr; - index_bytes += TRACK_HEADER_BYTES; // fixed amount (implementation-dependent) + if (mux->text_comment) index_bytes += 128 + strlen(mux->text_comment); + for (ntr = 0; ntr < ntracks; ntr++) + { + track_t* tr = ((track_t*) mux->tracks.data) + ntr; + index_bytes += TRACK_HEADER_BYTES; // fixed amount (implementation-dependent) // may need extra 4 bytes for duration field + 4 bytes for worst-case random access box index_bytes += tr->smpl.bytes * (sizeof(sample_t) + 4 + 4) / sizeof(sample_t); index_bytes += tr->vsps.bytes; @@ -1175,25 +1254,28 @@ static int mp4e_flush_index(MP4E_mux_t *mux) { ERR(write_pending_data(mux, tr)); } - base = (unsigned char *) malloc(index_bytes); - if (!base) - return MP4E_STATUS_NO_MEMORY; + base = (unsigned char*) malloc(index_bytes); + if (!base) return MP4E_STATUS_NO_MEMORY; p = base; - if (!mux->sequential_mode_flag) { + if (!mux->sequential_mode_flag) + { // update size of mdat box. // One of 2 points, which requires random file access. // Second is optional duration update at beginning of file in fragmentation mode. // This can be avoided using "till eof" size code, but in this case indexes must be // written before the mdat.... - int64_t size = mux->write_pos - sizeof(box_ftyp); + int64_t size = mux->write_pos - sizeof(box_ftyp); const int64_t size_limit = (int64_t) (uint64_t) 0xfffffffe; - if (size > size_limit) { + if (size > size_limit) + { WRITE_4(1); WRITE_4(BOX_mdat); WRITE_4((size >> 32) & 0xffffffff); WRITE_4(size & 0xffffffff); - } else { + } + else + { WRITE_4(8); WRITE_4(BOX_free); WRITE_4(size - 8); @@ -1203,26 +1285,27 @@ static int mp4e_flush_index(MP4E_mux_t *mux) { p = base; } - // Write index atoms; order taken from Table 1 of [1] + // Write index atoms; order taken from Table 1 of [1] #define MOOV_TIMESCALE 1000 ATOM(BOX_moov); ATOM_FULL(BOX_mvhd, 0); - WRITE_4(0); // creation_time - WRITE_4(0); // modification_time + WRITE_4(0); // creation_time + WRITE_4(0); // modification_time - if (ntracks) { - track_t *tr = ((track_t *) mux->tracks.data) + 0; // take 1st track + if (ntracks) + { + track_t* tr = ((track_t*) mux->tracks.data) + 0; // take 1st track unsigned duration = get_duration(tr); - duration = (unsigned) (duration * 1LL * MOOV_TIMESCALE / tr->info.time_scale); - WRITE_4(MOOV_TIMESCALE); // duration - WRITE_4(duration); // duration + duration = (unsigned) (duration * 1LL * MOOV_TIMESCALE / tr->info.time_scale); + WRITE_4(MOOV_TIMESCALE); // duration + WRITE_4(duration); // duration } - WRITE_4(0x00010000); // rate - WRITE_2(0x0100); // volume - WRITE_2(0); // reserved - WRITE_4(0); // reserved - WRITE_4(0); // reserved + WRITE_4(0x00010000); // rate + WRITE_2(0x0100); // volume + WRITE_2(0); // reserved + WRITE_4(0); // reserved + WRITE_4(0); // reserved // matrix[9] WRITE_4(0x00010000); @@ -1243,32 +1326,34 @@ static int mp4e_flush_index(MP4E_mux_t *mux) { WRITE_4(0); WRITE_4(0); - //next_track_ID is a non-zero integer that indicates a value to use for the track ID of the next track to be - //added to this presentation. Zero is not a valid track ID value. The value of next_track_ID shall be - //larger than the largest track-ID in use. + // next_track_ID is a non-zero integer that indicates a value to use for the track ID of the next track to be + // added to this presentation. Zero is not a valid track ID value. The value of next_track_ID shall be + // larger than the largest track-ID in use. WRITE_4(ntracks + 1); END_ATOM; - for (ntr = 0; ntr < ntracks; ntr++) { - track_t *tr = ((track_t *) mux->tracks.data) + ntr; - unsigned duration = get_duration(tr); - int samples_count = tr->smpl.bytes / sizeof(sample_t); - const sample_t *sample = (const sample_t *) tr->smpl.data; - unsigned handler_type; - const char *handler_ascii = NULL; + for (ntr = 0; ntr < ntracks; ntr++) + { + track_t* tr = ((track_t*) mux->tracks.data) + ntr; + unsigned duration = get_duration(tr); + int samples_count = tr->smpl.bytes / sizeof(sample_t); + const sample_t* sample = (const sample_t*) tr->smpl.data; + unsigned handler_type; + const char* handler_ascii = NULL; if (mux->enable_fragmentation) samples_count = 0; else if (samples_count <= 0) - continue; // skip empty track + continue; // skip empty track - switch (tr->info.track_media_kind) { + switch (tr->info.track_media_kind) + { case e_audio: - handler_type = MP4E_HANDLER_TYPE_SOUN; + handler_type = MP4E_HANDLER_TYPE_SOUN; handler_ascii = "SoundHandler"; break; case e_video: - handler_type = MP4E_HANDLER_TYPE_VIDE; + handler_type = MP4E_HANDLER_TYPE_VIDE; handler_ascii = "VideoHandler"; break; case e_private: @@ -1279,18 +1364,18 @@ static int mp4e_flush_index(MP4E_mux_t *mux) { } ATOM(BOX_trak); - ATOM_FULL(BOX_tkhd, 7); // flag: 1=trak enabled; 2=track in movie; 4=track in preview - WRITE_4(0); // creation_time - WRITE_4(0); // modification_time - WRITE_4(ntr + 1); // track_ID - WRITE_4(0); // reserved + ATOM_FULL(BOX_tkhd, 7); // flag: 1=trak enabled; 2=track in movie; 4=track in preview + WRITE_4(0); // creation_time + WRITE_4(0); // modification_time + WRITE_4(ntr + 1); // track_ID + WRITE_4(0); // reserved WRITE_4((unsigned) (duration * 1LL * MOOV_TIMESCALE / tr->info.time_scale)); WRITE_4(0); - WRITE_4(0); // reserved[2] - WRITE_2(0); // layer - WRITE_2(0); // alternate_group - WRITE_2(0x0100); // volume {if track_is_audio 0x0100 else 0}; - WRITE_2(0); // reserved + WRITE_4(0); // reserved[2] + WRITE_2(0); // layer + WRITE_2(0); // alternate_group + WRITE_2(0x0100); // volume {if track_is_audio 0x0100 else 0}; + WRITE_2(0); // reserved // matrix[9] WRITE_4(0x00010000); @@ -1303,72 +1388,80 @@ static int mp4e_flush_index(MP4E_mux_t *mux) { WRITE_4(0); WRITE_4(0x40000000); - if (tr->info.track_media_kind == e_audio || tr->info.track_media_kind == e_private) { - WRITE_4(0); // width - WRITE_4(0); // height - } else { - WRITE_4(tr->info.u.v.width * 0x10000); // width - WRITE_4(tr->info.u.v.height * 0x10000); // height + if (tr->info.track_media_kind == e_audio || tr->info.track_media_kind == e_private) + { + WRITE_4(0); // width + WRITE_4(0); // height + } + else + { + WRITE_4(tr->info.u.v.width * 0x10000); // width + WRITE_4(tr->info.u.v.height * 0x10000); // height } END_ATOM; ATOM(BOX_mdia); ATOM_FULL(BOX_mdhd, 0); - WRITE_4(0); // creation_time - WRITE_4(0); // modification_time + WRITE_4(0); // creation_time + WRITE_4(0); // modification_time WRITE_4(tr->info.time_scale); - WRITE_4(duration); // duration + WRITE_4(duration); // duration { int lang_code = - ((tr->info.language[0] & 31) << 10) | ((tr->info.language[1] & 31) << 5) | - (tr->info.language[2] & 31); - WRITE_2(lang_code); // language + ((tr->info.language[0] & 31) << 10) | ((tr->info.language[1] & 31) << 5) | (tr->info.language[2] & 31); + WRITE_2(lang_code); // language } - WRITE_2(0); // pre_defined + WRITE_2(0); // pre_defined END_ATOM; ATOM_FULL(BOX_hdlr, 0); - WRITE_4(0); // pre_defined - WRITE_4(handler_type); // handler_type + WRITE_4(0); // pre_defined + WRITE_4(handler_type); // handler_type WRITE_4(0); WRITE_4(0); - WRITE_4(0); // reserved[3] - // name is a null-terminated string in UTF-8 characters which gives a human-readable name for the track type (for debugging and inspection purposes). - // set mdia hdlr name field to what quicktime uses. - // Sony smartphone may fail to decode short files w/o handler name - if (handler_ascii) { - for (i = 0; i < (int) strlen(handler_ascii) + 1; i++) { + WRITE_4(0); // reserved[3] + // name is a null-terminated string in UTF-8 characters which gives a human-readable name for the track type + // (for debugging and inspection purposes). set mdia hdlr name field to what quicktime uses. Sony smartphone may + // fail to decode short files w/o handler name + if (handler_ascii) + { + for (i = 0; i < (int) strlen(handler_ascii) + 1; i++) + { WRITE_1(handler_ascii[i]); } - } else { + } + else + { WRITE_4(0); } END_ATOM; ATOM(BOX_minf); - if (tr->info.track_media_kind == e_audio) { + if (tr->info.track_media_kind == e_audio) + { // Sound Media Header Box ATOM_FULL(BOX_smhd, 0); - WRITE_2(0); // balance - WRITE_2(0); // reserved + WRITE_2(0); // balance + WRITE_2(0); // reserved END_ATOM; } - if (tr->info.track_media_kind == e_video) { + if (tr->info.track_media_kind == e_video) + { // mandatory Video Media Header Box ATOM_FULL(BOX_vmhd, 1); - WRITE_2(0); // graphicsmode + WRITE_2(0); // graphicsmode WRITE_2(0); WRITE_2(0); - WRITE_2(0); // opcolor[3] + WRITE_2(0); // opcolor[3] END_ATOM; } ATOM(BOX_dinf); ATOM_FULL(BOX_dref, 0); - WRITE_4(1); // entry_count - // If the flag is set indicating that the data is in the same file as this box, then no string (not even an empty one) - // shall be supplied in the entry field. + WRITE_4(1); // entry_count + // If the flag is set indicating that the data is in the same file as this box, then no string (not even an + // empty one) shall be supplied in the entry field. // ASP the correct way to avoid supply the string, is to use flag 1 // otherwise ISO reference demux crashes @@ -1379,63 +1472,80 @@ static int mp4e_flush_index(MP4E_mux_t *mux) { ATOM(BOX_stbl); ATOM_FULL(BOX_stsd, 0); - WRITE_4(1); // entry_count; + WRITE_4(1); // entry_count; - if (tr->info.track_media_kind == e_audio || tr->info.track_media_kind == e_private) { + if (tr->info.track_media_kind == e_audio || tr->info.track_media_kind == e_private) + { // AudioSampleEntry() assume MP4E_HANDLER_TYPE_SOUN - if (tr->info.track_media_kind == e_audio) { + if (tr->info.track_media_kind == e_audio) + { ATOM(BOX_mp4a); - } else { + } + else + { ATOM(BOX_mp4s); } // SampleEntry WRITE_4(0); - WRITE_2(0); // reserved[6] - WRITE_2(1); // data_reference_index; - this is a tag for descriptor below + WRITE_2(0); // reserved[6] + WRITE_2(1); // data_reference_index; - this is a tag for descriptor below - if (tr->info.track_media_kind == e_audio) { + if (tr->info.track_media_kind == e_audio) + { // AudioSampleEntry WRITE_4(0); - WRITE_4(0); // reserved[2] - WRITE_2(tr->info.u.a.channelcount); // channelcount - WRITE_2(16); // samplesize - WRITE_4(0); // pre_defined+reserved + WRITE_4(0); // reserved[2] + WRITE_2(tr->info.u.a.channelcount); // channelcount + WRITE_2(16); // samplesize + WRITE_4(0); // pre_defined+reserved WRITE_4((tr->info.time_scale << 16)); // samplerate == = {timescale of media}<<16; } ATOM_FULL(BOX_esds, 0); - if (tr->vsps.bytes > 0) { - int dsi_bytes = tr->vsps.bytes - 2; // - two bytes size field + if (tr->vsps.bytes > 0) + { + int dsi_bytes = tr->vsps.bytes - 2; // - two bytes size field int dsi_size_size = od_size_of_size(dsi_bytes); - int dcd_bytes = dsi_bytes + dsi_size_size + 1 + (1 + 1 + 3 + 4 + 4); + int dcd_bytes = dsi_bytes + dsi_size_size + 1 + (1 + 1 + 3 + 4 + 4); int dcd_size_size = od_size_of_size(dcd_bytes); - int esd_bytes = dcd_bytes + dcd_size_size + 1 + 3; - -#define WRITE_OD_LEN(size) if (size > 0x7F) do { size -= 0x7F; WRITE_1(0x00ff); } while (size > 0x7F); WRITE_1(size) - WRITE_1(3); // OD_ESD + int esd_bytes = dcd_bytes + dcd_size_size + 1 + 3; + +#define WRITE_OD_LEN(size) \ + if (size > 0x7F) do \ + { \ + size -= 0x7F; \ + WRITE_1(0x00ff); \ + } \ + while (size > 0x7F); \ + WRITE_1(size) + WRITE_1(3); // OD_ESD WRITE_OD_LEN(esd_bytes); - WRITE_2(0); // ES_ID(2) // TODO - what is this? - WRITE_1(0); // flags(1) + WRITE_2(0); // ES_ID(2) // TODO - what is this? + WRITE_1(0); // flags(1) - WRITE_1(4); // OD_DCD + WRITE_1(4); // OD_DCD WRITE_OD_LEN(dcd_bytes); - if (tr->info.track_media_kind == e_audio) { - WRITE_1(MP4_OBJECT_TYPE_AUDIO_ISO_IEC_14496_3); // OD_DCD - WRITE_1(5 << 2); // stream_type == AudioStream - } else { + if (tr->info.track_media_kind == e_audio) + { + WRITE_1(MP4_OBJECT_TYPE_AUDIO_ISO_IEC_14496_3); // OD_DCD + WRITE_1(5 << 2); // stream_type == AudioStream + } + else + { // http://xhelmboyx.tripod.com/formats/mp4-layout.txt - WRITE_1(208); // 208 = private video - WRITE_1(32 << 2); // stream_type == user private + WRITE_1(208); // 208 = private video + WRITE_1(32 << 2); // stream_type == user private } - WRITE_3(tr->info.u.a.channelcount * 6144 / - 8); // bufferSizeDB in bytes, constant as in reference decoder - WRITE_4(0); // maxBitrate TODO - WRITE_4(0); // avg_bitrate_bps TODO + WRITE_3( + tr->info.u.a.channelcount * 6144 / 8); // bufferSizeDB in bytes, constant as in reference decoder + WRITE_4(0); // maxBitrate TODO + WRITE_4(0); // avg_bitrate_bps TODO - WRITE_1(5); // OD_DSI + WRITE_1(5); // OD_DSI WRITE_OD_LEN(dsi_bytes); - for (i = 0; i < dsi_bytes; i++) { + for (i = 0; i < dsi_bytes; i++) + { WRITE_1(tr->vsps.data[2 + i]); } } @@ -1443,88 +1553,101 @@ static int mp4e_flush_index(MP4E_mux_t *mux) { END_ATOM; } - if (tr->info.track_media_kind == e_video && - (MP4_OBJECT_TYPE_AVC == tr->info.object_type_indication || - MP4_OBJECT_TYPE_HEVC == tr->info.object_type_indication)) { + if (tr->info.track_media_kind == e_video && (MP4_OBJECT_TYPE_AVC == tr->info.object_type_indication || + MP4_OBJECT_TYPE_HEVC == tr->info.object_type_indication)) + { int numOfSequenceParameterSets = items_count(&tr->vsps); - int numOfPictureParameterSets = items_count(&tr->vpps); - if (MP4_OBJECT_TYPE_AVC == tr->info.object_type_indication) { + int numOfPictureParameterSets = items_count(&tr->vpps); + if (MP4_OBJECT_TYPE_AVC == tr->info.object_type_indication) + { ATOM(BOX_avc1); - } else { + } + else + { ATOM(BOX_hvc1); } // VisualSampleEntry 8.16.2 // extends SampleEntry - WRITE_2(0); // reserved - WRITE_2(0); // reserved - WRITE_2(0); // reserved - WRITE_2(1); // data_reference_index - - WRITE_2(0); // pre_defined - WRITE_2(0); // reserved - WRITE_4(0); // pre_defined - WRITE_4(0); // pre_defined - WRITE_4(0); // pre_defined + WRITE_2(0); // reserved + WRITE_2(0); // reserved + WRITE_2(0); // reserved + WRITE_2(1); // data_reference_index + + WRITE_2(0); // pre_defined + WRITE_2(0); // reserved + WRITE_4(0); // pre_defined + WRITE_4(0); // pre_defined + WRITE_4(0); // pre_defined WRITE_2(tr->info.u.v.width); WRITE_2(tr->info.u.v.height); - WRITE_4(0x00480000); // horizresolution = 72 dpi - WRITE_4(0x00480000); // vertresolution = 72 dpi - WRITE_4(0); // reserved - WRITE_2(1); // frame_count - for (i = 0; i < 32; i++) { - WRITE_1(0); // compressorname + WRITE_4(0x00480000); // horizresolution = 72 dpi + WRITE_4(0x00480000); // vertresolution = 72 dpi + WRITE_4(0); // reserved + WRITE_2(1); // frame_count + for (i = 0; i < 32; i++) + { + WRITE_1(0); // compressorname } - WRITE_2(24); // depth - WRITE_2(-1); // pre_defined + WRITE_2(24); // depth + WRITE_2(-1); // pre_defined - if (MP4_OBJECT_TYPE_AVC == tr->info.object_type_indication) { + if (MP4_OBJECT_TYPE_AVC == tr->info.object_type_indication) + { ATOM(BOX_avcC); // AVCDecoderConfigurationRecord 5.2.4.1.1 - WRITE_1(1); // configurationVersion + WRITE_1(1); // configurationVersion WRITE_1(tr->vsps.data[2 + 1]); WRITE_1(tr->vsps.data[2 + 2]); WRITE_1(tr->vsps.data[2 + 3]); - WRITE_1(255); // 0xfc + NALU_len - 1 + WRITE_1(255); // 0xfc + NALU_len - 1 WRITE_1(0xe0 | numOfSequenceParameterSets); - for (i = 0; i < tr->vsps.bytes; i++) { + for (i = 0; i < tr->vsps.bytes; i++) + { WRITE_1(tr->vsps.data[i]); } WRITE_1(numOfPictureParameterSets); - for (i = 0; i < tr->vpps.bytes; i++) { + for (i = 0; i < tr->vpps.bytes; i++) + { WRITE_1(tr->vpps.data[i]); } - } else { + } + else + { int numOfVPS = items_count(&tr->vpps); ATOM(BOX_hvcC); // TODO: read actual params from stream - WRITE_1(1); // configurationVersion - WRITE_1(1); // Profile Space (2), Tier (1), Profile (5) - WRITE_4(0x60000000); // Profile Compatibility - WRITE_2(0); // progressive, interlaced, non packed constraint, frame only constraint flags - WRITE_4(0); // constraint indicator flags - WRITE_1(0); // level_idc - WRITE_2(0xf000); // Min Spatial Segmentation - WRITE_1(0xfc); // Parallelism Type - WRITE_1(0xfc); // Chroma Format - WRITE_1(0xf8); // Luma Depth - WRITE_1(0xf8); // Chroma Depth - WRITE_2(0); // Avg Frame Rate - WRITE_1(3); // ConstantFrameRate (2), NumTemporalLayers (3), TemporalIdNested (1), LengthSizeMinusOne (2) - - WRITE_1(3); // Num Of Arrays - WRITE_1((1 << 7) | (HEVC_NAL_VPS & 0x3f)); // Array Completeness + NAL Unit Type + WRITE_1(1); // configurationVersion + WRITE_1(1); // Profile Space (2), Tier (1), Profile (5) + WRITE_4(0x60000000); // Profile Compatibility + WRITE_2(0); // progressive, interlaced, non packed constraint, frame only constraint flags + WRITE_4(0); // constraint indicator flags + WRITE_1(0); // level_idc + WRITE_2(0xf000); // Min Spatial Segmentation + WRITE_1(0xfc); // Parallelism Type + WRITE_1(0xfc); // Chroma Format + WRITE_1(0xf8); // Luma Depth + WRITE_1(0xf8); // Chroma Depth + WRITE_2(0); // Avg Frame Rate + WRITE_1( + 3); // ConstantFrameRate (2), NumTemporalLayers (3), TemporalIdNested (1), LengthSizeMinusOne (2) + + WRITE_1(3); // Num Of Arrays + WRITE_1((1 << 7) | (HEVC_NAL_VPS & 0x3f)); // Array Completeness + NAL Unit Type WRITE_2(numOfVPS); - for (i = 0; i < tr->vvps.bytes; i++) { + for (i = 0; i < tr->vvps.bytes; i++) + { WRITE_1(tr->vvps.data[i]); } WRITE_1((1 << 7) | (HEVC_NAL_SPS & 0x3f)); WRITE_2(numOfSequenceParameterSets); - for (i = 0; i < tr->vsps.bytes; i++) { + for (i = 0; i < tr->vsps.bytes; i++) + { WRITE_1(tr->vsps.data[i]); } WRITE_1((1 << 7) | (HEVC_NAL_PPS & 0x3f)); WRITE_2(numOfPictureParameterSets); - for (i = 0; i < tr->vpps.bytes; i++) { + for (i = 0; i < tr->vpps.bytes; i++) + { WRITE_1(tr->vpps.data[i]); } } @@ -1541,11 +1664,13 @@ static int mp4e_flush_index(MP4E_mux_t *mux) { // Time to Sample Box ATOM_FULL(BOX_stts, 0); { - unsigned char *pentry_count = p; - int cnt = 1, entry_count = 0; + unsigned char* pentry_count = p; + int cnt = 1, entry_count = 0; WRITE_4(0); - for (i = 0; i < samples_count; i++, cnt++) { - if (i == (samples_count - 1) || sample[i].duration != sample[i + 1].duration) { + for (i = 0; i < samples_count; i++, cnt++) + { + if (i == (samples_count - 1) || sample[i].duration != sample[i + 1].duration) + { WRITE_4(cnt); WRITE_4(sample[i].duration); cnt = 0; @@ -1558,40 +1683,48 @@ static int mp4e_flush_index(MP4E_mux_t *mux) { // Sample To Chunk Box ATOM_FULL(BOX_stsc, 0); - if (mux->enable_fragmentation) { - WRITE_4(0); // entry_count - } else { - WRITE_4(1); // entry_count - WRITE_4(1); // first_chunk; - WRITE_4(1); // samples_per_chunk; - WRITE_4(1); // sample_description_index; + if (mux->enable_fragmentation) + { + WRITE_4(0); // entry_count + } + else + { + WRITE_4(1); // entry_count + WRITE_4(1); // first_chunk; + WRITE_4(1); // samples_per_chunk; + WRITE_4(1); // sample_description_index; } END_ATOM; // Sample Size Box ATOM_FULL(BOX_stsz, 0); - WRITE_4(0); // sample_size If this field is set to 0, then the samples have different sizes, and those sizes + WRITE_4(0); // sample_size If this field is set to 0, then the samples have different sizes, and those sizes // are stored in the sample size table. WRITE_4(samples_count); // sample_count; - for (i = 0; i < samples_count; i++) { + for (i = 0; i < samples_count; i++) + { WRITE_4(sample[i].size); } END_ATOM; // Chunk Offset Box int is_64_bit = 0; - if (samples_count && sample[samples_count - 1].offset > 0xffffffff) - is_64_bit = 1; - if (!is_64_bit) { + if (samples_count && sample[samples_count - 1].offset > 0xffffffff) is_64_bit = 1; + if (!is_64_bit) + { ATOM_FULL(BOX_stco, 0); WRITE_4(samples_count); - for (i = 0; i < samples_count; i++) { + for (i = 0; i < samples_count; i++) + { WRITE_4(sample[i].offset); } - } else { + } + else + { ATOM_FULL(BOX_co64, 0); WRITE_4(samples_count); - for (i = 0; i < samples_count; i++) { + for (i = 0; i < samples_count; i++) + { WRITE_4((sample[i].offset >> 32) & 0xffffffff); WRITE_4(sample[i].offset & 0xffffffff); } @@ -1601,15 +1734,19 @@ static int mp4e_flush_index(MP4E_mux_t *mux) { // Sync Sample Box { int ra_count = 0; - for (i = 0; i < samples_count; i++) { + for (i = 0; i < samples_count; i++) + { ra_count += !!sample[i].flag_random_access; } - if (ra_count != samples_count) { + if (ra_count != samples_count) + { // If the sync sample box is not present, every sample is a random access point. ATOM_FULL(BOX_stss, 0); WRITE_4(ra_count); - for (i = 0; i < samples_count; i++) { - if (sample[i].flag_random_access) { + for (i = 0; i < samples_count; i++) + { + if (sample[i].flag_random_access) + { WRITE_4(i + 1); } } @@ -1620,26 +1757,29 @@ static int mp4e_flush_index(MP4E_mux_t *mux) { END_ATOM; END_ATOM; END_ATOM; - } // tracks loop + } // tracks loop - if (mux->text_comment) { + if (mux->text_comment) + { ATOM(BOX_udta); ATOM_FULL(BOX_meta, 0); ATOM_FULL(BOX_hdlr, 0); - WRITE_4(0); // pre_defined -#define MP4E_HANDLER_TYPE_MDIR 0x6d646972 - WRITE_4(MP4E_HANDLER_TYPE_MDIR); // handler_type + WRITE_4(0); // pre_defined +#define MP4E_HANDLER_TYPE_MDIR 0x6d646972 + WRITE_4(MP4E_HANDLER_TYPE_MDIR); // handler_type WRITE_4(0); WRITE_4(0); - WRITE_4(0); // reserved[3] - WRITE_4(0); // name is a null-terminated string in UTF-8 characters which gives a human-readable name for the track type (for debugging and inspection purposes). + WRITE_4(0); // reserved[3] + WRITE_4(0); // name is a null-terminated string in UTF-8 characters which gives a human-readable name for the + // track type (for debugging and inspection purposes). END_ATOM; ATOM(BOX_ilst); ATOM(BOX_ccmt); ATOM(BOX_data); - WRITE_4(1); // type - WRITE_4(0); // lang - for (i = 0; i < (int) strlen(mux->text_comment) + 1; i++) { + WRITE_4(1); // type + WRITE_4(0); // lang + for (i = 0; i < (int) strlen(mux->text_comment) + 1; i++) + { WRITE_1(mux->text_comment[i]); } END_ATOM; @@ -1649,26 +1789,28 @@ static int mp4e_flush_index(MP4E_mux_t *mux) { END_ATOM; } - if (mux->enable_fragmentation) { - track_t *tr = ((track_t *) mux->tracks.data) + 0; + if (mux->enable_fragmentation) + { + track_t* tr = ((track_t*) mux->tracks.data) + 0; uint32_t movie_duration = get_duration(tr); ATOM(BOX_mvex); ATOM_FULL(BOX_mehd, 0); - WRITE_4(movie_duration); // duration + WRITE_4(movie_duration); // duration END_ATOM; - for (ntr = 0; ntr < ntracks; ntr++) { + for (ntr = 0; ntr < ntracks; ntr++) + { ATOM_FULL(BOX_trex, 0); - WRITE_4(ntr + 1); // track_ID - WRITE_4(1); // default_sample_description_index - WRITE_4(0); // default_sample_duration - WRITE_4(0); // default_sample_size - WRITE_4(0); // default_sample_flags + WRITE_4(ntr + 1); // track_ID + WRITE_4(1); // default_sample_description_index + WRITE_4(0); // default_sample_duration + WRITE_4(0); // default_sample_size + WRITE_4(0); // default_sample_flags END_ATOM; } END_ATOM; } - END_ATOM; // moov atom + END_ATOM; // moov atom assert((unsigned) (p - base) <= index_bytes); @@ -1678,18 +1820,17 @@ static int mp4e_flush_index(MP4E_mux_t *mux) { return err; } -int MP4E_close(MP4E_mux_t *mux) { - int err = MP4E_STATUS_OK; +int MP4E_close(MP4E_mux_t* mux) +{ + int err = MP4E_STATUS_OK; unsigned ntr, ntracks; - if (!mux) - return MP4E_STATUS_BAD_ARGUMENTS; - if (!mux->enable_fragmentation) - err = mp4e_flush_index(mux); - if (mux->text_comment) - free(mux->text_comment); + if (!mux) return MP4E_STATUS_BAD_ARGUMENTS; + if (!mux->enable_fragmentation) err = mp4e_flush_index(mux); + if (mux->text_comment) free(mux->text_comment); ntracks = mux->tracks.bytes / sizeof(track_t); - for (ntr = 0; ntr < ntracks; ntr++) { - track_t *tr = ((track_t *) mux->tracks.data) + ntr; + for (ntr = 0; ntr < ntracks; ntr++) + { + track_t* tr = ((track_t*) mux->tracks.data) + ntr; minimp4_vector_reset(&tr->vsps); minimp4_vector_reset(&tr->vpps); minimp4_vector_reset(&tr->smpl); @@ -1703,7 +1844,8 @@ int MP4E_close(MP4E_mux_t *mux) { typedef uint32_t bs_item_t; #define BS_BITS 32 -typedef struct { +typedef struct +{ // Look-ahead bit cache: MSB aligned, 17 bits guaranteed, zero stuffing unsigned int cache; @@ -1712,53 +1854,58 @@ typedef struct { int cache_free_bits; // Current read position - const uint16_t *buf; + const uint16_t* buf; // original data buffer - const uint16_t *origin; + const uint16_t* origin; // original data buffer length, bytes unsigned origin_bytes; } bit_reader_t; +#define LOAD_SHORT(x) ((uint16_t) (x << 8) | (x >> 8)) -#define LOAD_SHORT(x) ((uint16_t)(x << 8) | (x >> 8)) - -static unsigned int show_bits(bit_reader_t *bs, int n) { +static unsigned int show_bits(bit_reader_t* bs, int n) +{ unsigned int retval; assert(n > 0 && n <= 16); retval = (unsigned int) (bs->cache >> (32 - n)); return retval; } -static void flush_bits(bit_reader_t *bs, int n) { +static void flush_bits(bit_reader_t* bs, int n) +{ assert(n >= 0 && n <= 16); bs->cache <<= n; bs->cache_free_bits += n; - if (bs->cache_free_bits >= 0) { + if (bs->cache_free_bits >= 0) + { bs->cache |= ((uint32_t) LOAD_SHORT(*bs->buf)) << bs->cache_free_bits; bs->buf++; bs->cache_free_bits -= 16; } } -static unsigned int get_bits(bit_reader_t *bs, int n) { +static unsigned int get_bits(bit_reader_t* bs, int n) +{ unsigned int retval = show_bits(bs, n); flush_bits(bs, n); return retval; } -static void set_pos_bits(bit_reader_t *bs, unsigned pos_bits) { +static void set_pos_bits(bit_reader_t* bs, unsigned pos_bits) +{ assert((int) pos_bits >= 0); - bs->buf = bs->origin + pos_bits / 16; - bs->cache = 0; + bs->buf = bs->origin + pos_bits / 16; + bs->cache = 0; bs->cache_free_bits = 16; flush_bits(bs, 0); flush_bits(bs, pos_bits & 15); } -static unsigned get_pos_bits(const bit_reader_t *bs) { +static unsigned get_pos_bits(const bit_reader_t* bs) +{ // Current bitbuffer position = // position of next wobits in the internal buffer // minus bs, available in bit cache wobits @@ -1768,12 +1915,14 @@ static unsigned get_pos_bits(const bit_reader_t *bs) { return pos_bits; } -static int remaining_bits(const bit_reader_t *bs) { +static int remaining_bits(const bit_reader_t* bs) +{ return bs->origin_bytes * 8 - get_pos_bits(bs); } -static void init_bits(bit_reader_t *bs, const void *data, unsigned data_bytes) { - bs->origin = (const uint16_t *) data; +static void init_bits(bit_reader_t* bs, const void* data, unsigned data_bytes) +{ + bs->origin = (const uint16_t*) data; bs->origin_bytes = data_bytes; set_pos_bits(bs, 0); } @@ -1781,13 +1930,16 @@ static void init_bits(bit_reader_t *bs, const void *data, unsigned data_bytes) { #define GetBits(n) get_bits(bs, n) /** -* Unsigned Golomb code -*/ -static int ue_bits(bit_reader_t *bs) { + * Unsigned Golomb code + */ +static int ue_bits(bit_reader_t* bs) +{ int clz; int val; - for (clz = 0; !get_bits(bs, 1); clz++) {} - //get_bits(bs, clz + 1); + for (clz = 0; !get_bits(bs, 1); clz++) + { + } + // get_bits(bs, clz + 1); val = (1 << clz) - 1 + (clz ? get_bits(bs, clz) : 0); return val; } @@ -1795,118 +1947,142 @@ static int ue_bits(bit_reader_t *bs) { #if MINIMP4_TRANSCODE_SPS_ID /** -* Output bitstream -*/ -typedef struct { - int shift; // bit position in the cache - uint32_t cache; // bit cache - bs_item_t *buf; // current position - bs_item_t *origin; // initial position + * Output bitstream + */ +typedef struct +{ + int shift; // bit position in the cache + uint32_t cache; // bit cache + bs_item_t* buf; // current position + bs_item_t* origin; // initial position } bs_t; -#define SWAP32(x) (uint32_t)((((x) >> 24) & 0xFF) | (((x) >> 8) & 0xFF00) | (((x) << 8) & 0xFF0000) | ((x & 0xFF) << 24)) +#define SWAP32(x) \ + (uint32_t)((((x) >> 24) & 0xFF) | (((x) >> 8) & 0xFF00) | (((x) << 8) & 0xFF0000) | ((x & 0xFF) << 24)) -static void h264e_bs_put_bits(bs_t *bs, unsigned n, unsigned val) { +static void h264e_bs_put_bits(bs_t* bs, unsigned n, unsigned val) +{ assert(!(val >> n)); bs->shift -= n; assert((unsigned) n <= 32); - if (bs->shift < 0) { + if (bs->shift < 0) + { assert(-bs->shift < 32); bs->cache |= val >> -bs->shift; *bs->buf++ = SWAP32(bs->cache); - bs->shift = 32 + bs->shift; - bs->cache = 0; + bs->shift = 32 + bs->shift; + bs->cache = 0; } bs->cache |= val << bs->shift; } -static void h264e_bs_flush(bs_t *bs) { +static void h264e_bs_flush(bs_t* bs) +{ *bs->buf = SWAP32(bs->cache); } -static unsigned h264e_bs_get_pos_bits(const bs_t *bs) { +static unsigned h264e_bs_get_pos_bits(const bs_t* bs) +{ unsigned pos_bits = (unsigned) ((bs->buf - bs->origin) * BS_BITS); pos_bits += BS_BITS - bs->shift; assert((int) pos_bits >= 0); return pos_bits; } -static unsigned h264e_bs_byte_align(bs_t *bs) { +static unsigned h264e_bs_byte_align(bs_t* bs) +{ int pos = h264e_bs_get_pos_bits(bs); h264e_bs_put_bits(bs, -pos & 7, 0); return pos + (-pos & 7); } /** -* Golomb code -* 0 => 1 -* 1 => 01 0 -* 2 => 01 1 -* 3 => 001 00 -* 4 => 001 01 -* -* [0] => 1 -* [1..2] => 01x -* [3..6] => 001xx -* [7..14] => 0001xxx -* -*/ -static void h264e_bs_put_golomb(bs_t *bs, unsigned val) { - int size = 0; - unsigned t = val + 1; - do { + * Golomb code + * 0 => 1 + * 1 => 01 0 + * 2 => 01 1 + * 3 => 001 00 + * 4 => 001 01 + * + * [0] => 1 + * [1..2] => 01x + * [3..6] => 001xx + * [7..14] => 0001xxx + * + */ +static void h264e_bs_put_golomb(bs_t* bs, unsigned val) +{ + int size = 0; + unsigned t = val + 1; + do + { size++; - } while (t >>= 1); + } + while (t >>= 1); h264e_bs_put_bits(bs, 2 * size - 1, val + 1); } -static void h264e_bs_init_bits(bs_t *bs, void *data) { - bs->origin = (bs_item_t *) data; - bs->buf = bs->origin; - bs->shift = BS_BITS; - bs->cache = 0; +static void h264e_bs_init_bits(bs_t* bs, void* data) +{ + bs->origin = (bs_item_t*) data; + bs->buf = bs->origin; + bs->shift = BS_BITS; + bs->cache = 0; } -static int find_mem_cache(void *cache[], int cache_bytes[], int cache_size, void *mem, int bytes) { +static int find_mem_cache(void* cache[], int cache_bytes[], int cache_size, void* mem, int bytes) +{ int i; - if (!bytes) - return -1; - for (i = 0; i < cache_size; i++) { - if (cache_bytes[i] == bytes && !memcmp(mem, cache[i], bytes)) - return i; // found - } - for (i = 0; i < cache_size; i++) { - if (!cache_bytes[i]) { + if (!bytes) return -1; + for (i = 0; i < cache_size; i++) + { + if (cache_bytes[i] == bytes && !memcmp(mem, cache[i], bytes)) return i; // found + } + for (i = 0; i < cache_size; i++) + { + if (!cache_bytes[i]) + { cache[i] = malloc(bytes); - if (cache[i]) { + if (cache[i]) + { memcpy(cache[i], mem, bytes); cache_bytes[i] = bytes; } - return i; // put in + return i; // put in } } return -1; // no room } /** -* 7.4.1.1. "Encapsulation of an SODB within an RBSP" -*/ -static int remove_nal_escapes(unsigned char *dst, const unsigned char *src, int h264_data_bytes) { + * 7.4.1.1. "Encapsulation of an SODB within an RBSP" + */ +static int remove_nal_escapes(unsigned char* dst, const unsigned char* src, int h264_data_bytes) +{ int i = 0, j = 0, zero_cnt = 0; - for (j = 0; j < h264_data_bytes; j++) { - if (zero_cnt == 2 && src[j] <= 3) { - if (src[j] == 3) { - if (j == h264_data_bytes - 1) { + for (j = 0; j < h264_data_bytes; j++) + { + if (zero_cnt == 2 && src[j] <= 3) + { + if (src[j] == 3) + { + if (j == h264_data_bytes - 1) + { // cabac_zero_word: no action - } else if (src[j + 1] <= 3) { + } + else if (src[j + 1] <= 3) + { j++; zero_cnt = 0; - } else { + } + else + { // TODO: assume end-of-nal - //return 0; + // return 0; } - } else + } + else return 0; } dst[i++] = src[j]; @@ -1915,22 +2091,25 @@ static int remove_nal_escapes(unsigned char *dst, const unsigned char *src, int else zero_cnt++; } - //while (--j > i) src[j] = 0; + // while (--j > i) src[j] = 0; return i; } /** -* Put NAL escape codes to the output bitstream -*/ -static int nal_put_esc(uint8_t *d, const uint8_t *s, int n) { + * Put NAL escape codes to the output bitstream + */ +static int nal_put_esc(uint8_t* d, const uint8_t* s, int n) +{ int i, j = 4, cntz = 0; d[0] = d[1] = d[2] = 0; - d[3] = 1; // start code - for (i = 0; i < n; i++) { + d[3] = 1; // start code + for (i = 0; i < n; i++) + { uint8_t byte = *s++; - if (cntz == 2 && byte <= 3) { + if (cntz == 2 && byte <= 3) + { d[j++] = 3; - cntz = 0; + cntz = 0; } if (byte) cntz = 0; @@ -1941,11 +2120,13 @@ static int nal_put_esc(uint8_t *d, const uint8_t *s, int n) { return j; } -static void copy_bits(bit_reader_t *bs, bs_t *bd) { +static void copy_bits(bit_reader_t* bs, bs_t* bd) +{ unsigned cb, bits; - int bit_count = remaining_bits(bs); - while (bit_count > 7) { - cb = MINIMP4_MIN(bit_count - 7, 8); + int bit_count = remaining_bits(bs); + while (bit_count > 7) + { + cb = MINIMP4_MIN(bit_count - 7, 8); bits = GetBits(cb); h264e_bs_put_bits(bd, cb, bits); bit_count -= cb; @@ -1953,24 +2134,28 @@ static void copy_bits(bit_reader_t *bs, bs_t *bd) { // cut extra zeros after stop-bit bits = GetBits(bit_count); - for (; bit_count && ~bits & 1; bit_count--) { + for (; bit_count && ~bits & 1; bit_count--) + { bits >>= 1; } - if (bit_count) { + if (bit_count) + { h264e_bs_put_bits(bd, bit_count, bits); } } -static int change_sps_id(bit_reader_t *bs, bs_t *bd, int new_id, int *old_id) { +static int change_sps_id(bit_reader_t* bs, bs_t* bd, int new_id, int* old_id) +{ unsigned bits, sps_id, i, bytes; - for (i = 0; i < 3; i++) { + for (i = 0; i < 3; i++) + { bits = GetBits(8); h264e_bs_put_bits(bd, 8, bits); } - sps_id = ue_bits(bs); // max = 31 + sps_id = ue_bits(bs); // max = 31 *old_id = sps_id; - sps_id = new_id; + sps_id = new_id; assert(sps_id <= 31); h264e_bs_put_golomb(bd, sps_id); @@ -1981,15 +2166,15 @@ static int change_sps_id(bit_reader_t *bs, bs_t *bd, int new_id, int *old_id) { return bytes; } -static int -patch_pps(h264_sps_id_patcher_t *h, bit_reader_t *bs, bs_t *bd, int new_pps_id, int *old_id) { - int bytes; +static int patch_pps(h264_sps_id_patcher_t* h, bit_reader_t* bs, bs_t* bd, int new_pps_id, int* old_id) +{ + int bytes; unsigned pps_id = ue_bits(bs); // max = 255 unsigned sps_id = ue_bits(bs); // max = 31 *old_id = pps_id; - sps_id = h->map_sps[sps_id]; - pps_id = new_pps_id; + sps_id = h->map_sps[sps_id]; + pps_id = new_pps_id; assert(sps_id <= 31); assert(pps_id <= 255); @@ -2003,10 +2188,11 @@ patch_pps(h264_sps_id_patcher_t *h, bit_reader_t *bs, bs_t *bd, int new_pps_id, return bytes; } -static void patch_slice_header(h264_sps_id_patcher_t *h, bit_reader_t *bs, bs_t *bd) { +static void patch_slice_header(h264_sps_id_patcher_t* h, bit_reader_t* bs, bs_t* bd) +{ unsigned first_mb_in_slice = ue_bits(bs); - unsigned slice_type = ue_bits(bs); - unsigned pps_id = ue_bits(bs); + unsigned slice_type = ue_bits(bs); + unsigned pps_id = ue_bits(bs); pps_id = h->map_pps[pps_id]; @@ -2018,16 +2204,16 @@ static void patch_slice_header(h264_sps_id_patcher_t *h, bit_reader_t *bs, bs_t copy_bits(bs, bd); } -static int transcode_nalu(h264_sps_id_patcher_t *h, const unsigned char *src, int nalu_bytes, - unsigned char *dst) { +static int transcode_nalu(h264_sps_id_patcher_t* h, const unsigned char* src, int nalu_bytes, unsigned char* dst) +{ int old_id; bit_reader_t bst[1]; - bs_t bdt[1]; + bs_t bdt[1]; bit_reader_t bs[1]; - bs_t bd[1]; - int payload_type = src[0] & 31; + bs_t bd[1]; + int payload_type = src[0] & 31; *dst = *src; h264e_bs_init_bits(bd, dst + 1); @@ -2035,25 +2221,26 @@ static int transcode_nalu(h264_sps_id_patcher_t *h, const unsigned char *src, in h264e_bs_init_bits(bdt, dst + 1); init_bits(bst, src + 1, nalu_bytes - 1); - switch (payload_type) { - case 7: { + switch (payload_type) + { + case 7: + { int cb = change_sps_id(bst, bdt, 0, &old_id); int id = find_mem_cache(h->sps_cache, h->sps_bytes, MINIMP4_MAX_SPS, dst + 1, cb); - if (id == -1) - return 0; + if (id == -1) return 0; h->map_sps[old_id] = id; change_sps_id(bs, bd, id, &old_id); } - break; - case 8: { + break; + case 8: + { int cb = patch_pps(h, bst, bdt, 0, &old_id); int id = find_mem_cache(h->pps_cache, h->pps_bytes, MINIMP4_MAX_PPS, dst + 1, cb); - if (id == -1) - return 0; + if (id == -1) return 0; h->map_pps[old_id] = id; patch_pps(h, bs, bd, id, &old_id); } - break; + break; case 1: case 2: case 5: @@ -2073,43 +2260,49 @@ static int transcode_nalu(h264_sps_id_patcher_t *h, const unsigned char *src, in #endif /** -* Set pointer just after start code (00 .. 00 01), or to EOF if not found: -* -* NZ NZ ... NZ 00 00 00 00 01 xx xx ... xx (EOF) -* ^ ^ -* non-zero head.............. here ....... or here if no start code found -* -*/ -static const uint8_t *find_start_code(const uint8_t *h264_data, int h264_data_bytes, int *zcount) { - const uint8_t *eof = h264_data + h264_data_bytes; - const uint8_t *p = h264_data; - do { - int zero_cnt = 1; - const uint8_t *found = (uint8_t *) memchr(p, 0, eof - p); - p = found ? found : eof; + * Set pointer just after start code (00 .. 00 01), or to EOF if not found: + * + * NZ NZ ... NZ 00 00 00 00 01 xx xx ... xx (EOF) + * ^ ^ + * non-zero head.............. here ....... or here if no start code found + * + */ +static const uint8_t* find_start_code(const uint8_t* h264_data, int h264_data_bytes, int* zcount) +{ + const uint8_t* eof = h264_data + h264_data_bytes; + const uint8_t* p = h264_data; + do + { + int zero_cnt = 1; + const uint8_t* found = (uint8_t*) memchr(p, 0, eof - p); + p = found ? found : eof; while (p + zero_cnt < eof && !p[zero_cnt]) zero_cnt++; - if (zero_cnt >= 2 && p[zero_cnt] == 1) { + if (zero_cnt >= 2 && p[zero_cnt] == 1) + { *zcount = zero_cnt + 1; return p + zero_cnt + 1; } p += zero_cnt; - } while (p < eof); + } + while (p < eof); *zcount = 0; return eof; } /** -* Locate NAL unit in given buffer, and calculate it's length -*/ -static const uint8_t * -find_nal_unit(const uint8_t *h264_data, int h264_data_bytes, int *pnal_unit_bytes) { - const uint8_t *eof = h264_data + h264_data_bytes; - int zcount; - const uint8_t *start = find_start_code(h264_data, h264_data_bytes, &zcount); - const uint8_t *stop = start; - if (start) { + * Locate NAL unit in given buffer, and calculate it's length + */ +static const uint8_t* find_nal_unit(const uint8_t* h264_data, int h264_data_bytes, int* pnal_unit_bytes) +{ + const uint8_t* eof = h264_data + h264_data_bytes; + int zcount; + const uint8_t* start = find_start_code(h264_data, h264_data_bytes, &zcount); + const uint8_t* stop = start; + if (start) + { stop = find_start_code(start, (int) (eof - start), &zcount); - while (stop > start && !stop[-1]) { + while (stop > start && !stop[-1]) + { stop--; } } @@ -2118,22 +2311,23 @@ find_nal_unit(const uint8_t *h264_data, int h264_data_bytes, int *pnal_unit_byte return start; } -int mp4_h26x_write_init(mp4_h26x_writer_t *h, MP4E_mux_t *mux, int width, int height, int is_hevc) { +int mp4_h26x_write_init(mp4_h26x_writer_t* h, MP4E_mux_t* mux, int width, int height, int is_hevc) +{ MP4E_track_t tr; - tr.track_media_kind = e_video; - tr.language[0] = 'u'; - tr.language[1] = 'n'; - tr.language[2] = 'd'; - tr.language[3] = 0; + tr.track_media_kind = e_video; + tr.language[0] = 'u'; + tr.language[1] = 'n'; + tr.language[2] = 'd'; + tr.language[3] = 0; tr.object_type_indication = is_hevc ? MP4_OBJECT_TYPE_HEVC : MP4_OBJECT_TYPE_AVC; - tr.time_scale = 90000; - tr.default_duration = 0; - tr.u.v.width = width; - tr.u.v.height = height; - h->mux_track_id = MP4E_add_track(mux, &tr); - h->mux = mux; - - h->is_hevc = is_hevc; + tr.time_scale = 90000; + tr.default_duration = 0; + tr.u.v.width = width; + tr.u.v.height = height; + h->mux_track_id = MP4E_add_track(mux, &tr); + h->mux = mux; + + h->is_hevc = is_hevc; h->need_vps = is_hevc; h->need_sps = 1; h->need_pps = 1; @@ -2144,32 +2338,34 @@ int mp4_h26x_write_init(mp4_h26x_writer_t *h, MP4E_mux_t *mux, int width, int he return MP4E_STATUS_OK; } -void mp4_h26x_write_close(mp4_h26x_writer_t *h) { +void mp4_h26x_write_close(mp4_h26x_writer_t* h) +{ #if MINIMP4_TRANSCODE_SPS_ID - h264_sps_id_patcher_t *p = &h->sps_patcher; - int i; - for (i = 0; i < MINIMP4_MAX_SPS; i++) { - if (p->sps_cache[i]) - free(p->sps_cache[i]); + h264_sps_id_patcher_t* p = &h->sps_patcher; + int i; + for (i = 0; i < MINIMP4_MAX_SPS; i++) + { + if (p->sps_cache[i]) free(p->sps_cache[i]); } - for (i = 0; i < MINIMP4_MAX_PPS; i++) { - if (p->pps_cache[i]) - free(p->pps_cache[i]); + for (i = 0; i < MINIMP4_MAX_PPS; i++) + { + if (p->pps_cache[i]) free(p->pps_cache[i]); } #endif memset(h, 0, sizeof(*h)); } -static int mp4_h265_write_nal(mp4_h26x_writer_t *h, const unsigned char *nal, int sizeof_nal, - unsigned timeStamp90kHz_next) { +static int mp4_h265_write_nal( + mp4_h26x_writer_t* h, const unsigned char* nal, int sizeof_nal, unsigned timeStamp90kHz_next) +{ int payload_type = (nal[0] >> 1) & 0x3f; - int is_intra = payload_type >= HEVC_NAL_BLA_W_LP && payload_type <= HEVC_NAL_CRA_NUT; - int err = MP4E_STATUS_OK; - //printf("payload_type=%d, intra=%d\n", payload_type, is_intra); + int is_intra = payload_type >= HEVC_NAL_BLA_W_LP && payload_type <= HEVC_NAL_CRA_NUT; + int err = MP4E_STATUS_OK; + // printf("payload_type=%d, intra=%d\n", payload_type, is_intra); - if (is_intra && !h->need_sps && !h->need_pps && !h->need_vps) - h->need_idr = 0; - switch (payload_type) { + if (is_intra && !h->need_sps && !h->need_pps && !h->need_vps) h->need_idr = 0; + switch (payload_type) + { case HEVC_NAL_VPS: MP4E_set_vps(h->mux, h->mux_track_id, nal, sizeof_nal); h->need_vps = 0; @@ -2183,23 +2379,21 @@ static int mp4_h265_write_nal(mp4_h26x_writer_t *h, const unsigned char *nal, in h->need_pps = 0; break; default: - if (h->need_vps || h->need_sps || h->need_pps || h->need_idr) { + if (h->need_vps || h->need_sps || h->need_pps || h->need_idr) + { return MP4E_STATUS_BAD_ARGUMENTS; } { - unsigned char *tmp = (unsigned char *) malloc(4 + sizeof_nal); - if (!tmp) - return MP4E_STATUS_NO_MEMORY; + unsigned char* tmp = (unsigned char*) malloc(4 + sizeof_nal); + if (!tmp) return MP4E_STATUS_NO_MEMORY; int sample_kind = MP4E_SAMPLE_DEFAULT; - tmp[0] = (unsigned char) (sizeof_nal >> 24); - tmp[1] = (unsigned char) (sizeof_nal >> 16); - tmp[2] = (unsigned char) (sizeof_nal >> 8); - tmp[3] = (unsigned char) (sizeof_nal); + tmp[0] = (unsigned char) (sizeof_nal >> 24); + tmp[1] = (unsigned char) (sizeof_nal >> 16); + tmp[2] = (unsigned char) (sizeof_nal >> 8); + tmp[3] = (unsigned char) (sizeof_nal); memcpy(tmp + 4, nal, sizeof_nal); - if (is_intra) - sample_kind = MP4E_SAMPLE_RANDOM_ACCESS; - err = MP4E_put_sample(h->mux, h->mux_track_id, tmp, 4 + sizeof_nal, - timeStamp90kHz_next, sample_kind); + if (is_intra) sample_kind = MP4E_SAMPLE_RANDOM_ACCESS; + err = MP4E_put_sample(h->mux, h->mux_track_id, tmp, 4 + sizeof_nal, timeStamp90kHz_next, sample_kind); free(tmp); } break; @@ -2207,41 +2401,44 @@ static int mp4_h265_write_nal(mp4_h26x_writer_t *h, const unsigned char *nal, in return err; } -int mp4_h26x_write_nal(mp4_h26x_writer_t *h, const unsigned char *nal, int length, - unsigned timeStamp90kHz_next) { - const unsigned char *eof = nal + length; - int payload_type, sizeof_nal, err = MP4E_STATUS_OK; - for (;; nal++) { +int mp4_h26x_write_nal(mp4_h26x_writer_t* h, const unsigned char* nal, int length, unsigned timeStamp90kHz_next) +{ + const unsigned char* eof = nal + length; + int payload_type, sizeof_nal, err = MP4E_STATUS_OK; + for (;; nal++) + { #if MINIMP4_TRANSCODE_SPS_ID unsigned char *nal1, *nal2; #endif nal = find_nal_unit(nal, (int) (eof - nal), &sizeof_nal); - if (!sizeof_nal) { + if (!sizeof_nal) + { break; } - if (h->is_hevc) { + if (h->is_hevc) + { ERR(mp4_h265_write_nal(h, nal, sizeof_nal, timeStamp90kHz_next)); continue; } payload_type = nal[0] & 31; - if (9 == payload_type) - continue; // access unit delimiter, nothing to be done + if (9 == payload_type) continue; // access unit delimiter, nothing to be done #if MINIMP4_TRANSCODE_SPS_ID // Transcode SPS, PPS and slice headers, reassigning ID's for SPS and PPS: // - assign unique ID's to different SPS and PPS // - assign same ID's to equal (except ID) SPS and PPS // - save all different SPS and PPS - nal1 = (unsigned char *) malloc(sizeof_nal * 17 / 16 + 32); - if (!nal1) - return MP4E_STATUS_NO_MEMORY; - nal2 = (unsigned char *) malloc(sizeof_nal * 17 / 16 + 32); - if (!nal2) { + nal1 = (unsigned char*) malloc(sizeof_nal * 17 / 16 + 32); + if (!nal1) return MP4E_STATUS_NO_MEMORY; + nal2 = (unsigned char*) malloc(sizeof_nal * 17 / 16 + 32); + if (!nal2) + { free(nal1); return MP4E_STATUS_NO_MEMORY; } sizeof_nal = remove_nal_escapes(nal2, nal, sizeof_nal); - if (!sizeof_nal) { - exit_with_free: + if (!sizeof_nal) + { + exit_with_free: free(nal1); free(nal2); return MP4E_STATUS_BAD_ARGUMENTS; @@ -2250,41 +2447,39 @@ int mp4_h26x_write_nal(mp4_h26x_writer_t *h, const unsigned char *nal, int lengt sizeof_nal = transcode_nalu(&h->sps_patcher, nal2, sizeof_nal, nal1); sizeof_nal = nal_put_esc(nal2, nal1, sizeof_nal); - switch (payload_type) { + switch (payload_type) + { case 7: MP4E_set_sps(h->mux, h->mux_track_id, nal2 + 4, sizeof_nal - 4); h->need_sps = 0; break; case 8: - if (h->need_sps) - goto exit_with_free; + if (h->need_sps) goto exit_with_free; MP4E_set_pps(h->mux, h->mux_track_id, nal2 + 4, sizeof_nal - 4); h->need_pps = 0; break; case 5: - if (h->need_sps) - goto exit_with_free; + if (h->need_sps) goto exit_with_free; h->need_idr = 0; // flow through default: - if (h->need_sps) - goto exit_with_free; - if (!h->need_pps && !h->need_idr) { + if (h->need_sps) goto exit_with_free; + if (!h->need_pps && !h->need_idr) + { bit_reader_t bs[1]; init_bits(bs, nal + 1, sizeof_nal - 4 - 1); unsigned first_mb_in_slice = ue_bits(bs); - //unsigned slice_type = ue_bits(bs); + // unsigned slice_type = ue_bits(bs); int sample_kind = MP4E_SAMPLE_DEFAULT; - nal2[0] = (unsigned char) ((sizeof_nal - 4) >> 24); - nal2[1] = (unsigned char) ((sizeof_nal - 4) >> 16); - nal2[2] = (unsigned char) ((sizeof_nal - 4) >> 8); - nal2[3] = (unsigned char) ((sizeof_nal - 4)); + nal2[0] = (unsigned char) ((sizeof_nal - 4) >> 24); + nal2[1] = (unsigned char) ((sizeof_nal - 4) >> 16); + nal2[2] = (unsigned char) ((sizeof_nal - 4) >> 8); + nal2[3] = (unsigned char) ((sizeof_nal - 4)); if (first_mb_in_slice) sample_kind = MP4E_SAMPLE_CONTINUATION; else if (payload_type == 5) sample_kind = MP4E_SAMPLE_RANDOM_ACCESS; - err = MP4E_put_sample(h->mux, h->mux_track_id, nal2, sizeof_nal, - timeStamp90kHz_next, sample_kind); + err = MP4E_put_sample(h->mux, h->mux_track_id, nal2, sizeof_nal, timeStamp90kHz_next, sample_kind); } break; } @@ -2293,7 +2488,8 @@ int mp4_h26x_write_nal(mp4_h26x_writer_t *h, const unsigned char *nal, int lengt #else // No SPS/PPS transcoding // This branch assumes that encoder use correct SPS/PPS ID's - switch (payload_type) { + switch (payload_type) + { case 7: MP4E_set_sps(h->mux, h->mux_track_id, nal, sizeof_nal); h->need_sps = 0; @@ -2303,67 +2499,66 @@ int mp4_h26x_write_nal(mp4_h26x_writer_t *h, const unsigned char *nal, int lengt h->need_pps = 0; break; case 5: - if (h->need_sps) - return MP4E_STATUS_BAD_ARGUMENTS; + if (h->need_sps) return MP4E_STATUS_BAD_ARGUMENTS; h->need_idr = 0; // flow through default: - if (h->need_sps) - return MP4E_STATUS_BAD_ARGUMENTS; + if (h->need_sps) return MP4E_STATUS_BAD_ARGUMENTS; if (!h->need_pps && !h->need_idr) { - bit_reader_t bs[1]; - unsigned char *tmp = (unsigned char *)malloc(4 + sizeof_nal); - if (!tmp) - return MP4E_STATUS_NO_MEMORY; + bit_reader_t bs[1]; + unsigned char* tmp = (unsigned char*) malloc(4 + sizeof_nal); + if (!tmp) return MP4E_STATUS_NO_MEMORY; init_bits(bs, nal + 1, sizeof_nal - 1); unsigned first_mb_in_slice = ue_bits(bs); - int sample_kind = MP4E_SAMPLE_DEFAULT; - tmp[0] = (unsigned char)(sizeof_nal >> 24); - tmp[1] = (unsigned char)(sizeof_nal >> 16); - tmp[2] = (unsigned char)(sizeof_nal >> 8); - tmp[3] = (unsigned char)(sizeof_nal); + int sample_kind = MP4E_SAMPLE_DEFAULT; + tmp[0] = (unsigned char) (sizeof_nal >> 24); + tmp[1] = (unsigned char) (sizeof_nal >> 16); + tmp[2] = (unsigned char) (sizeof_nal >> 8); + tmp[3] = (unsigned char) (sizeof_nal); memcpy(tmp + 4, nal, sizeof_nal); if (first_mb_in_slice) sample_kind = MP4E_SAMPLE_CONTINUATION; else if (payload_type == 5) sample_kind = MP4E_SAMPLE_RANDOM_ACCESS; - err = MP4E_put_sample(h->mux, h->mux_track_id, tmp, 4 + sizeof_nal, timeStamp90kHz_next, sample_kind); + err = + MP4E_put_sample(h->mux, h->mux_track_id, tmp, 4 + sizeof_nal, timeStamp90kHz_next, sample_kind); free(tmp); } break; } #endif - if (err) - break; + if (err) break; } return err; } #if MP4D_TRACE_SUPPORTED -# define TRACE(x) printf x +#define TRACE(x) printf x #else -# define TRACE(x) +#define TRACE(x) #endif -#define NELEM(x) (sizeof(x) / sizeof((x)[0])) +#define NELEM(x) (sizeof(x) / sizeof((x)[0])) -static int minimp4_fgets(MP4D_demux_t *mp4) { +static int minimp4_fgets(MP4D_demux_t* mp4) +{ uint8_t c; - if (mp4->read_callback(mp4->read_pos, &c, 1, mp4->token)) - return -1; + if (mp4->read_callback(mp4->read_pos, &c, 1, mp4->token)) return -1; mp4->read_pos++; return c; } /** -* Read given number of bytes from input stream -* Used to read box headers -*/ -static unsigned minimp4_read(MP4D_demux_t *mp4, int nb, int *eof_flag) { + * Read given number of bytes from input stream + * Used to read box headers + */ +static unsigned minimp4_read(MP4D_demux_t* mp4, int nb, int* eof_flag) +{ uint32_t v = 0; - int last_byte; - switch (nb) { + int last_byte; + switch (nb) + { case 4: v = (v << 8) | minimp4_fgets(mp4); case 3: @@ -2374,21 +2569,23 @@ static unsigned minimp4_read(MP4D_demux_t *mp4, int nb, int *eof_flag) { case 1: v = (v << 8) | (last_byte = minimp4_fgets(mp4)); } - if (last_byte < 0) { + if (last_byte < 0) + { *eof_flag = 1; } return v; } /** -* Read given number of bytes, but no more than *payload_bytes specifies... -* Used to read box payload -*/ -static uint32_t -read_payload(MP4D_demux_t *mp4, unsigned nb, boxsize_t *payload_bytes, int *eof_flag) { - if (*payload_bytes < nb) { + * Read given number of bytes, but no more than *payload_bytes specifies... + * Used to read box payload + */ +static uint32_t read_payload(MP4D_demux_t* mp4, unsigned nb, boxsize_t* payload_bytes, int* eof_flag) +{ + if (*payload_bytes < nb) + { *eof_flag = 1; - nb = (int) *payload_bytes; + nb = (int) *payload_bytes; } *payload_bytes -= nb; @@ -2396,48 +2593,65 @@ read_payload(MP4D_demux_t *mp4, unsigned nb, boxsize_t *payload_bytes, int *eof_ } /** -* Skips given number of bytes. -* Avoid math operations with fpos_t -*/ -static void my_fseek(MP4D_demux_t *mp4, boxsize_t pos, int *eof_flag) { + * Skips given number of bytes. + * Avoid math operations with fpos_t + */ +static void my_fseek(MP4D_demux_t* mp4, boxsize_t pos, int* eof_flag) +{ mp4->read_pos += pos; - if (mp4->read_pos >= mp4->read_size) - *eof_flag = 1; + if (mp4->read_pos >= mp4->read_size) *eof_flag = 1; } #define READ(n) read_payload(mp4, n, &payload_bytes, &eof_flag) -#define SKIP(n) { boxsize_t t = MINIMP4_MIN(payload_bytes, n); my_fseek(mp4, t, &eof_flag); payload_bytes -= t; } -#define MALLOC(t, p, size) p = (t)malloc(size); if (!(p)) { ERROR("out of memory"); } +#define SKIP(n) \ + { \ + boxsize_t t = MINIMP4_MIN(payload_bytes, n); \ + my_fseek(mp4, t, &eof_flag); \ + payload_bytes -= t; \ + } +#define MALLOC(t, p, size) \ + p = (t) malloc(size); \ + if (!(p)) \ + { \ + ERROR("out of memory"); \ + } /* -* On error: release resources. -*/ -#define RETURN_ERROR(mess) { \ - TRACE(("\nMP4 ERROR: " mess)); \ - MP4D_close(mp4); \ - return 0; \ -} + * On error: release resources. + */ +#define RETURN_ERROR(mess) \ + { \ + TRACE(("\nMP4 ERROR: " mess)); \ + MP4D_close(mp4); \ + return 0; \ + } /* -* Any errors, occurred on top-level hierarchy is passed to exit check: 'if (!mp4->track_count) ... ' -*/ -#define ERROR(mess) \ - if (!depth) \ - break; \ - else \ + * Any errors, occurred on top-level hierarchy is passed to exit check: 'if (!mp4->track_count) ... ' + */ +#define ERROR(mess) \ + if (!depth) \ + break; \ + else \ RETURN_ERROR(mess); -typedef enum { - BOX_ATOM, BOX_OD +typedef enum +{ + BOX_ATOM, + BOX_OD } boxtype_t; -int MP4D_open(MP4D_demux_t *mp4, - int (*read_callback)(int64_t offset, void *buffer, size_t size, void *token), - void *token, int64_t file_size) { +int MP4D_open( + MP4D_demux_t* mp4, + int (*read_callback)(int64_t offset, void* buffer, size_t size, void* token), + void* token, + int64_t file_size) +{ // box stack size int depth = 0; - struct { + struct + { // remaining bytes for box in the stack boxsize_t bytes; @@ -2451,129 +2665,132 @@ int MP4D_open(MP4D_demux_t *mp4, uint32_t box_path[MAX_CHUNKS_DEPTH]; #endif - int eof_flag = 0; - unsigned i; - MP4D_track_t *tr = NULL; + int eof_flag = 0; + unsigned i; + MP4D_track_t* tr = NULL; - if (!mp4 || !read_callback) { + if (!mp4 || !read_callback) + { TRACE(("\nERROR: invlaid arguments!")); return 0; } memset(mp4, 0, sizeof(MP4D_demux_t)); mp4->read_callback = read_callback; - mp4->token = token; - mp4->read_size = file_size; + mp4->token = token; + mp4->read_size = file_size; - stack[0].format = BOX_ATOM; // start with atom box - stack[0].bytes = 0; // never accessed + stack[0].format = BOX_ATOM; // start with atom box + stack[0].bytes = 0; // never accessed - do { + do + { // List of boxes, derived from 'FullBox' // ~~~~~~~~~~~~~~~~~~~~~ // need read version field and check version for these boxes - static const struct { + static const struct + { uint32_t name; unsigned max_version; unsigned use_track_flag; - } g_fullbox[] = - { + } g_fullbox[] = { #if MP4D_INFO_SUPPORTED - {BOX_mdhd, 1, 1}, - {BOX_mvhd, 1, 0}, - {BOX_hdlr, 0, 0}, - {BOX_meta, 0, - 0}, // Android can produce meta box without 'FullBox' field, comment this line to simulate the bug + {BOX_mdhd, 1, 1}, + {BOX_mvhd, 1, 0}, + {BOX_hdlr, 0, 0}, + {BOX_meta, + 0, + 0}, // Android can produce meta box without 'FullBox' field, comment this line to simulate the bug #endif #if MP4D_TRACE_TIMESTAMPS - {BOX_stts, 0, 0}, - {BOX_ctts, 0, 0}, + {BOX_stts, 0, 0}, + {BOX_ctts, 0, 0}, #endif - {BOX_stz2, 0, 1}, - {BOX_stsz, 0, 1}, - {BOX_stsc, 0, 1}, - {BOX_stco, 0, 1}, - {BOX_co64, 0, 1}, - {BOX_stsd, 0, 0}, - {BOX_esds, 0, - 1} // esds does not use track, but switches to OD mode. Check here, to avoid OD check - }; + {BOX_stz2, 0, 1}, + {BOX_stsz, 0, 1}, + {BOX_stsc, 0, 1}, + {BOX_stco, 0, 1}, + {BOX_co64, 0, 1}, + {BOX_stsd, 0, 0}, + {BOX_esds, 0, 1} // esds does not use track, but switches to OD mode. Check here, to avoid OD check + }; // List of boxes, which contains other boxes ('envelopes') // Parser will descend down for boxes in this list, otherwise parsing will proceed to // the next sibling box // OD boxes handled in the same way as atom boxes... - static const struct { - uint32_t name; + static const struct + { + uint32_t name; boxtype_t type; - } g_envelope_box[] = - { - {BOX_esds, - BOX_OD}, // TODO: BOX_esds can be used for both audio and video, but this code supports audio only! - {OD_ESD, BOX_OD}, - {OD_DCD, BOX_OD}, - {OD_DSI, BOX_OD}, - {BOX_trak, BOX_ATOM}, - {BOX_moov, BOX_ATOM}, - //{BOX_moof, BOX_ATOM}, - {BOX_mdia, BOX_ATOM}, - {BOX_tref, BOX_ATOM}, - {BOX_minf, BOX_ATOM}, - {BOX_dinf, BOX_ATOM}, - {BOX_stbl, BOX_ATOM}, - {BOX_stsd, BOX_ATOM}, - {BOX_mp4a, BOX_ATOM}, - {BOX_mp4s, BOX_ATOM}, + } g_envelope_box[] = { + {BOX_esds, + BOX_OD}, // TODO: BOX_esds can be used for both audio and video, but this code supports audio only! + {OD_ESD, BOX_OD}, + {OD_DCD, BOX_OD}, + {OD_DSI, BOX_OD}, + {BOX_trak, BOX_ATOM}, + {BOX_moov, BOX_ATOM}, + //{BOX_moof, BOX_ATOM}, + {BOX_mdia, BOX_ATOM}, + {BOX_tref, BOX_ATOM}, + {BOX_minf, BOX_ATOM}, + {BOX_dinf, BOX_ATOM}, + {BOX_stbl, BOX_ATOM}, + {BOX_stsd, BOX_ATOM}, + {BOX_mp4a, BOX_ATOM}, + {BOX_mp4s, BOX_ATOM}, #if MP4D_AVC_SUPPORTED - {BOX_mp4v, BOX_ATOM}, - {BOX_avc1, BOX_ATOM}, - //{BOX_avc2, BOX_ATOM}, - //{BOX_svc1, BOX_ATOM}, + {BOX_mp4v, BOX_ATOM}, + {BOX_avc1, BOX_ATOM}, + //{BOX_avc2, BOX_ATOM}, + //{BOX_svc1, BOX_ATOM}, #endif #if MP4D_HEVC_SUPPORTED - {BOX_hvc1, BOX_ATOM}, + {BOX_hvc1, BOX_ATOM}, #endif - {BOX_udta, BOX_ATOM}, - {BOX_meta, BOX_ATOM}, - {BOX_ilst, BOX_ATOM} - }; + {BOX_udta, BOX_ATOM}, + {BOX_meta, BOX_ATOM}, + {BOX_ilst, BOX_ATOM} + }; - uint32_t FullAtomVersionAndFlags = 0; + uint32_t FullAtomVersionAndFlags = 0; boxsize_t payload_bytes; boxsize_t box_bytes; - uint32_t box_name; + uint32_t box_name; #if MP4D_INFO_SUPPORTED - unsigned char **ptag = NULL; + unsigned char** ptag = NULL; #endif int read_bytes = 0; // Read header box type and it's length - if (stack[depth].format == BOX_ATOM) { + if (stack[depth].format == BOX_ATOM) + { box_bytes = minimp4_read(mp4, 4, &eof_flag); #if FIX_BAD_ANDROID_META_BOX - broken_android_meta_hack: + broken_android_meta_hack: #endif - if (eof_flag) - break; // normal exit + if (eof_flag) break; // normal exit - if (box_bytes >= 2 && box_bytes < 8) { + if (box_bytes >= 2 && box_bytes < 8) + { ERROR("invalid box size (broken file?)"); } - box_name = minimp4_read(mp4, 4, &eof_flag); + box_name = minimp4_read(mp4, 4, &eof_flag); read_bytes = 8; // Decode box size - if (box_bytes == 0 || // standard indication of 'till eof' size - box_bytes == - (boxsize_t) 0xFFFFFFFFU // some files uses non-standard 'till eof' signaling - ) { + if (box_bytes == 0 || // standard indication of 'till eof' size + box_bytes == (boxsize_t) 0xFFFFFFFFU // some files uses non-standard 'till eof' signaling + ) + { box_bytes = ~(boxsize_t) 0; } payload_bytes = box_bytes - 8; - if (box_bytes == 1) // 64-bit sizes + if (box_bytes == 1) // 64-bit sizes { TRACE(("\n64-bit chunk encountered")); @@ -2582,105 +2799,137 @@ int MP4D_open(MP4D_demux_t *mp4, box_bytes <<= 32; box_bytes |= minimp4_read(mp4, 4, &eof_flag); #else - if (box_bytes) { + if (box_bytes) + { ERROR("UNSUPPORTED FEATURE: MP4BoxHeader(): 64-bit boxes not supported!"); } box_bytes = minimp4_read(mp4, 4, &eof_flag); #endif - if (box_bytes < 16) { + if (box_bytes < 16) + { ERROR("invalid box size (broken file?)"); } payload_bytes = box_bytes - 16; } // Read and check box version for some boxes - for (i = 0; i < NELEM(g_fullbox); i++) { - if (box_name == g_fullbox[i].name) { + for (i = 0; i < NELEM(g_fullbox); i++) + { + if (box_name == g_fullbox[i].name) + { FullAtomVersionAndFlags = READ(4); read_bytes += 4; #if FIX_BAD_ANDROID_META_BOX // Fix invalid BOX_meta, found in some Android-produced MP4 // This branch is optional: bad box would be skipped - if (box_name == BOX_meta) { - if (FullAtomVersionAndFlags >= 8 && - FullAtomVersionAndFlags < payload_bytes) { - if (box_bytes > stack[depth].bytes) { + if (box_name == BOX_meta) + { + if (FullAtomVersionAndFlags >= 8 && FullAtomVersionAndFlags < payload_bytes) + { + if (box_bytes > stack[depth].bytes) + { ERROR("broken file structure!"); } - stack[depth].bytes -= box_bytes;; + stack[depth].bytes -= box_bytes; + ; depth++; - stack[depth].bytes = payload_bytes + 4; // +4 need for missing header + stack[depth].bytes = payload_bytes + 4; // +4 need for missing header stack[depth].format = BOX_ATOM; - box_bytes = FullAtomVersionAndFlags; + box_bytes = FullAtomVersionAndFlags; TRACE(("Bad metadata box detected (Android bug?)!\n")); goto broken_android_meta_hack; } } -#endif // FIX_BAD_ANDROID_META_BOX +#endif // FIX_BAD_ANDROID_META_BOX - if ((FullAtomVersionAndFlags >> 24) > g_fullbox[i].max_version) { + if ((FullAtomVersionAndFlags >> 24) > g_fullbox[i].max_version) + { ERROR("unsupported box version!"); } - if (g_fullbox[i].use_track_flag && !tr) { + if (g_fullbox[i].use_track_flag && !tr) + { ERROR("broken file structure!"); } } } - } else // stack[depth].format == BOX_OD + } + else // stack[depth].format == BOX_OD { int val; - box_name = OD_BASE + minimp4_read(mp4, 1, &eof_flag); // 1-byte box type + box_name = OD_BASE + minimp4_read(mp4, 1, &eof_flag); // 1-byte box type read_bytes += 1; - if (eof_flag) - break; + if (eof_flag) break; payload_bytes = 0; - box_bytes = 1; - do { + box_bytes = 1; + do + { val = minimp4_read(mp4, 1, &eof_flag); read_bytes += 1; - if (eof_flag) { + if (eof_flag) + { ERROR("premature EOF!"); } payload_bytes = (payload_bytes << 7) | (val & 0x7F); box_bytes++; - } while (val & 0x80); + } + while (val & 0x80); box_bytes += payload_bytes; } #if MP4D_TRACE_SUPPORTED - box_path[depth] = (box_name >> 24) | (box_name << 24) | ((box_name >> 8) & 0x0000FF00) | ((box_name << 8) & 0x00FF0000); - TRACE(("%2d %8d %.*s (%d bytes remains for sibilings) \n", depth, (int)box_bytes, depth*4, (char*)box_path, (int)stack[depth].bytes)); + box_path[depth] = + (box_name >> 24) | (box_name << 24) | ((box_name >> 8) & 0x0000FF00) | ((box_name << 8) & 0x00FF0000); + TRACE( + ("%2d %8d %.*s (%d bytes remains for sibilings) \n", + depth, + (int) box_bytes, + depth * 4, + (char*) box_path, + (int) stack[depth].bytes)); #endif // Check that box size <= parent size - if (depth) { + if (depth) + { // Skip box with bad size assert(box_bytes > 0); - if (box_bytes > stack[depth].bytes) { - TRACE(("Wrong %c%c%c%c box size: broken file?\n", (box_name >> 24) & 255, - (box_name >> 16) & 255, (box_name >> 8) & 255, box_name & 255)); - box_bytes = stack[depth].bytes; - box_name = 0; + if (box_bytes > stack[depth].bytes) + { + TRACE( + ("Wrong %c%c%c%c box size: broken file?\n", + (box_name >> 24) & 255, + (box_name >> 16) & 255, + (box_name >> 8) & 255, + box_name & 255)); + box_bytes = stack[depth].bytes; + box_name = 0; payload_bytes = box_bytes - read_bytes; } stack[depth].bytes -= box_bytes; } // Read box header - switch (box_name) { - case BOX_stz2: //ISO/IEC 14496-1 Page 38. Section 8.17.2 - Sample Size Box. - case BOX_stsz: { - int size = 0; + switch (box_name) + { + case BOX_stz2: // ISO/IEC 14496-1 Page 38. Section 8.17.2 - Sample Size Box. + case BOX_stsz: + { + int size = 0; uint32_t sample_size = READ(4); - tr->sample_count = READ(4); + tr->sample_count = READ(4); MALLOC(unsigned int*, tr->entry_size, tr->sample_count * 4); - for (i = 0; i < tr->sample_count; i++) { - if (box_name == BOX_stsz) { + for (i = 0; i < tr->sample_count; i++) + { + if (box_name == BOX_stsz) + { tr->entry_size[i] = (sample_size ? sample_size : READ(4)); - } else { - switch (sample_size & 0xFF) { + } + else + { + switch (sample_size & 0xFF) + { case 16: tr->entry_size[i] = READ(2); break; @@ -2688,10 +2937,13 @@ int MP4D_open(MP4D_demux_t *mp4, tr->entry_size[i] = READ(1); break; case 4: - if (i & 1) { + if (i & 1) + { tr->entry_size[i] = size & 15; - } else { - size = READ(1); + } + else + { + size = READ(1); tr->entry_size[i] = (size >> 4); } break; @@ -2699,20 +2951,24 @@ int MP4D_open(MP4D_demux_t *mp4, } } } - break; + break; - case BOX_stsc: //ISO/IEC 14496-12 Page 38. Section 8.18 - Sample To Chunk Box. + case BOX_stsc: // ISO/IEC 14496-12 Page 38. Section 8.18 - Sample To Chunk Box. tr->sample_to_chunk_count = READ(4); - MALLOC(MP4D_sample_to_chunk_t*, tr->sample_to_chunk, - tr->sample_to_chunk_count * sizeof(tr->sample_to_chunk[0])); - for (i = 0; i < tr->sample_to_chunk_count; i++) { - tr->sample_to_chunk[i].first_chunk = READ(4); + MALLOC( + MP4D_sample_to_chunk_t*, + tr->sample_to_chunk, + tr->sample_to_chunk_count * sizeof(tr->sample_to_chunk[0])); + for (i = 0; i < tr->sample_to_chunk_count; i++) + { + tr->sample_to_chunk[i].first_chunk = READ(4); tr->sample_to_chunk[i].samples_per_chunk = READ(4); - SKIP(4); // sample_description_index + SKIP(4); // sample_description_index } break; #if MP4D_TRACE_TIMESTAMPS || MP4D_TIMESTAMPS_SUPPORTED - case BOX_stts: { + case BOX_stts: + { unsigned count = READ(4); unsigned j, k = 0, ts = 0, ts_count = count; #if MP4D_TIMESTAMPS_SUPPORTED @@ -2720,49 +2976,54 @@ int MP4D_open(MP4D_demux_t *mp4, MALLOC(unsigned int*, tr->duration, ts_count * 4); #endif - for (i = 0; i < count; i++) { + for (i = 0; i < count; i++) + { unsigned sc = READ(4); - int d = READ(4); + int d = READ(4); TRACE(("sample %8d count %8d duration %8d\n", i, sc, d)); #if MP4D_TIMESTAMPS_SUPPORTED - if (k + sc > ts_count) { - ts_count = k + sc; - tr->timestamp = (unsigned int *) realloc(tr->timestamp, - ts_count * sizeof(unsigned)); - tr->duration = (unsigned int *) realloc(tr->duration, - ts_count * sizeof(unsigned)); + if (k + sc > ts_count) + { + ts_count = k + sc; + tr->timestamp = (unsigned int*) realloc(tr->timestamp, ts_count * sizeof(unsigned)); + tr->duration = (unsigned int*) realloc(tr->duration, ts_count * sizeof(unsigned)); } - for (j = 0; j < sc; j++) { - tr->duration[k] = d; + for (j = 0; j < sc; j++) + { + tr->duration[k] = d; tr->timestamp[k++] = ts; ts += d; } #endif } } - break; - case BOX_ctts: { + break; + case BOX_ctts: + { unsigned count = READ(4); - for (i = 0; i < count; i++) { + for (i = 0; i < count; i++) + { int sc = READ(4); - int d = READ(4); + int d = READ(4); (void) sc; (void) d; TRACE(("sample %8d count %8d decoding to composition offset %8d\n", i, sc, d)); } } - break; + break; #endif - case BOX_stco: //ISO/IEC 14496-12 Page 39. Section 8.19 - Chunk Offset Box. + case BOX_stco: // ISO/IEC 14496-12 Page 39. Section 8.19 - Chunk Offset Box. case BOX_co64: tr->chunk_count = READ(4); - MALLOC(MP4D_file_offset_t*, tr->chunk_offset, - tr->chunk_count * sizeof(MP4D_file_offset_t)); - for (i = 0; i < tr->chunk_count; i++) { + MALLOC(MP4D_file_offset_t*, tr->chunk_offset, tr->chunk_count * sizeof(MP4D_file_offset_t)); + for (i = 0; i < tr->chunk_count; i++) + { tr->chunk_offset[i] = READ(4); - if (box_name == BOX_co64) { + if (box_name == BOX_co64) + { #if !MP4D_64BIT_SUPPORTED - if (tr->chunk_offset[i]) { + if (tr->chunk_offset[i]) + { ERROR("UNSUPPORTED FEATURE: 64-bit chunk_offset not supported!"); } #endif @@ -2773,15 +3034,17 @@ int MP4D_open(MP4D_demux_t *mp4, break; #if MP4D_INFO_SUPPORTED - case BOX_mvhd: SKIP(((FullAtomVersionAndFlags >> 24) == 1) ? 8 + 8 : 4 + 4); - mp4->timescale = READ(4); + case BOX_mvhd: + SKIP(((FullAtomVersionAndFlags >> 24) == 1) ? 8 + 8 : 4 + 4); + mp4->timescale = READ(4); mp4->duration_hi = ((FullAtomVersionAndFlags >> 24) == 1) ? READ(4) : 0; mp4->duration_lo = READ(4); SKIP(4 + 2 + 2 + 4 * 2 + 4 * 9 + 4 * 6 + 4); break; - case BOX_mdhd: SKIP(((FullAtomVersionAndFlags >> 24) == 1) ? 8 + 8 : 4 + 4); - tr->timescale = READ(4); + case BOX_mdhd: + SKIP(((FullAtomVersionAndFlags >> 24) == 1) ? 8 + 8 : 4 + 4); + tr->timescale = READ(4); tr->duration_hi = ((FullAtomVersionAndFlags >> 24) == 1) ? READ(4) : 0; tr->duration_lo = READ(4); @@ -2797,9 +3060,9 @@ int MP4D_open(MP4D_demux_t *mp4, break; case BOX_hdlr: - if (tr) // When this box is within 'meta' box, the track may not be avaialable + if (tr) // When this box is within 'meta' box, the track may not be avaialable { - SKIP(4); // pre_defined + SKIP(4); // pre_defined tr->handler_type = READ(4); } // typically hdlr box does not contain any useful info. @@ -2807,7 +3070,8 @@ int MP4D_open(MP4D_demux_t *mp4, break; case BOX_btrt: - if (!tr) { + if (!tr) + { ERROR("broken file structure!"); } @@ -2837,24 +3101,27 @@ int MP4D_open(MP4D_demux_t *mp4, #endif - case BOX_stsd: SKIP(4); // entry_count, BOX_mp4a & BOX_mp4v boxes follows immediately + case BOX_stsd: + SKIP(4); // entry_count, BOX_mp4a & BOX_mp4v boxes follows immediately break; case BOX_mp4s: // private stream - if (!tr) { + if (!tr) + { ERROR("broken file structure!"); } - SKIP(6 * 1 + 2/*Base SampleEntry*/); + SKIP(6 * 1 + 2 /*Base SampleEntry*/); break; case BOX_mp4a: - if (!tr) { + if (!tr) + { ERROR("broken file structure!"); } #if MP4D_INFO_SUPPORTED - SKIP(6 * 1 + 2/*Base SampleEntry*/ + 4 * 2); + SKIP(6 * 1 + 2 /*Base SampleEntry*/ + 4 * 2); tr->SampleDescription.audio.channelcount = READ(2); - SKIP(2/*samplesize*/ + 2 + 2); + SKIP(2 /*samplesize*/ + 2 + 2); tr->SampleDescription.audio.samplerate_hz = READ(4) >> 16; #else SKIP(28); @@ -2863,19 +3130,20 @@ int MP4D_open(MP4D_demux_t *mp4, #if MP4D_AVC_SUPPORTED case BOX_avc1: // AVCSampleEntry extends VisualSampleEntry -// case BOX_avc2: - no test -// case BOX_svc1: - no test + // case BOX_avc2: - no test + // case BOX_svc1: - no test case BOX_mp4v: - if (!tr) { + if (!tr) + { ERROR("broken file structure!"); } #if MP4D_INFO_SUPPORTED - SKIP(6 * 1 + 2/*Base SampleEntry*/ + 2 + 2 + 4 * 3); - tr->SampleDescription.video.width = READ(2); + SKIP(6 * 1 + 2 /*Base SampleEntry*/ + 2 + 2 + 4 * 3); + tr->SampleDescription.video.width = READ(2); tr->SampleDescription.video.height = READ(2); // frame_count is always 1 // compressorname is rarely set.. - SKIP(4 + 4 + 4 + 2/*frame_count*/ + 32/*compressorname*/ + 2 + 2); + SKIP(4 + 4 + 4 + 2 /*frame_count*/ + 32 /*compressorname*/ + 2 + 2); #else SKIP(78); #endif @@ -2892,16 +3160,16 @@ int MP4D_open(MP4D_demux_t *mp4, // hack: AAC-specific DSI field reused (for it have same purpoose as sps/pps) // TODO: check this hack if BOX_esds co-exist with BOX_avcC tr->object_type_indication = MP4_OBJECT_TYPE_AVC; - tr->dsi = (unsigned char *) malloc((size_t) box_bytes); - tr->dsi_bytes = (unsigned) box_bytes; + tr->dsi = (unsigned char*) malloc((size_t) box_bytes); + tr->dsi_bytes = (unsigned) box_bytes; { - int spspps; - unsigned char *p = tr->dsi; - unsigned int configurationVersion = READ(1); - unsigned int AVCProfileIndication = READ(1); - unsigned int profile_compatibility = READ(1); - unsigned int AVCLevelIndication = READ(1); - //bit(6) reserved = + int spspps; + unsigned char* p = tr->dsi; + unsigned int configurationVersion = READ(1); + unsigned int AVCProfileIndication = READ(1); + unsigned int profile_compatibility = READ(1); + unsigned int AVCLevelIndication = READ(1); + // bit(6) reserved = unsigned int lengthSizeMinusOne = READ(1) & 3; (void) configurationVersion; @@ -2910,17 +3178,21 @@ int MP4D_open(MP4D_demux_t *mp4, (void) AVCLevelIndication; (void) lengthSizeMinusOne; - for (spspps = 0; spspps < 2; spspps++) { + for (spspps = 0; spspps < 2; spspps++) + { unsigned int numOfSequenceParameterSets = READ(1); - if (!spspps) { + if (!spspps) + { numOfSequenceParameterSets &= 31; // clears 3 msb for SPS } *p++ = numOfSequenceParameterSets; - for (i = 0; i < numOfSequenceParameterSets; i++) { + for (i = 0; i < numOfSequenceParameterSets; i++) + { unsigned k, sequenceParameterSetLength = READ(2); *p++ = sequenceParameterSetLength >> 8; *p++ = sequenceParameterSetLength; - for (k = 0; k < sequenceParameterSetLength; k++) { + for (k = 0; k < sequenceParameterSetLength; k++) + { *p++ = READ(1); } } @@ -2929,44 +3201,47 @@ int MP4D_open(MP4D_demux_t *mp4, break; #endif // MP4D_AVC_SUPPORTED - case OD_ESD: { - unsigned flags = READ(3); // ES_ID(2) + flags(1) + case OD_ESD: + { + unsigned flags = READ(3); // ES_ID(2) + flags(1) - if (flags & 0x80) // steamdependflag + if (flags & 0x80) // steamdependflag { - SKIP(2); // dependsOnESID + SKIP(2); // dependsOnESID } - if (flags & 0x40) // urlflag + if (flags & 0x40) // urlflag { unsigned bytecount = READ(1); - SKIP(bytecount); // skip URL + SKIP(bytecount); // skip URL } - if (flags & 0x20) // ocrflag (was reserved in MPEG-4 v.1) + if (flags & 0x20) // ocrflag (was reserved in MPEG-4 v.1) { - SKIP(2); // OCRESID + SKIP(2); // OCRESID } break; } - case OD_DCD: //ISO/IEC 14496-1 Page 28. Section 8.6.5 - DecoderConfigDescriptor. - assert(tr); // ensured by g_fullbox[] check + case OD_DCD: // ISO/IEC 14496-1 Page 28. Section 8.6.5 - DecoderConfigDescriptor. + assert(tr); // ensured by g_fullbox[] check tr->object_type_indication = READ(1); #if MP4D_INFO_SUPPORTED tr->stream_type = READ(1) >> 2; - SKIP(3/*bufferSizeDB*/ + 4/*maxBitrate*/); + SKIP(3 /*bufferSizeDB*/ + 4 /*maxBitrate*/); tr->avg_bitrate_bps = READ(4); #else - SKIP(1+3+4+4); + SKIP(1 + 3 + 4 + 4); #endif break; - case OD_DSI: //ISO/IEC 14496-1 Page 28. Section 8.6.5 - DecoderConfigDescriptor. - assert(tr); // ensured by g_fullbox[] check - if (!tr->dsi && payload_bytes) { + case OD_DSI: // ISO/IEC 14496-1 Page 28. Section 8.6.5 - DecoderConfigDescriptor. + assert(tr); // ensured by g_fullbox[] check + if (!tr->dsi && payload_bytes) + { MALLOC(unsigned char*, tr->dsi, (int) payload_bytes); - for (i = 0; i < payload_bytes; i++) { + for (i = 0; i < payload_bytes; i++) + { tr->dsi[i] = minimp4_read(mp4, 1, - &eof_flag); // These bytes available due to check above + &eof_flag); // These bytes available due to check above } tr->dsi_bytes = i; payload_bytes -= i; @@ -2974,13 +3249,14 @@ int MP4D_open(MP4D_demux_t *mp4, } default: - TRACE(("[%c%c%c%c] %d\n", box_name >> 24, box_name >> 16, box_name - >> 8, box_name, (int) payload_bytes)); + TRACE( + ("[%c%c%c%c] %d\n", box_name >> 24, box_name >> 16, box_name >> 8, box_name, (int) payload_bytes)); } #if MP4D_INFO_SUPPORTED // Read tag is tag pointer is set - if (ptag && !*ptag && payload_bytes > 16) { + if (ptag && !*ptag && payload_bytes > 16) + { #if 0 uint32_t size = READ(4); uint32_t data = READ(4); @@ -2991,88 +3267,104 @@ int MP4D_open(MP4D_demux_t *mp4, SKIP(4 + 4 + 4 + 4); #endif MALLOC(unsigned char*, *ptag, (unsigned) payload_bytes + 1); - for (i = 0; payload_bytes != 0; i++) { + for (i = 0; payload_bytes != 0; i++) + { (*ptag)[i] = READ(1); } - (*ptag)[i] = 0; // zero-terminated string + (*ptag)[i] = 0; // zero-terminated string } #endif - if (box_name == BOX_trak) { + if (box_name == BOX_trak) + { // New track found: allocate memory using realloc() // Typically there are 1 audio track for AAC audio file, // 4 tracks for movie file, // 3-5 tracks for scalable audio (CELP+AAC) // and up to 50 tracks for BSAC scalable audio - void *mem = realloc(mp4->track, (mp4->track_count + 1) * sizeof(MP4D_track_t)); - if (!mem) { + void* mem = realloc(mp4->track, (mp4->track_count + 1) * sizeof(MP4D_track_t)); + if (!mem) + { // if realloc fails, it does not deallocate old pointer! ERROR("out of memory"); } - mp4->track = (MP4D_track_t *) mem; - tr = mp4->track + mp4->track_count++; + mp4->track = (MP4D_track_t*) mem; + tr = mp4->track + mp4->track_count++; memset(tr, 0, sizeof(MP4D_track_t)); - } else if (box_name == BOX_meta) { + } + else if (box_name == BOX_meta) + { tr = NULL; // Avoid update of 'hdlr' box, which may contains in the 'meta' box } // If this box is envelope, save it's size in box stack - for (i = 0; i < NELEM(g_envelope_box); i++) { - if (box_name == g_envelope_box[i].name) { - if (++depth >= MAX_CHUNKS_DEPTH) { + for (i = 0; i < NELEM(g_envelope_box); i++) + { + if (box_name == g_envelope_box[i].name) + { + if (++depth >= MAX_CHUNKS_DEPTH) + { ERROR("too deep atoms nesting!"); } - stack[depth].bytes = payload_bytes; + stack[depth].bytes = payload_bytes; stack[depth].format = g_envelope_box[i].type; break; } } // if box is not envelope, just skip it - if (i == NELEM(g_envelope_box)) { - if (payload_bytes > file_size) { + if (i == NELEM(g_envelope_box)) + { + if (payload_bytes > file_size) + { eof_flag = 1; - } else { + } + else + { SKIP(payload_bytes); } } // remove empty boxes from stack // don't touch box with index 0 (which indicates whole file) - while (depth > 0 && !stack[depth].bytes) { + while (depth > 0 && !stack[depth].bytes) + { depth--; } + } + while (!eof_flag); - } while (!eof_flag); - - if (!mp4->track_count) { + if (!mp4->track_count) + { RETURN_ERROR("no tracks found"); } return 1; } /** -* Find chunk, containing given sample. -* Returns chunk number, and first sample in this chunk. -*/ -static int sample_to_chunk(MP4D_track_t *tr, unsigned nsample, unsigned *nfirst_sample_in_chunk) { - unsigned chunk_group = 0, nc; - unsigned sum = 0; + * Find chunk, containing given sample. + * Returns chunk number, and first sample in this chunk. + */ +static int sample_to_chunk(MP4D_track_t* tr, unsigned nsample, unsigned* nfirst_sample_in_chunk) +{ + unsigned chunk_group = 0, nc; + unsigned sum = 0; *nfirst_sample_in_chunk = 0; - if (tr->chunk_count <= 1) { + if (tr->chunk_count <= 1) + { return 0; } - for (nc = 0; nc < tr->chunk_count; nc++) { - if (chunk_group + 1 < tr->sample_to_chunk_count // stuck at last entry till EOF - && nc + 1 == // Chunks counted starting with '1' - tr->sample_to_chunk[chunk_group + 1].first_chunk) // next group? + for (nc = 0; nc < tr->chunk_count; nc++) + { + if (chunk_group + 1 < tr->sample_to_chunk_count // stuck at last entry till EOF + && nc + 1 == // Chunks counted starting with '1' + tr->sample_to_chunk[chunk_group + 1].first_chunk) // next group? { chunk_group++; } sum += tr->sample_to_chunk[chunk_group].samples_per_chunk; - if (nsample < sum) - return nc; + if (nsample < sum) return nc; // TODO: this can be calculated once per file *nfirst_sample_in_chunk = sum; @@ -3081,34 +3373,43 @@ static int sample_to_chunk(MP4D_track_t *tr, unsigned nsample, unsigned *nfirst_ } // Exported API function -MP4D_file_offset_t -MP4D_frame_offset(const MP4D_demux_t *mp4, unsigned ntrack, unsigned nsample, unsigned *frame_bytes, - unsigned *timestamp, unsigned *duration) { - MP4D_track_t *tr = mp4->track + ntrack; - unsigned ns; - int nchunk = sample_to_chunk(tr, nsample, &ns); +MP4D_file_offset_t MP4D_frame_offset( + const MP4D_demux_t* mp4, + unsigned ntrack, + unsigned nsample, + unsigned* frame_bytes, + unsigned* timestamp, + unsigned* duration) +{ + MP4D_track_t* tr = mp4->track + ntrack; + unsigned ns; + int nchunk = sample_to_chunk(tr, nsample, &ns); MP4D_file_offset_t offset; - if (nchunk < 0) { + if (nchunk < 0) + { *frame_bytes = 0; return 0; } offset = tr->chunk_offset[nchunk]; - for (; ns < nsample; ns++) { + for (; ns < nsample; ns++) + { offset += tr->entry_size[ns]; } *frame_bytes = tr->entry_size[ns]; - if (timestamp) { + if (timestamp) + { #if MP4D_TIMESTAMPS_SUPPORTED *timestamp = tr->timestamp[ns]; #else *timestamp = 0; #endif } - if (duration) { + if (duration) + { #if MP4D_TIMESTAMPS_SUPPORTED *duration = tr->duration[ns]; #else @@ -3119,12 +3420,19 @@ MP4D_frame_offset(const MP4D_demux_t *mp4, unsigned ntrack, unsigned nsample, un return offset; } -#define FREE(x) if (x) {free(x); x = NULL;} +#define FREE(x) \ + if (x) \ + { \ + free(x); \ + x = NULL; \ + } // Exported API function -void MP4D_close(MP4D_demux_t *mp4) { - while (mp4->track_count) { - MP4D_track_t *tr = mp4->track + --mp4->track_count; +void MP4D_close(MP4D_demux_t* mp4) +{ + while (mp4->track_count) + { + MP4D_track_t* tr = mp4->track + --mp4->track_count; FREE(tr->entry_size); #if MP4D_TIMESTAMPS_SUPPORTED FREE(tr->timestamp); @@ -3145,56 +3453,55 @@ void MP4D_close(MP4D_demux_t *mp4) { #endif } -static int skip_spspps(const unsigned char *p, int nbytes, int nskip) { +static int skip_spspps(const unsigned char* p, int nbytes, int nskip) +{ int i, k = 0; - for (i = 0; i < nskip; i++) { + for (i = 0; i < nskip; i++) + { unsigned segmbytes; - if (k > nbytes - 2) - return -1; + if (k > nbytes - 2) return -1; segmbytes = p[k] * 256 + p[k + 1]; k += 2 + segmbytes; } return k; } -static const void * -MP4D_read_spspps(const MP4D_demux_t *mp4, unsigned int ntrack, int pps_flag, int nsps, - int *sps_bytes) { - int sps_count, skip_bytes; - int bytepos = 0; - unsigned char *p = mp4->track[ntrack].dsi; - if (ntrack >= mp4->track_count) - return NULL; +static const void* MP4D_read_spspps( + const MP4D_demux_t* mp4, unsigned int ntrack, int pps_flag, int nsps, int* sps_bytes) +{ + int sps_count, skip_bytes; + int bytepos = 0; + unsigned char* p = mp4->track[ntrack].dsi; + if (ntrack >= mp4->track_count) return NULL; if (mp4->track[ntrack].object_type_indication != MP4_OBJECT_TYPE_AVC) - return NULL; // SPS/PPS are specific for AVC format only + return NULL; // SPS/PPS are specific for AVC format only - if (pps_flag) { + if (pps_flag) + { // Skip all SPS - sps_count = p[bytepos++]; + sps_count = p[bytepos++]; skip_bytes = skip_spspps(p + bytepos, mp4->track[ntrack].dsi_bytes - bytepos, sps_count); - if (skip_bytes < 0) - return NULL; + if (skip_bytes < 0) return NULL; bytepos += skip_bytes; } // Skip sps/pps before the given target sps_count = p[bytepos++]; - if (nsps >= sps_count) - return NULL; + if (nsps >= sps_count) return NULL; skip_bytes = skip_spspps(p + bytepos, mp4->track[ntrack].dsi_bytes - bytepos, nsps); - if (skip_bytes < 0) - return NULL; + if (skip_bytes < 0) return NULL; bytepos += skip_bytes; *sps_bytes = p[bytepos] * 256 + p[bytepos + 1]; return p + bytepos + 2; } - -const void *MP4D_read_sps(const MP4D_demux_t *mp4, unsigned int ntrack, int nsps, int *sps_bytes) { +const void* MP4D_read_sps(const MP4D_demux_t* mp4, unsigned int ntrack, int nsps, int* sps_bytes) +{ return MP4D_read_spspps(mp4, ntrack, 0, nsps, sps_bytes); } -const void *MP4D_read_pps(const MP4D_demux_t *mp4, unsigned int ntrack, int npps, int *pps_bytes) { +const void* MP4D_read_pps(const MP4D_demux_t* mp4, unsigned int ntrack, int npps, int* pps_bytes) +{ return MP4D_read_spspps(mp4, ntrack, 1, npps, pps_bytes); } @@ -3206,62 +3513,92 @@ const void *MP4D_read_pps(const MP4D_demux_t *mp4, unsigned int ntrack, int npps // // Decodes ISO/IEC 14496 MP4 stream type to ASCII string // -static const char *GetMP4StreamTypeName(int streamType) +static const char* GetMP4StreamTypeName(int streamType) { switch (streamType) { - case 0x00: return "Forbidden"; - case 0x01: return "ObjectDescriptorStream"; - case 0x02: return "ClockReferenceStream"; - case 0x03: return "SceneDescriptionStream"; - case 0x04: return "VisualStream"; - case 0x05: return "AudioStream"; - case 0x06: return "MPEG7Stream"; - case 0x07: return "IPMPStream"; - case 0x08: return "ObjectContentInfoStream"; - case 0x09: return "MPEGJStream"; - default: - if (streamType >= 0x20 && streamType <= 0x3F) - { - return "User private"; - } else - { - return "Reserved for ISO use"; - } + case 0x00: + return "Forbidden"; + case 0x01: + return "ObjectDescriptorStream"; + case 0x02: + return "ClockReferenceStream"; + case 0x03: + return "SceneDescriptionStream"; + case 0x04: + return "VisualStream"; + case 0x05: + return "AudioStream"; + case 0x06: + return "MPEG7Stream"; + case 0x07: + return "IPMPStream"; + case 0x08: + return "ObjectContentInfoStream"; + case 0x09: + return "MPEGJStream"; + default: + if (streamType >= 0x20 && streamType <= 0x3F) + { + return "User private"; + } + else + { + return "Reserved for ISO use"; + } } } // // Decodes ISO/IEC 14496 MP4 object type to ASCII string // -static const char *GetMP4ObjectTypeName(int objectTypeIndication) +static const char* GetMP4ObjectTypeName(int objectTypeIndication) { switch (objectTypeIndication) { - case 0x00: return "Forbidden"; - case 0x01: return "Systems ISO/IEC 14496-1"; - case 0x02: return "Systems ISO/IEC 14496-1"; - case 0x20: return "Visual ISO/IEC 14496-2"; - case 0x40: return "Audio ISO/IEC 14496-3"; - case 0x60: return "Visual ISO/IEC 13818-2 Simple Profile"; - case 0x61: return "Visual ISO/IEC 13818-2 Main Profile"; - case 0x62: return "Visual ISO/IEC 13818-2 SNR Profile"; - case 0x63: return "Visual ISO/IEC 13818-2 Spatial Profile"; - case 0x64: return "Visual ISO/IEC 13818-2 High Profile"; - case 0x65: return "Visual ISO/IEC 13818-2 422 Profile"; - case 0x66: return "Audio ISO/IEC 13818-7 Main Profile"; - case 0x67: return "Audio ISO/IEC 13818-7 LC Profile"; - case 0x68: return "Audio ISO/IEC 13818-7 SSR Profile"; - case 0x69: return "Audio ISO/IEC 13818-3"; - case 0x6A: return "Visual ISO/IEC 11172-2"; - case 0x6B: return "Audio ISO/IEC 11172-3"; - case 0x6C: return "Visual ISO/IEC 10918-1"; - case 0xFF: return "no object type specified"; - default: - if (objectTypeIndication >= 0xC0 && objectTypeIndication <= 0xFE) - return "User private"; - else - return "Reserved for ISO use"; + case 0x00: + return "Forbidden"; + case 0x01: + return "Systems ISO/IEC 14496-1"; + case 0x02: + return "Systems ISO/IEC 14496-1"; + case 0x20: + return "Visual ISO/IEC 14496-2"; + case 0x40: + return "Audio ISO/IEC 14496-3"; + case 0x60: + return "Visual ISO/IEC 13818-2 Simple Profile"; + case 0x61: + return "Visual ISO/IEC 13818-2 Main Profile"; + case 0x62: + return "Visual ISO/IEC 13818-2 SNR Profile"; + case 0x63: + return "Visual ISO/IEC 13818-2 Spatial Profile"; + case 0x64: + return "Visual ISO/IEC 13818-2 High Profile"; + case 0x65: + return "Visual ISO/IEC 13818-2 422 Profile"; + case 0x66: + return "Audio ISO/IEC 13818-7 Main Profile"; + case 0x67: + return "Audio ISO/IEC 13818-7 LC Profile"; + case 0x68: + return "Audio ISO/IEC 13818-7 SSR Profile"; + case 0x69: + return "Audio ISO/IEC 13818-3"; + case 0x6A: + return "Visual ISO/IEC 11172-2"; + case 0x6B: + return "Audio ISO/IEC 11172-3"; + case 0x6C: + return "Visual ISO/IEC 10918-1"; + case 0xFF: + return "no object type specified"; + default: + if (objectTypeIndication >= 0xC0 && objectTypeIndication <= 0xFE) + return "User private"; + else + return "Reserved for ISO use"; } } @@ -3291,14 +3628,19 @@ album = May I Sing With Me year = 1992 No|type|lng| duration | bitrate| Stream type | Object type - 0|mdir|und| 92.42 s 3980 frm| 128000| AudioStream | Audio ISO/IEC 14496-3MP4 FILE: 1 tracks found. Movie time 92.42 sec + 0|mdir|und| 92.42 s 3980 frm| 128000| AudioStream | Audio ISO/IEC 14496-3MP4 FILE: 1 tracks found. Movie +time 92.42 sec */ -void MP4D_printf_info(const MP4D_demux_t *mp4) +void MP4D_printf_info(const MP4D_demux_t* mp4) { unsigned i; - printf("\nMP4 FILE: %d tracks found. Movie time %.2f sec\n", mp4->track_count, (4294967296.0*mp4->duration_hi + mp4->duration_lo) / mp4->timescale); -#define STR_TAG(name) if (mp4->tag.name) printf("%10s = %s\n", #name, mp4->tag.name) + printf( + "\nMP4 FILE: %d tracks found. Movie time %.2f sec\n", + mp4->track_count, + (4294967296.0 * mp4->duration_hi + mp4->duration_lo) / mp4->timescale); +#define STR_TAG(name) \ + if (mp4->tag.name) printf("%10s = %s\n", #name, mp4->tag.name) STR_TAG(title); STR_TAG(artist); STR_TAG(album); @@ -3308,12 +3650,19 @@ void MP4D_printf_info(const MP4D_demux_t *mp4) printf("\nNo|type|lng| duration | bitrate| %-23s| Object type", "Stream type"); for (i = 0; i < mp4->track_count; i++) { - MP4D_track_t *tr = mp4->track + i; - - printf("\n%2d|%c%c%c%c|%c%c%c|%7.2f s %6d frm| %7d|", i, - (tr->handler_type >> 24), (tr->handler_type >> 16), (tr->handler_type >> 8), (tr->handler_type >> 0), - tr->language[0], tr->language[1], tr->language[2], - (65536.0*65536.0*tr->duration_hi + tr->duration_lo) / tr->timescale, + MP4D_track_t* tr = mp4->track + i; + + printf( + "\n%2d|%c%c%c%c|%c%c%c|%7.2f s %6d frm| %7d|", + i, + (tr->handler_type >> 24), + (tr->handler_type >> 16), + (tr->handler_type >> 8), + (tr->handler_type >> 0), + tr->language[0], + tr->language[1], + tr->language[2], + (65536.0 * 65536.0 * tr->duration_hi + tr->duration_lo) / tr->timescale, tr->sample_count, tr->avg_bitrate_bps); @@ -3322,8 +3671,12 @@ void MP4D_printf_info(const MP4D_demux_t *mp4) if (tr->handler_type == MP4D_HANDLER_TYPE_SOUN) { - printf(" - %d ch %d hz", tr->SampleDescription.audio.channelcount, tr->SampleDescription.audio.samplerate_hz); - } else if (tr->handler_type == MP4D_HANDLER_TYPE_VIDE) + printf( + " - %d ch %d hz", + tr->SampleDescription.audio.channelcount, + tr->SampleDescription.audio.samplerate_hz); + } + else if (tr->handler_type == MP4D_HANDLER_TYPE_VIDE) { printf(" - %dx%d", tr->SampleDescription.video.width, tr->SampleDescription.video.height); } @@ -3331,5 +3684,5 @@ void MP4D_printf_info(const MP4D_demux_t *mp4) printf("\n"); } -#endif // MP4D_PRINT_INFO_SUPPORTED -//#endif \ No newline at end of file +#endif // MP4D_PRINT_INFO_SUPPORTED + // #endif \ No newline at end of file diff --git a/app/videonative/src/main/cpp/parser/H26XParser.cpp b/app/videonative/src/main/cpp/parser/H26XParser.cpp index d235602..b0bd5a4 100644 --- a/app/videonative/src/main/cpp/parser/H26XParser.cpp +++ b/app/videonative/src/main/cpp/parser/H26XParser.cpp @@ -2,48 +2,62 @@ // Created by Constantin on 24.01.2018. // #include "H26XParser.h" -#include #include #include #include +#include #include -H26XParser::H26XParser(NALU_DATA_CALLBACK onNewNALU) : - onNewNALU(std::move(onNewNALU)), - mDecodeRTP(std::bind(&H26XParser::onNewNaluDataExtracted, this, std::placeholders::_1, - std::placeholders::_2, std::placeholders::_3)) { +H26XParser::H26XParser(NALU_DATA_CALLBACK onNewNALU) + : onNewNALU(std::move(onNewNALU)), + mDecodeRTP(std::bind( + &H26XParser::onNewNaluDataExtracted, + this, + std::placeholders::_1, + std::placeholders::_2, + std::placeholders::_3)) +{ } -void H26XParser::reset() { +void H26XParser::reset() +{ mDecodeRTP.reset(); - nParsedNALUs = 0; + nParsedNALUs = 0; nParsedKonfigurationFrames = 0; } -void H26XParser::parse_rtp_stream(const uint8_t *rtp_data, const size_t data_length) { +void H26XParser::parse_rtp_stream(const uint8_t* rtp_data, const size_t data_length) +{ const RTP::RTPPacket rtpPacket(rtp_data, data_length); - if (rtpPacket.header.payload == RTP_PAYLOAD_TYPE_H264) { + if (rtpPacket.header.payload == RTP_PAYLOAD_TYPE_H264) + { IS_H265 = false; mDecodeRTP.parseRTPH264toNALU(rtp_data, data_length); - } else if (rtpPacket.header.payload == RTP_PAYLOAD_TYPE_H265) { + } + else if (rtpPacket.header.payload == RTP_PAYLOAD_TYPE_H265) + { IS_H265 = true; mDecodeRTP.parseRTPH265toNALU(rtp_data, data_length); } } -void H26XParser::onNewNaluDataExtracted(const std::chrono::steady_clock::time_point creation_time, - const uint8_t *nalu_data, const int nalu_data_size) { +void H26XParser::onNewNaluDataExtracted( + const std::chrono::steady_clock::time_point creation_time, const uint8_t* nalu_data, const int nalu_data_size) +{ NALU nalu(nalu_data, nalu_data_size, IS_H265, creation_time); newNaluExtracted(nalu); } -void H26XParser::newNaluExtracted(const NALU &nalu) { - if (onNewNALU != nullptr) { +void H26XParser::newNaluExtracted(const NALU& nalu) +{ + if (onNewNALU != nullptr) + { onNewNALU(nalu); } nParsedNALUs++; const bool sps_or_pps = nalu.isSPS() || nalu.isPPS(); - if (sps_or_pps) { + if (sps_or_pps) + { nParsedKonfigurationFrames++; } } diff --git a/app/videonative/src/main/cpp/parser/H26XParser.h b/app/videonative/src/main/cpp/parser/H26XParser.h index 6058d8d..c2c045e 100644 --- a/app/videonative/src/main/cpp/parser/H26XParser.h +++ b/app/videonative/src/main/cpp/parser/H26XParser.h @@ -22,41 +22,41 @@ #include "ParseRTP.h" // -#include #include +#include - -class H26XParser { -public: +class H26XParser +{ + public: H26XParser(NALU_DATA_CALLBACK onNewNALU); - void parse_rtp_stream(const uint8_t *rtp_data, const size_t data_len); + void parse_rtp_stream(const uint8_t* rtp_data, const size_t data_len); void reset(); -public: - long nParsedNALUs = 0; + public: + long nParsedNALUs = 0; long nParsedKonfigurationFrames = 0; - //For live video set to -1 (no fps limitation), else additional latency will be generated + // For live video set to -1 (no fps limitation), else additional latency will be generated void setLimitFPS(int maxFPS); -private: - void newNaluExtracted(const NALU &nalu); + private: + void newNaluExtracted(const NALU& nalu); - void onNewNaluDataExtracted(const std::chrono::steady_clock::time_point creation_time, - const uint8_t *nalu_data, const int nalu_data_size); + void onNewNaluDataExtracted( + const std::chrono::steady_clock::time_point creation_time, const uint8_t* nalu_data, const int nalu_data_size); - const NALU_DATA_CALLBACK onNewNALU; - std::chrono::steady_clock::time_point lastFrameLimitFPS = std::chrono::steady_clock::now(); + const NALU_DATA_CALLBACK onNewNALU; + std::chrono::steady_clock::time_point lastFrameLimitFPS = std::chrono::steady_clock::now(); std::chrono::steady_clock::time_point lastTimeOnNewNALUCalled = std::chrono::steady_clock::now(); RTPDecoder mDecodeRTP; - int maxFPS = 0; + int maxFPS = 0; bool IS_H265 = false; - //First time a NALU was succesfully decoded - //std::chrono::steady_clock::time_point timeFirstNALUArrived=std::chrono::steady_clock::time_point(0); + // First time a NALU was succesfully decoded + // std::chrono::steady_clock::time_point timeFirstNALUArrived=std::chrono::steady_clock::time_point(0); }; -#endif //FPV_VR_PARSE2H264RAW_H +#endif // FPV_VR_PARSE2H264RAW_H diff --git a/app/videonative/src/main/cpp/parser/ParseRTP.cpp b/app/videonative/src/main/cpp/parser/ParseRTP.cpp index 753ae19..a0fd7fe 100644 --- a/app/videonative/src/main/cpp/parser/ParseRTP.cpp +++ b/app/videonative/src/main/cpp/parser/ParseRTP.cpp @@ -7,64 +7,80 @@ #include #include "../helper/AndroidLogger.hpp" -static int diff_between_packets(int last_packet, int curr_packet) { - if (last_packet == curr_packet) { +static int diff_between_packets(int last_packet, int curr_packet) +{ + if (last_packet == curr_packet) + { MLOGD << "Duplicate?!"; } - if (curr_packet < last_packet) { - // This is not neccessarily an error, the rtp seq nr is of type uint16_t and therefore loops around in regular intervals - //MLOGD<<"Assuming overflow"; + if (curr_packet < last_packet) + { + // This is not neccessarily an error, the rtp seq nr is of type uint16_t and therefore loops around in regular + // intervals + // MLOGD<<"Assuming overflow"; // We probably have overflown the uin16_t range of rtp const auto diff = curr_packet + UINT16_MAX + 1 - last_packet; - //MLOGD<<"last:"< sizeof(nalu_header_t)); - const nalu_header_t &nalu_header = *(const nalu_header_t *) &data[0]; - timePointStartOfReceivingNALU = std::chrono::steady_clock::now(); + const nalu_header_t& nalu_header = *(const nalu_header_t*) &data[0]; + timePointStartOfReceivingNALU = std::chrono::steady_clock::now(); // Full NALU - we can remove the 'drop packet' flag - if (flagPacketHasGoneMissing) { -// MLOGD << "Got full NALU - clearing missing packet flag"; + if (flagPacketHasGoneMissing) + { + // MLOGD << "Got full NALU - clearing missing packet flag"; flagPacketHasGoneMissing = false; } write_h264_h265_nalu_start(); - const uint8_t h264_nal_header = (uint8_t) (nalu_header.type & 0x1f) - | (nalu_header.nri << 5) - | (nalu_header.f << 7); + const uint8_t h264_nal_header = (uint8_t) (nalu_header.type & 0x1f) | (nalu_header.nri << 5) | (nalu_header.f << 7); // write the reconstructed NAL header (the h264 "type") append_nalu_data_byte(h264_nal_header); // write the rest of the data @@ -97,56 +113,67 @@ void RTPDecoder::h264_reconstruct_and_forward_one_nalu(const uint8_t *data, cons m_nalu_data_length = 0; } -void RTPDecoder::parseRTPH264toNALU(const uint8_t *rtp_data, const size_t data_length) { - //12 rtp header bytes and 1 nalu_header_t type byte - if (data_length <= sizeof(rtp_header_t) + sizeof(nalu_header_t)) { +void RTPDecoder::parseRTPH264toNALU(const uint8_t* rtp_data, const size_t data_length) +{ + // 12 rtp header bytes and 1 nalu_header_t type byte + if (data_length <= sizeof(rtp_header_t) + sizeof(nalu_header_t)) + { MLOGD << "Not enough rtp data"; return; } - //MLOGD<<"Got h264 rtp data"; + // MLOGD<<"Got h264 rtp data"; const RTP::RTPPacketH264 rtpPacket(rtp_data, data_length); - //MLOGD<<"RTP Header: "<asString(); - if (!validateRTPPacket(rtpPacket.header)) { + // MLOGD<<"RTP Header: "<asString(); + if (!validateRTPPacket(rtpPacket.header)) + { return; } - const auto &nalu_header = rtpPacket.getNALUHeaderH264(); - if (nalu_header.type == 28) { /* FU-A */ - //MLOGD<<"Got RTP H264 type 28 (fragmented) payload size:"<1){ MLOGD<<"Doing werid things"; //m_nalu_data_length+=(curr_packet_diff-1)*1024; @@ -155,30 +182,39 @@ void RTPDecoder::parseRTPH264toNALU(const uint8_t *rtp_data, const size_t data_l append_nalu_data(fu_payload, fu_payload_size); m_total_n_fragments_for_current_fu++; } - } else if (nalu_header.type > 0 && nalu_header.type < 24) { - //MLOGD<<"Got RTP H264 type [1..23] (single) payload size:"< 0 && nalu_header.type < 24) + { + // MLOGD<<"Got RTP H264 type [1..23] (single) payload size:"< offset + 3)) { + if (!(rtp_payload_size > offset + 3)) + { break; } } - } else { + } + else + { MLOGD << "Got unsupported H264 RTP packet. NALU type:" << (int) nalu_header.type; } } @@ -186,14 +222,15 @@ void RTPDecoder::parseRTPH264toNALU(const uint8_t *rtp_data, const size_t data_l // https://github.com/ireader/media-server/blob/master/librtp/payload/rtp-h265-unpack.c #define FU_START(v) (v & 0x80) -#define FU_END(v) (v & 0x40) -#define FU_NAL(v) (v & 0x3F) +#define FU_END(v) (v & 0x40) +#define FU_NAL(v) (v & 0x3F) -void RTPDecoder::h265_forward_one_nalu(const uint8_t *data, int data_size, - bool write_4_bytes_for_start_code) { +void RTPDecoder::h265_forward_one_nalu(const uint8_t* data, int data_size, bool write_4_bytes_for_start_code) +{ timePointStartOfReceivingNALU = std::chrono::steady_clock::now(); - if (flagPacketHasGoneMissing) { - //MLOGD<<"Got full NALU - clearing missing packet flag"; + if (flagPacketHasGoneMissing) + { + // MLOGD<<"Got full NALU - clearing missing packet flag"; flagPacketHasGoneMissing = false; } write_h264_h265_nalu_start(write_4_bytes_for_start_code); @@ -204,152 +241,180 @@ void RTPDecoder::h265_forward_one_nalu(const uint8_t *data, int data_size, m_nalu_data_length = 0; } -void RTPDecoder::parseRTPH265toNALU(const uint8_t *rtp_data, const size_t data_length) { +void RTPDecoder::parseRTPH265toNALU(const uint8_t* rtp_data, const size_t data_length) +{ // 12 rtp header bytes and 1 nalu_header_t type byte - if (data_length <= sizeof(rtp_header_t) + sizeof(nal_unit_header_h265_t)) { + if (data_length <= sizeof(rtp_header_t) + sizeof(nal_unit_header_h265_t)) + { MLOGD << "Not enough rtp data"; return; } - //MLOGD<<"Got h265 rtp data"; + // MLOGD<<"Got h265 rtp data"; const RTP::RTPPacketH265 rtpPacket(rtp_data, data_length); - //MLOGD<<"RTP Header: "<asString(); - if (!validateRTPPacket(rtpPacket.header)) { + // MLOGD<<"RTP Header: "<asString(); + if (!validateRTPPacket(rtpPacket.header)) + { MLOGD << "Invalid rtp packet"; return; } - const auto &nal_unit_header_h265 = rtpPacket.getNALUHeaderH265(); - if (nal_unit_header_h265.type > 50) { + const auto& nal_unit_header_h265 = rtpPacket.getNALUHeaderH265(); + if (nal_unit_header_h265.type > 50) + { MLOGD << "Unsupported (HEVC) NAL type " << (int) nal_unit_header_h265.type; return; } - if (nal_unit_header_h265.type == 48) { - //MLOGD<<"Got RTP H265 type 48 (aggregated) payload size:"< offset + 3)) { + if (!(rtp_payload_size > offset + 3)) + { break; } } return; - } else if (nal_unit_header_h265.type == 49) { + } + else if (nal_unit_header_h265.type == 49) + { // FU-X packet - //MLOGD<<"Got RTP H265 type 49 (fragmented) payload size:"<(rtp_data,rtp_data+data_length)); + } + else if (fu_header.s) + { + // MLOGD<<"start of fu packetization"; + // MLOGD<<"Bytes "<(rtp_data,rtp_data+data_length)); timePointStartOfReceivingNALU = std::chrono::steady_clock::now(); - if (flagPacketHasGoneMissing) { -// MLOGD << "Got fu-a start - clearing missing packet flag"; + if (flagPacketHasGoneMissing) + { + // MLOGD << "Got fu-a start - clearing missing packet flag"; flagPacketHasGoneMissing = false; } write_h264_h265_nalu_start(); // copy header and reconstruct ?!!! - const uint8_t *ptr = &rtp_data[sizeof(rtp_header_t)]; - uint8_t variableNoIdea = rtp_data[sizeof(rtp_header_t) + - sizeof(nal_unit_header_h265_t)]; + const uint8_t* ptr = &rtp_data[sizeof(rtp_header_t)]; + uint8_t variableNoIdea = rtp_data[sizeof(rtp_header_t) + sizeof(nal_unit_header_h265_t)]; // replace NAL Unit Type Bits - I have no idea how that works, but this manipulation works :) const uint8_t tmp_unknown = (FU_NAL(variableNoIdea) << 1) | (ptr[0] & 0x81); append_nalu_data_byte(tmp_unknown); append_nalu_data_byte(ptr[1]); // copy the rest of the data append_nalu_data(fu_payload, fu_payload_size); - } else { - //MLOGD<<"middle of fu packetization"; + } + else + { + // MLOGD<<"middle of fu packetization"; append_nalu_data(fu_payload, fu_payload_size); } - } else { + } + else + { // single NAL unit - //MLOGD<<"Got RTP H265 type any (single) payload size:"< m_curr_nalu.size()) { - MLOGD << "Weird - not enough space to write NALU. curr_size:" << m_nalu_data_length - << " append:" << data_len; +void RTPDecoder::append_nalu_data(const uint8_t* data, size_t data_len) +{ + if (m_nalu_data_length + data_len > m_curr_nalu.size()) + { + MLOGD << "Weird - not enough space to write NALU. curr_size:" << m_nalu_data_length << " append:" << data_len; return; } - uint8_t *p = &m_curr_nalu.at(m_nalu_data_length); + uint8_t* p = &m_curr_nalu.at(m_nalu_data_length); memcpy(p, data, data_len); m_nalu_data_length += data_len; } -void RTPDecoder::append_nalu_data_byte(uint8_t byte) { +void RTPDecoder::append_nalu_data_byte(uint8_t byte) +{ append_nalu_data(&byte, 1); } -void RTPDecoder::append_empty(size_t data_len) { - if (m_nalu_data_length + data_len > m_curr_nalu.size()) { - MLOGD << "Weird - not enugh space to write NALU. curr_size:" << m_nalu_data_length - << " append:" << data_len; +void RTPDecoder::append_empty(size_t data_len) +{ + if (m_nalu_data_length + data_len > m_curr_nalu.size()) + { + MLOGD << "Weird - not enugh space to write NALU. curr_size:" << m_nalu_data_length << " append:" << data_len; return; } - uint8_t *p = &m_curr_nalu.at(m_nalu_data_length); + uint8_t* p = &m_curr_nalu.at(m_nalu_data_length); std::memset(p, 0, data_len); m_nalu_data_length += data_len; } -void RTPDecoder::write_h264_h265_nalu_start(const bool use_4_bytes) { - //m_curr_nalu=std::make_shared>(); +void RTPDecoder::write_h264_h265_nalu_start(const bool use_4_bytes) +{ + // m_curr_nalu=std::make_shared>(); m_nalu_data_length = 0; - if (use_4_bytes) { + if (use_4_bytes) + { append_nalu_data_byte(0); append_nalu_data_byte(0); append_nalu_data_byte(0); append_nalu_data_byte(1); assert(m_nalu_data_length == 4); - } else { + } + else + { append_nalu_data_byte(0); append_nalu_data_byte(0); append_nalu_data_byte(1); @@ -357,33 +422,35 @@ void RTPDecoder::write_h264_h265_nalu_start(const bool use_4_bytes) { } } -bool RTPDecoder::check_has_valid_prefix(const uint8_t *nalu_data, int nalu_data_len, - bool use_4_bytes_start_code) { - if (nalu_data_len < 5) { +bool RTPDecoder::check_has_valid_prefix(const uint8_t* nalu_data, int nalu_data_len, bool use_4_bytes_start_code) +{ + if (nalu_data_len < 5) + { MLOGD << "Not a valid nalu - less than 5 bytes"; return false; } - if (use_4_bytes_start_code) { - const bool valid = nalu_data[0] == 0 && - nalu_data[1] == 0 && - nalu_data[2] == 0 && - nalu_data[3] == 1; - if (!valid) { + if (use_4_bytes_start_code) + { + const bool valid = nalu_data[0] == 0 && nalu_data[1] == 0 && nalu_data[2] == 0 && nalu_data[3] == 1; + if (!valid) + { MLOGD << "Not a valid nalu - missing start code (4 bytes)"; } return valid; - } else { - const bool valid = nalu_data[0] == 0 && - nalu_data[1] == 0 && - nalu_data[2] == 1; - if (!valid) { + } + else + { + const bool valid = nalu_data[0] == 0 && nalu_data[1] == 0 && nalu_data[2] == 1; + if (!valid) + { MLOGD << "Not a valid nalu - missing start code (3 bytes)"; } return valid; } } -bool RTPDecoder::check_curr_nalu_has_valid_prefix(bool use_4_bytes_start_code) { - uint8_t *p = &m_curr_nalu.at(0); +bool RTPDecoder::check_curr_nalu_has_valid_prefix(bool use_4_bytes_start_code) +{ + uint8_t* p = &m_curr_nalu.at(0); return check_has_valid_prefix(p, m_nalu_data_length, use_4_bytes_start_code); } diff --git a/app/videonative/src/main/cpp/parser/ParseRTP.h b/app/videonative/src/main/cpp/parser/ParseRTP.h index 02e3e57..41573c7 100644 --- a/app/videonative/src/main/cpp/parser/ParseRTP.h +++ b/app/videonative/src/main/cpp/parser/ParseRTP.h @@ -5,10 +5,10 @@ #ifndef LIVE_VIDEO_10MS_ANDROID_PARSERTP_H #define LIVE_VIDEO_10MS_ANDROID_PARSERTP_H +#include #include #include #include -#include #include "RTP.hpp" /********************************************* @@ -19,17 +19,18 @@ ** No special dependencies other than std library. ** R.n Supports single, aggregated and fragmented rtp packets for both h264 and h265. ** Data is forwarded directly via a callback for no thread scheduling overhead -**********************************************/ + **********************************************/ // Enough for pretty much any resolution/framerate we handle in OpenHD static constexpr const auto NALU_MAXLEN = 1024 * 1024; -typedef std::function RTP_FRAME_DATA_CALLBACK; +typedef std::function + RTP_FRAME_DATA_CALLBACK; -class RTPDecoder { -public: +class RTPDecoder +{ + public: // NALUs are passed on via the callback, one by one. // (Each time the callback is called, it contains exactly one NALU prefixed with the 0,0,0,1 start code) RTPDecoder(RTP_FRAME_DATA_CALLBACK cb, bool feed_incomplete_frames = false); @@ -38,27 +39,27 @@ class RTPDecoder { // if the payload is dynamic (h264 or h265) // Returns false if payload is wrong // sets the 'missing packet' flag to true if packet got lost - bool validateRTPPacket(const rtp_header_t &rtpHeader); + bool validateRTPPacket(const rtp_header_t& rtpHeader); // parse rtp h264 packet to NALU - void parseRTPH264toNALU(const uint8_t *rtp_data, const size_t data_length); + void parseRTPH264toNALU(const uint8_t* rtp_data, const size_t data_length); // parse rtp h265 packet to NALU - void parseRTPH265toNALU(const uint8_t *rtp_data, const size_t data_length); + void parseRTPH265toNALU(const uint8_t* rtp_data, const size_t data_length); // exp - void parse_rtp_mjpeg(const uint8_t *rtp_data, const size_t data_length); + void parse_rtp_mjpeg(const uint8_t* rtp_data, const size_t data_length); // reset to defaults void reset(); -private: + private: // Write 0,0,0,1 (or 0,0,1) into the start of the NALU buffer and set the length to 4 / 3 void write_h264_h265_nalu_start(bool use_4_bytes = true); // copy data_len bytes into the data buffer at the current position // and increase its size by data_len - void append_nalu_data(const uint8_t *data, size_t data_len); + void append_nalu_data(const uint8_t* data, size_t data_len); // like append_nalu_data, but for one byte void append_nalu_data_byte(uint8_t byte); @@ -70,45 +71,48 @@ class RTPDecoder { void forwardNALU(const bool isH265 = false); const RTP_FRAME_DATA_CALLBACK m_cb; - //std::shared_ptr> m_curr_nalu{}; + // std::shared_ptr> m_curr_nalu{}; std::array m_curr_nalu; - size_t m_nalu_data_length = 0; - bool m_feed_incomplete_frames; - int m_total_n_fragments_for_current_fu = 0; -private: - //TDOD: What shall we do if a start, middle or end of fu-a is missing ? - int lastSequenceNumber = -1; + size_t m_nalu_data_length = 0; + bool m_feed_incomplete_frames; + int m_total_n_fragments_for_current_fu = 0; + + private: + // TDOD: What shall we do if a start, middle or end of fu-a is missing ? + int lastSequenceNumber = -1; bool flagPacketHasGoneMissing = false; -public: + + public: // each time there is a "gap" between packets, this counter is increased - int m_n_gaps = 0; + int m_n_gaps = 0; int m_n_lost_packets = 0; // This time point is as 'early as possible' to debug the parsing time as accurately as possible. // E.g for a fu-a NALU the time point when the start fu-a was received, not when its end is received std::chrono::steady_clock::time_point timePointStartOfReceivingNALU; -private: - // reconstruct and forward a single nalu, either from a "single" or "aggregated" rtp packet (not from a fragmented packet) - // data should point to the nalu_header_t, size includes the nalu_header_t size and the following bytes that make up the nalu - void h264_reconstruct_and_forward_one_nalu(const uint8_t *data, int data_size); + + private: + // reconstruct and forward a single nalu, either from a "single" or "aggregated" rtp packet (not from a fragmented + // packet) data should point to the nalu_header_t, size includes the nalu_header_t size and the following bytes that + // make up the nalu + void h264_reconstruct_and_forward_one_nalu(const uint8_t* data, int data_size); // forward a single nalu, either froma a "single" or "aggregated" rtp packet (not from a fragmented packet) // ( In contrast to h264 we don't need the stupid reconstruction with h265) // data should point to "just" the rtp payload - void h265_forward_one_nalu(const uint8_t *data, int data_size, - bool write_4_bytes_for_start_code = true); + void h265_forward_one_nalu(const uint8_t* data, int data_size, bool write_4_bytes_for_start_code = true); // wtf - static bool check_has_valid_prefix(const uint8_t *nalu_data, int nalu_data_len, - bool use_4_bytes_start_code); + static bool check_has_valid_prefix(const uint8_t* nalu_data, int nalu_data_len, bool use_4_bytes_start_code); bool check_curr_nalu_has_valid_prefix(bool use_4_bytes_start_code); // we can clear the missing packet flag when we either receive the first packet of a fragmented rtp packet or // a non-fragmented rtp packet - //void clear_missing_packet_flag(); + // void clear_missing_packet_flag(); int curr_packet_diff = 0; -private: + + private: std::chrono::steady_clock::time_point m_last_log_wrong_rtp_payload_time = std::chrono::steady_clock::now(); }; -#endif //LIVE_VIDEO_10MS_ANDROID_PARSERTP_H +#endif // LIVE_VIDEO_10MS_ANDROID_PARSERTP_H diff --git a/app/videonative/src/main/cpp/parser/RTP.hpp b/app/videonative/src/main/cpp/parser/RTP.hpp index 94de9f5..27bce8d 100644 --- a/app/videonative/src/main/cpp/parser/RTP.hpp +++ b/app/videonative/src/main/cpp/parser/RTP.hpp @@ -17,8 +17,8 @@ static_assert(__BYTE_ORDER__ == __LITTLE_ENDIAN); // of 'interpreting bits the right way' for us // Same for both h264 and h265 // Defined in https://tools.ietf.org/html/rfc3550 -//0 1 2 3 -//0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 //+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ //|V=2|P|X| CC |M| PT | sequence number | //+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ @@ -29,33 +29,29 @@ static_assert(__BYTE_ORDER__ == __LITTLE_ENDIAN); //| contributing source (CSRC) identifiers | //| .... | //+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -struct rtp_header_t { - //For little endian - uint8_t cc: 4; // CSRC count - uint8_t extension: 1; // Extension bit - uint8_t padding: 1; // Padding bit - uint8_t version: 2; // Version, currently 2 - uint8_t payload: 7; // Payload type - uint8_t marker: 1; // Marker bit +struct rtp_header_t +{ + // For little endian + uint8_t cc : 4; // CSRC count + uint8_t extension : 1; // Extension bit + uint8_t padding : 1; // Padding bit + uint8_t version : 2; // Version, currently 2 + uint8_t payload : 7; // Payload type + uint8_t marker : 1; // Marker bit // - uint16_t sequence; // sequence number - uint32_t timestamp; // timestamp - uint32_t sources; // contributing sources -//NOTE: sequence,timestamp and sources has to be converted to the right endianness using htonl/htons -//For all the other members, I reversed the order such that it matches the byte order of the architecture - uint16_t getSequence() const { - return htons(sequence); - } + uint16_t sequence; // sequence number + uint32_t timestamp; // timestamp + uint32_t sources; // contributing sources + // NOTE: sequence,timestamp and sources has to be converted to the right endianness using htonl/htons + // For all the other members, I reversed the order such that it matches the byte order of the architecture + uint16_t getSequence() const { return htons(sequence); } - uint32_t getTimestamp() const { - return htonl(timestamp); - } + uint32_t getTimestamp() const { return htonl(timestamp); } - uint32_t getSources() const { - return htonl(sources); - } + uint32_t getSources() const { return htonl(sources); } - std::string asString() const { + std::string asString() const + { std::stringstream ss; ss << "cc" << (int) cc << "\n"; ss << "extension" << (int) extension << "\n"; @@ -68,10 +64,9 @@ struct rtp_header_t { ss << "sources" << (int) getSources() << "\n"; return ss.str(); } -} __attribute__ ((packed)); /* 12 bytes */ +} __attribute__((packed)); /* 12 bytes */ static_assert(sizeof(rtp_header_t) == 12); - //******************************************************** H264 ******************************************************** // https://tools.ietf.org/html/rfc6184 // https://www.rfc-editor.org/rfc/rfc6184#section-1.3 @@ -80,11 +75,12 @@ static_assert(sizeof(rtp_header_t) == 12); //+-+-+-+-+-+-+-+-+ //|F|NRI| Type | //+---------------+ -struct nalu_header_t { - uint8_t type: 5; - uint8_t nri: 2; - uint8_t f: 1; -} __attribute__ ((packed)); +struct nalu_header_t +{ + uint8_t type : 5; + uint8_t nri : 2; + uint8_t f : 1; +} __attribute__((packed)); static_assert(sizeof(nalu_header_t) == 1); // fu indicator and nalu_header are exactly the same ! // only in a fu packet the nalu header is named fu indicator @@ -94,12 +90,13 @@ using fu_indicator_t = nalu_header_t; //+-+-+-+-+-+-+-+-+ //|S|E|R| Type | //+---------------+ -struct fu_header_t { // removed typedef here - uint8_t type: 5; - uint8_t r: 1; - uint8_t e: 1; - uint8_t s: 1; -} __attribute__ ((packed)); +struct fu_header_t +{ // removed typedef here + uint8_t type : 5; + uint8_t r : 1; + uint8_t e : 1; + uint8_t s : 1; +} __attribute__((packed)); static_assert(sizeof(fu_header_t) == 1); //******************************************************** H265 ******************************************************** @@ -110,12 +107,13 @@ static_assert(sizeof(fu_header_t) == 1); // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // |F| Type | LayerId | TID | // +-------------+-----------------+ -struct nal_unit_header_h265_t { - uint8_t f: 1; //1st byte - uint8_t type: 6; //1st byte - uint8_t layerId: 6; //2nd byte - uint8_t tid: 3; //2nd byte -}__attribute__ ((packed)); +struct nal_unit_header_h265_t +{ + uint8_t f : 1; // 1st byte + uint8_t type : 6; // 1st byte + uint8_t layerId : 6; // 2nd byte + uint8_t tid : 3; // 2nd byte +} __attribute__((packed)); static_assert(sizeof(nal_unit_header_h265_t) == 2); // defined in 4.4.3 FU Header //+---------------+ @@ -123,16 +121,16 @@ static_assert(sizeof(nal_unit_header_h265_t) == 2); //+-+-+-+-+-+-+-+-+ //|S|E| FuType | //+---------------+ -struct fu_header_h265_t { - uint8_t fuType: 6; - uint8_t e: 1; - uint8_t s: 1; -}__attribute__ ((packed)); +struct fu_header_h265_t +{ + uint8_t fuType : 6; + uint8_t e : 1; + uint8_t s : 1; +} __attribute__((packed)); static_assert(sizeof(fu_header_h265_t) == 1); - -// ******************************************************** MJPEG ******************************************************** -// https://datatracker.ietf.org/doc/html/rfc2435 +// ******************************************************** MJPEG +// ******************************************************** https://datatracker.ietf.org/doc/html/rfc2435 // https://datatracker.ietf.org/doc/html/rfc2435#section-3.1 // 0 1 2 3 // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 @@ -141,13 +139,14 @@ static_assert(sizeof(fu_header_h265_t) == 1); //+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ //| Type | Q | Width | Height | //+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -struct jpeg_main_header_t { - uint8_t type_specific: 8; - uint32_t fragment_offset: 24; - uint8_t type: 8; - uint8_t q: 8; - uint8_t width: 8; - uint8_t height: 8; +struct jpeg_main_header_t +{ + uint8_t type_specific : 8; + uint32_t fragment_offset : 24; + uint8_t type : 8; + uint8_t q : 8; + uint8_t width : 8; + uint8_t height : 8; }; // https://datatracker.ietf.org/doc/html/rfc2435#section-3.1.7 // 0 1 2 3 @@ -155,7 +154,8 @@ struct jpeg_main_header_t { // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | Restart Interval |F|L| Restart Count | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -struct jpeg_restart_marker_header_t { +struct jpeg_restart_marker_header_t +{ uint16_t restart_interval; uint16_t f; uint16_t l; @@ -170,99 +170,106 @@ struct jpeg_restart_marker_header_t { // | Quantization Table Data | // | ... | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -struct jpeg_quant_table_header_t { +struct jpeg_quant_table_header_t +{ uint16_t mbz; uint16_t precision; uint16_t length; // quantization table data }; - // Unfortunately the payload header is the same for h264 and h265 (they don't have a type for it and catch // them both with a "generic" type. -static constexpr auto RTP_PAYLOAD_TYPE_H264 = 96; -static constexpr auto RTP_PAYLOAD_TYPE_H265 = 97; +static constexpr auto RTP_PAYLOAD_TYPE_H264 = 96; +static constexpr auto RTP_PAYLOAD_TYPE_H265 = 97; static constexpr auto RTP_PAYLOAD_TYPE_AUDIO = 98; -static constexpr auto MY_SSRC_NUM = 10; +static constexpr auto MY_SSRC_NUM = 10; -namespace RTP { +namespace RTP +{ // A RTP packet consists of the header and payload // The payload also first holds another header (the NALU header) for h264 and h265 -// And depending on this header there might be another header,but this depth is left to the H264/H265 implementation (see below) -// Constructing an RTP packet just reinterprets the memory in the right way, e.g. has no performance overhead - class RTPPacket { - public: - // construct from raw data (e.g. received via UDP) - RTPPacket(const uint8_t *rtp_data, const size_t data_length) : - header(*((rtp_header_t *) rtp_data)), - rtpPayload(&rtp_data[sizeof(rtp_header_t)]), - rtpPayloadSize(data_length - sizeof(rtp_header_t)) { - assert(data_length >= sizeof(rtp_header_t)); - // r.n we don't support padding - assert(header.padding == 0); - } +// And depending on this header there might be another header,but this depth is left to the H264/H265 implementation +// (see below) Constructing an RTP packet just reinterprets the memory in the right way, e.g. has no performance +// overhead +class RTPPacket +{ + public: + // construct from raw data (e.g. received via UDP) + RTPPacket(const uint8_t* rtp_data, const size_t data_length) + : header(*((rtp_header_t*) rtp_data)), + rtpPayload(&rtp_data[sizeof(rtp_header_t)]), + rtpPayloadSize(data_length - sizeof(rtp_header_t)) + { + assert(data_length >= sizeof(rtp_header_t)); + // r.n we don't support padding + assert(header.padding == 0); + } - // const reference to the rtp header - const rtp_header_t &header; - // pointer to the rtp payload - const uint8_t *const rtpPayload; - // size of the rtp payload - const std::size_t rtpPayloadSize; - }; + // const reference to the rtp header + const rtp_header_t& header; + // pointer to the rtp payload + const uint8_t* const rtpPayload; + // size of the rtp payload + const std::size_t rtpPayloadSize; +}; // The NALU header for h264 and h265 comes directly after the rtp header - class RTPPacketH264 : public RTPPacket { - public: - using RTPPacket::RTPPacket; +class RTPPacketH264 : public RTPPacket +{ + public: + using RTPPacket::RTPPacket; - // reference to the NALU header if packet type is H264 - const nalu_header_t &getNALUHeaderH264() const { - assert(rtpPayloadSize >= sizeof(nalu_header_t)); - return *(nalu_header_t *) rtpPayload; - } - - // the fu header comes after the rtp header and after the nal_unit_header. - // WARNING: Call this function only for fu packets ! - const fu_header_t &getFuHeader() const { - assert(getNALUHeaderH264().type == 28); - return *(fu_header_t *) &rtpPayload[sizeof(nalu_header_t)]; - } + // reference to the NALU header if packet type is H264 + const nalu_header_t& getNALUHeaderH264() const + { + assert(rtpPayloadSize >= sizeof(nalu_header_t)); + return *(nalu_header_t*) rtpPayload; + } - const uint8_t *getFuPayload() const { - return &rtpPayload[sizeof(nalu_header_t) + sizeof(fu_header_t)]; - } + // the fu header comes after the rtp header and after the nal_unit_header. + // WARNING: Call this function only for fu packets ! + const fu_header_t& getFuHeader() const + { + assert(getNALUHeaderH264().type == 28); + return *(fu_header_t*) &rtpPayload[sizeof(nalu_header_t)]; + } - std::size_t getFuPayloadSize() const { - return rtpPayloadSize - (sizeof(nalu_header_t) + sizeof(fu_header_t)); - } - }; + const uint8_t* getFuPayload() const { return &rtpPayload[sizeof(nalu_header_t) + sizeof(fu_header_t)]; } - class RTPPacketH265 : public RTPPacket { - public: - using RTPPacket::RTPPacket; + std::size_t getFuPayloadSize() const { return rtpPayloadSize - (sizeof(nalu_header_t) + sizeof(fu_header_t)); } +}; - // reference to the NALU header if packet type is H265 - const nal_unit_header_h265_t &getNALUHeaderH265() const { - assert(rtpPayloadSize >= sizeof(nal_unit_header_h265_t)); - return *(nal_unit_header_h265_t *) rtpPayload; - } +class RTPPacketH265 : public RTPPacket +{ + public: + using RTPPacket::RTPPacket; - // the fu header comes after the rtp header and after the nal_unit_header. - // WARNING: Call this function only for fu packets ! - const fu_header_h265_t &getFuHeader() const { - assert(getNALUHeaderH265().type == 49); - return *(fu_header_h265_t *) &rtpPayload[sizeof(nal_unit_header_h265_t)]; - } + // reference to the NALU header if packet type is H265 + const nal_unit_header_h265_t& getNALUHeaderH265() const + { + assert(rtpPayloadSize >= sizeof(nal_unit_header_h265_t)); + return *(nal_unit_header_h265_t*) rtpPayload; + } - const uint8_t *getFuPayload() const { - return &rtpPayload[sizeof(nal_unit_header_h265_t) + sizeof(fu_header_h265_t)]; - } + // the fu header comes after the rtp header and after the nal_unit_header. + // WARNING: Call this function only for fu packets ! + const fu_header_h265_t& getFuHeader() const + { + assert(getNALUHeaderH265().type == 49); + return *(fu_header_h265_t*) &rtpPayload[sizeof(nal_unit_header_h265_t)]; + } - std::size_t getFuPayloadSize() const { - return rtpPayloadSize - (sizeof(nal_unit_header_h265_t) + sizeof(fu_header_h265_t)); - } - }; -}// namespace RTP + const uint8_t* getFuPayload() const + { + return &rtpPayload[sizeof(nal_unit_header_h265_t) + sizeof(fu_header_h265_t)]; + } + std::size_t getFuPayloadSize() const + { + return rtpPayloadSize - (sizeof(nal_unit_header_h265_t) + sizeof(fu_header_h265_t)); + } +}; +} // namespace RTP -#endif //FPVUE_RTP_HPP +#endif // FPVUE_RTP_HPP diff --git a/app/videonative/src/main/cpp/scripts/format.sh b/app/videonative/src/main/cpp/scripts/format.sh new file mode 100755 index 0000000..5a251dd --- /dev/null +++ b/app/videonative/src/main/cpp/scripts/format.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +# Find the repository root directory. +REPO_ROOT=$(git rev-parse --show-toplevel) + +# Apply clang-format to all .cpp, .c, .h, and .hpp files within $REPO_ROOT/app/videonative/src/main/cpp, +# excluding the $REPO_ROOT/app/videonative/src/main/cpp/libs/include directory. + +find "$REPO_ROOT/app/videonative/src/main/cpp" \ + -path "$REPO_ROOT/app/videonative/src/main/cpp/libs/include" -prune -o \ + \( -type f \( -name '*.cpp' -o -name '*.c' -o -name '*.h' -o -name '*.hpp' \) -exec clang-format -i {} \; \) diff --git a/app/videonative/src/main/cpp/time_util.h b/app/videonative/src/main/cpp/time_util.h index 27cdbfc..f4852cb 100644 --- a/app/videonative/src/main/cpp/time_util.h +++ b/app/videonative/src/main/cpp/time_util.h @@ -5,20 +5,21 @@ #ifndef FPVUE_TIME_UTIL_H #define FPVUE_TIME_UTIL_H - -#include -#include #include +#include +#include /** * @return milliseconds */ -uint64_t get_time_ms() { +uint64_t get_time_ms() +{ struct timespec spec; - if (clock_gettime(1, &spec) == -1) { /* 1 is CLOCK_MONOTONIC */ + if (clock_gettime(1, &spec) == -1) + { /* 1 is CLOCK_MONOTONIC */ abort(); } return spec.tv_sec * 1000 + spec.tv_nsec / 1e6; } -#endif //FPVUE_TIME_UTIL_H +#endif // FPVUE_TIME_UTIL_H