From 680fce1f0d5e0c96d098cc7582305014450987aa Mon Sep 17 00:00:00 2001 From: iflyhere <57563846+iflyhere@users.noreply.github.com> Date: Thu, 27 Aug 2026 20:26:22 +0200 Subject: [PATCH] Fix: DVR recording leaks one heap buffer per frame onNewNALU() allocated a copy of every NALU with new uint8_t[] and pushed it into naluQueue as a NALU. NALU is documented as a non-owning view ("it does not do any memory management", NALU.hpp) and its destructor is defaulted, so popping the queue never freed anything. The buffer leaks for as long as a recording runs - roughly the video bitrate, so ~1 MB/s at 8 Mbit/s. A ten minute recording leaks a few hundred MB and the app eventually gets killed by the OOM killer, mid flight. The queue now holds an owning DvrNalu { std::vector, bool } that is moved in and out, so the bytes are freed with the queue entry and anything still queued is released when the writer thread stops. Note: the queued NALU is also read after the h265 flag was needed for mp4_h26x_write_init(), so the flag is read from the front element before the move instead. --- app/videonative/src/main/cpp/VideoPlayer.cpp | 18 ++++++++---------- app/videonative/src/main/cpp/VideoPlayer.h | 17 ++++++++++++++--- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/app/videonative/src/main/cpp/VideoPlayer.cpp b/app/videonative/src/main/cpp/VideoPlayer.cpp index 6175d585..239200a5 100644 --- a/app/videonative/src/main/cpp/VideoPlayer.cpp +++ b/app/videonative/src/main/cpp/VideoPlayer.cpp @@ -61,16 +61,15 @@ void VideoPlayer::processQueue() } if (!naluQueue.empty()) { - NALU nalu = naluQueue.front(); if (framerate == 0) { if (latestDecodingInfo.currentFPS <= 0) { continue; } + const bool is_h265 = naluQueue.front().is_h265; if (MP4E_STATUS_OK != - mp4_h26x_write_init( - &mp4wr, mux, latestVideoRatio.width, latestVideoRatio.height, nalu.IS_H265_PACKET)) + mp4_h26x_write_init(&mp4wr, mux, latestVideoRatio.width, latestVideoRatio.height, is_h265)) { __android_log_print(ANDROID_LOG_DEBUG, TAG, "error: mp4_h26x_write_init failed"); } @@ -82,12 +81,13 @@ void VideoPlayer::processQueue() framerate, latestVideoRatio.width, latestVideoRatio.height, - nalu.IS_H265_PACKET); + is_h265); } + DvrNalu nalu = std::move(naluQueue.front()); naluQueue.pop(); lock.unlock(); // Process the NALU - auto res = mp4_h26x_write_nal(&mp4wr, nalu.getData(), nalu.getSize(), 90000 / framerate); + auto res = mp4_h26x_write_nal(&mp4wr, nalu.data.data(), (int) nalu.data.size(), 90000 / framerate); if (MP4E_STATUS_OK != res) { __android_log_print(ANDROID_LOG_DEBUG, TAG, "mp4_h26x_write_nal failed with %d", res); @@ -144,11 +144,9 @@ void VideoPlayer::onNewNALU(const NALU& nalu) { return; } - // Copy data to write if from a different thread. - 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_); + // The writer thread outlives this call, so hand it an owning copy. + enqueueNALU(DvrNalu{std::vector(nalu.getData(), nalu.getData() + nalu.getSize()), + nalu.IS_H265_PACKET}); } void VideoPlayer::setVideoSurface(JNIEnv* env, jobject surface, jint i) diff --git a/app/videonative/src/main/cpp/VideoPlayer.h b/app/videonative/src/main/cpp/VideoPlayer.h index 830d423e..8d712c5a 100644 --- a/app/videonative/src/main/cpp/VideoPlayer.h +++ b/app/videonative/src/main/cpp/VideoPlayer.h @@ -9,6 +9,8 @@ #include #include #include +#include +#include #include "AudioDecoder.h" #include "BufferedPacketQueue.h" #include "UdpReceiver.h" @@ -74,9 +76,18 @@ class VideoPlayer H26XParser mParser; BufferedPacketQueue mBufferedPacketQueueVideo, mBufferedPacketQueueAudio; + // A NALU is a non-owning view onto the parser's buffer (see NALU.hpp), which is + // reused for the next packet. The DVR writer runs on its own thread, so what gets + // handed over has to own its bytes. + struct DvrNalu + { + std::vector data; + bool is_h265 = false; + }; + // DVR attributes int dvr_fd; - std::queue naluQueue; + std::queue naluQueue; std::mutex mtx; std::condition_variable cv; bool stopFlag = false; @@ -84,11 +95,11 @@ class VideoPlayer int dvr_mp4_fragmentation = 0; uint64_t last_dvr_write = 0; - void enqueueNALU(const NALU& nalu) + void enqueueNALU(DvrNalu&& nalu) { { std::lock_guard lock(mtx); - naluQueue.push(nalu); + naluQueue.push(std::move(nalu)); } cv.notify_one(); }