xuning

老版本录音实现

  1 +project(yolov8ncnn)
  2 +
  3 +cmake_minimum_required(VERSION 3.10)
  4 +
  5 +set(OpenCV_DIR ${CMAKE_SOURCE_DIR}/opencv-mobile-4.11.0-android/sdk/native/jni)
  6 +find_package(OpenCV REQUIRED core imgproc)
  7 +
  8 +set(ncnn_DIR ${CMAKE_SOURCE_DIR}/ncnn-20250503-android-vulkan/${ANDROID_ABI}/lib/cmake/ncnn)
  9 +find_package(ncnn REQUIRED)
  10 +
  11 +add_library(yolov8ncnn SHARED yolov8ncnn.cpp yolov8.cpp yolov8_det.cpp yolov8_seg.cpp yolov8_pose.cpp yolov8_cls.cpp yolov8_obb.cpp ndkcamera.cpp mp4recorder.cpp av_writer.cpp audio_recorder.cpp)
  12 +
  13 +target_link_libraries(yolov8ncnn ncnn ${OpenCV_LIBS} camera2ndk mediandk android log OpenSLES)
  1 +#include "audio_recorder.h"
  2 +#include <android/log.h>
  3 +
  4 +#define TAG "AudioRecorder"
  5 +#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__)
  6 +#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)
  7 +
  8 +AudioRecorder::AudioRecorder()
  9 + : engine_object_(nullptr)
  10 + , engine_(nullptr)
  11 + , recorder_object_(nullptr)
  12 + , recorder_(nullptr)
  13 + , buffer_queue_(nullptr)
  14 + , sample_rate_(44100)
  15 + , channels_(1)
  16 + , buffer_size_(4096)
  17 + , recording_(false)
  18 + , should_stop_(false)
  19 + , using_buffer1_(true) {
  20 +
  21 + audio_buffer1_.resize(buffer_size_);
  22 + audio_buffer2_.resize(buffer_size_);
  23 +}
  24 +
  25 +AudioRecorder::~AudioRecorder() {
  26 + stopRecording();
  27 + cleanup();
  28 +}
  29 +
  30 +bool AudioRecorder::startRecording(int sampleRate, int channels) {
  31 + std::lock_guard<std::mutex> lock(recorder_mutex_);
  32 +
  33 + if (recording_) {
  34 + LOGE("Already recording");
  35 + return false;
  36 + }
  37 +
  38 + sample_rate_ = sampleRate;
  39 + channels_ = channels;
  40 + buffer_size_ = sample_rate_ * channels_ / 10; // 100ms buffer
  41 +
  42 + audio_buffer1_.resize(buffer_size_);
  43 + audio_buffer2_.resize(buffer_size_);
  44 +
  45 + // Create OpenSL ES engine
  46 + SLresult result = slCreateEngine(&engine_object_, 0, nullptr, 0, nullptr, nullptr);
  47 + if (result != SL_RESULT_SUCCESS) {
  48 + LOGE("Failed to create OpenSL ES engine: %d", result);
  49 + return false;
  50 + }
  51 +
  52 + result = (*engine_object_)->Realize(engine_object_, SL_BOOLEAN_FALSE);
  53 + if (result != SL_RESULT_SUCCESS) {
  54 + LOGE("Failed to realize engine: %d", result);
  55 + cleanup();
  56 + return false;
  57 + }
  58 +
  59 + result = (*engine_object_)->GetInterface(engine_object_, SL_IID_ENGINE, &engine_);
  60 + if (result != SL_RESULT_SUCCESS) {
  61 + LOGE("Failed to get engine interface: %d", result);
  62 + cleanup();
  63 + return false;
  64 + }
  65 +
  66 + // Configure audio source
  67 + SLDataLocator_IODevice loc_dev = {
  68 + SL_DATALOCATOR_IODEVICE,
  69 + SL_IODEVICE_AUDIOINPUT,
  70 + SL_DEFAULTDEVICEID_AUDIOINPUT,
  71 + nullptr
  72 + };
  73 + SLDataSource audioSrc = {&loc_dev, nullptr};
  74 +
  75 + // Configure audio sink
  76 + SLDataLocator_AndroidSimpleBufferQueue loc_bq = {
  77 + SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE,
  78 + 2
  79 + };
  80 +
  81 + SLDataFormat_PCM format_pcm = {
  82 + SL_DATAFORMAT_PCM,
  83 + static_cast<SLuint32>(channels_),
  84 + static_cast<SLuint32>(sample_rate_ * 1000), // mHz
  85 + SL_PCMSAMPLEFORMAT_FIXED_16,
  86 + SL_PCMSAMPLEFORMAT_FIXED_16,
  87 + channels_ == 1 ? SL_SPEAKER_FRONT_CENTER : (SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT),
  88 + SL_BYTEORDER_LITTLEENDIAN
  89 + };
  90 +
  91 + SLDataSink audioSnk = {&loc_bq, &format_pcm};
  92 +
  93 + // Create audio recorder
  94 + const SLInterfaceID id[1] = {SL_IID_ANDROIDSIMPLEBUFFERQUEUE};
  95 + const SLboolean req[1] = {SL_BOOLEAN_TRUE};
  96 +
  97 + result = (*engine_)->CreateAudioRecorder(engine_, &recorder_object_, &audioSrc, &audioSnk, 1, id, req);
  98 + if (result != SL_RESULT_SUCCESS) {
  99 + LOGE("Failed to create audio recorder: %d", result);
  100 + cleanup();
  101 + return false;
  102 + }
  103 +
  104 + result = (*recorder_object_)->Realize(recorder_object_, SL_BOOLEAN_FALSE);
  105 + if (result != SL_RESULT_SUCCESS) {
  106 + LOGE("Failed to realize recorder: %d", result);
  107 + cleanup();
  108 + return false;
  109 + }
  110 +
  111 + result = (*recorder_object_)->GetInterface(recorder_object_, SL_IID_RECORD, &recorder_);
  112 + if (result != SL_RESULT_SUCCESS) {
  113 + LOGE("Failed to get recorder interface: %d", result);
  114 + cleanup();
  115 + return false;
  116 + }
  117 +
  118 + result = (*recorder_object_)->GetInterface(recorder_object_, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &buffer_queue_);
  119 + if (result != SL_RESULT_SUCCESS) {
  120 + LOGE("Failed to get buffer queue interface: %d", result);
  121 + cleanup();
  122 + return false;
  123 + }
  124 +
  125 + // Set callback
  126 + result = (*buffer_queue_)->RegisterCallback(buffer_queue_, audioCallback, this);
  127 + if (result != SL_RESULT_SUCCESS) {
  128 + LOGE("Failed to register callback: %d", result);
  129 + cleanup();
  130 + return false;
  131 + }
  132 +
  133 + // Start recording
  134 + recording_ = true;
  135 + should_stop_ = false;
  136 +
  137 + // Enqueue initial buffers
  138 + (*buffer_queue_)->Enqueue(buffer_queue_, audio_buffer1_.data(), audio_buffer1_.size() * sizeof(short));
  139 + (*buffer_queue_)->Enqueue(buffer_queue_, audio_buffer2_.data(), audio_buffer2_.size() * sizeof(short));
  140 +
  141 + result = (*recorder_)->SetRecordState(recorder_, SL_RECORDSTATE_RECORDING);
  142 + if (result != SL_RESULT_SUCCESS) {
  143 + LOGE("Failed to start recording: %d", result);
  144 + recording_ = false;
  145 + cleanup();
  146 + return false;
  147 + }
  148 +
  149 + LOGI("Audio recording started: %dHz, %d channels", sample_rate_, channels_);
  150 + return true;
  151 +}
  152 +
  153 +bool AudioRecorder::stopRecording() {
  154 + std::lock_guard<std::mutex> lock(recorder_mutex_);
  155 +
  156 + if (!recording_) {
  157 + return true;
  158 + }
  159 +
  160 + should_stop_ = true;
  161 + recording_ = false;
  162 +
  163 + if (recorder_) {
  164 + (*recorder_)->SetRecordState(recorder_, SL_RECORDSTATE_STOPPED);
  165 + }
  166 +
  167 + // Clear buffer queue
  168 + if (buffer_queue_) {
  169 + (*buffer_queue_)->Clear(buffer_queue_);
  170 + }
  171 +
  172 + cleanup();
  173 +
  174 + // Notify waiting threads
  175 + queue_cv_.notify_all();
  176 +
  177 + LOGI("Audio recording stopped");
  178 + return true;
  179 +}
  180 +
  181 +void AudioRecorder::audioCallback(SLAndroidSimpleBufferQueueItf bq, void* context) {
  182 + AudioRecorder* recorder = static_cast<AudioRecorder*>(context);
  183 + if (recorder && recorder->recording_) {
  184 + // Determine which buffer was just filled
  185 + std::vector<short>* current_buffer = recorder->using_buffer1_ ?
  186 + &recorder->audio_buffer1_ : &recorder->audio_buffer2_;
  187 +
  188 + // Process the audio data
  189 + recorder->processAudioData(current_buffer->data(), current_buffer->size());
  190 +
  191 + // Switch buffers
  192 + recorder->using_buffer1_ = !recorder->using_buffer1_;
  193 +
  194 + // Re-enqueue the buffer
  195 + (*bq)->Enqueue(bq, current_buffer->data(), current_buffer->size() * sizeof(short));
  196 + }
  197 +}
  198 +
  199 +void AudioRecorder::processAudioData(const short* data, size_t size) {
  200 + if (!recording_ || should_stop_) {
  201 + return;
  202 + }
  203 +
  204 + // Copy audio data to queue
  205 + std::vector<short> audio_data(data, data + size);
  206 +
  207 + {
  208 + std::lock_guard<std::mutex> lock(queue_mutex_);
  209 + audio_queue_.push(std::move(audio_data));
  210 +
  211 + // Limit queue size to prevent memory issues
  212 + while (audio_queue_.size() > 100) {
  213 + audio_queue_.pop();
  214 + }
  215 + }
  216 +
  217 + queue_cv_.notify_one();
  218 +}
  219 +
  220 +bool AudioRecorder::getAudioData(std::vector<short>& audioData) {
  221 + std::unique_lock<std::mutex> lock(queue_mutex_);
  222 +
  223 + if (audio_queue_.empty()) {
  224 + if (!recording_) {
  225 + return false;
  226 + }
  227 + // Wait for data with timeout
  228 + queue_cv_.wait_for(lock, std::chrono::milliseconds(100));
  229 + }
  230 +
  231 + if (!audio_queue_.empty()) {
  232 + audioData = std::move(audio_queue_.front());
  233 + audio_queue_.pop();
  234 + return true;
  235 + }
  236 +
  237 + return false;
  238 +}
  239 +
  240 +void AudioRecorder::cleanup() {
  241 + if (buffer_queue_) {
  242 + (*buffer_queue_)->Clear(buffer_queue_);
  243 + buffer_queue_ = nullptr;
  244 + }
  245 +
  246 + if (recorder_object_) {
  247 + (*recorder_object_)->Destroy(recorder_object_);
  248 + recorder_object_ = nullptr;
  249 + recorder_ = nullptr;
  250 + }
  251 +
  252 + if (engine_object_) {
  253 + (*engine_object_)->Destroy(engine_object_);
  254 + engine_object_ = nullptr;
  255 + engine_ = nullptr;
  256 + }
  257 +}
  1 +#ifndef AUDIO_RECORDER_H
  2 +#define AUDIO_RECORDER_H
  3 +
  4 +#include <string>
  5 +#include <thread>
  6 +#include <mutex>
  7 +#include <condition_variable>
  8 +#include <queue>
  9 +#include <vector>
  10 +#include <atomic>
  11 +#include <SLES/OpenSLES.h>
  12 +#include <SLES/OpenSLES_Android.h>
  13 +
  14 +class AudioRecorder {
  15 +public:
  16 + AudioRecorder();
  17 + ~AudioRecorder();
  18 +
  19 + bool startRecording(int sampleRate = 44100, int channels = 1);
  20 + bool stopRecording();
  21 + bool isRecording() const { return recording_; }
  22 +
  23 + // Get audio data for encoding
  24 + bool getAudioData(std::vector<short>& audioData);
  25 +
  26 + // Audio parameters
  27 + int getSampleRate() const { return sample_rate_; }
  28 + int getChannels() const { return channels_; }
  29 +
  30 +private:
  31 + static void audioCallback(SLAndroidSimpleBufferQueueItf bq, void* context);
  32 + void processAudioData(const short* data, size_t size);
  33 + void cleanup();
  34 +
  35 + // OpenSL ES objects
  36 + SLObjectItf engine_object_;
  37 + SLEngineItf engine_;
  38 + SLObjectItf recorder_object_;
  39 + SLRecordItf recorder_;
  40 + SLAndroidSimpleBufferQueueItf buffer_queue_;
  41 +
  42 + // Audio parameters
  43 + int sample_rate_;
  44 + int channels_;
  45 + int buffer_size_;
  46 +
  47 + // Recording state
  48 + std::atomic<bool> recording_;
  49 + std::atomic<bool> should_stop_;
  50 +
  51 + // Audio buffers
  52 + std::vector<short> audio_buffer1_;
  53 + std::vector<short> audio_buffer2_;
  54 + bool using_buffer1_;
  55 +
  56 + // Audio data queue
  57 + std::queue<std::vector<short>> audio_queue_;
  58 + std::mutex queue_mutex_;
  59 + std::condition_variable queue_cv_;
  60 +
  61 + // Thread safety
  62 + std::mutex recorder_mutex_;
  63 +};
  64 +
  65 +#endif // AUDIO_RECORDER_H
  1 +#include "av_writer.h"
  2 +#include <android/log.h>
  3 +#include <cstring>
  4 +
  5 +#define LOG_TAG "AVWriter"
  6 +#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
  7 +#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
  8 +
  9 +// AVI format constants
  10 +#define FOURCC(a,b,c,d) ((uint32_t)(a) | ((uint32_t)(b) << 8) | ((uint32_t)(c) << 16) | ((uint32_t)(d) << 24))
  11 +
  12 +static const uint32_t RIFF_FOURCC = FOURCC('R','I','F','F');
  13 +static const uint32_t AVI_FOURCC = FOURCC('A','V','I',' ');
  14 +static const uint32_t LIST_FOURCC = FOURCC('L','I','S','T');
  15 +static const uint32_t HDRL_FOURCC = FOURCC('h','d','r','l');
  16 +static const uint32_t AVIH_FOURCC = FOURCC('a','v','i','h');
  17 +static const uint32_t STRL_FOURCC = FOURCC('s','t','r','l');
  18 +static const uint32_t STRH_FOURCC = FOURCC('s','t','r','h');
  19 +static const uint32_t STRF_FOURCC = FOURCC('s','t','r','f');
  20 +static const uint32_t MOVI_FOURCC = FOURCC('m','o','v','i');
  21 +static const uint32_t VIDS_FOURCC = FOURCC('v','i','d','s');
  22 +static const uint32_t AUDS_FOURCC = FOURCC('a','u','d','s');
  23 +static const uint32_t DIB_FOURCC = FOURCC('D','I','B',' ');
  24 +static const uint32_t PCM_FOURCC = FOURCC('P','C','M',' ');
  25 +static const uint32_t DC00_FOURCC = FOURCC('0','0','d','c');
  26 +static const uint32_t WB01_FOURCC = FOURCC('0','1','w','b');
  27 +
  28 +AVWriter::AVWriter()
  29 + : is_open_(false)
  30 + , width_(0)
  31 + , height_(0)
  32 + , fps_(30)
  33 + , video_frame_count_(0)
  34 + , audio_enabled_(false)
  35 + , sample_rate_(44100)
  36 + , channels_(1)
  37 + , audio_sample_count_(0)
  38 + , total_video_size_(0)
  39 + , total_audio_size_(0)
  40 +{
  41 +}
  42 +
  43 +AVWriter::~AVWriter() {
  44 + close();
  45 +}
  46 +
  47 +bool AVWriter::open(const std::string& filename, int width, int height, int fps,
  48 + bool enableAudio, int sampleRate, int channels) {
  49 + if (is_open_) {
  50 + close();
  51 + }
  52 +
  53 + filename_ = filename;
  54 + width_ = width;
  55 + height_ = height;
  56 + fps_ = fps;
  57 + audio_enabled_ = enableAudio;
  58 + sample_rate_ = sampleRate;
  59 + channels_ = channels;
  60 + video_frame_count_ = 0;
  61 + audio_sample_count_ = 0;
  62 + total_video_size_ = 0;
  63 + total_audio_size_ = 0;
  64 +
  65 + // Change extension to .avi
  66 + std::string avi_filename = filename;
  67 + size_t pos = avi_filename.find_last_of('.');
  68 + if (pos != std::string::npos) {
  69 + avi_filename = avi_filename.substr(0, pos) + ".avi";
  70 + }
  71 +
  72 + file_.open(avi_filename, std::ios::binary);
  73 + if (!file_.is_open()) {
  74 + LOGE("Failed to open file: %s", avi_filename.c_str());
  75 + return false;
  76 + }
  77 +
  78 + writeAVIHeader();
  79 + is_open_ = true;
  80 +
  81 + LOGI("Opened AV file: %s (%dx%d @ %dfps) Audio: %s",
  82 + avi_filename.c_str(), width, height, fps,
  83 + audio_enabled_ ? "ON" : "OFF");
  84 + return true;
  85 +}
  86 +
  87 +bool AVWriter::writeVideoFrame(const cv::Mat& frame) {
  88 + if (!is_open_ || frame.empty()) {
  89 + return false;
  90 + }
  91 +
  92 + writeVideoFrameInternal(frame);
  93 + video_frame_count_++;
  94 + return true;
  95 +}
  96 +
  97 +bool AVWriter::writeAudioData(const std::vector<short>& audioData) {
  98 + if (!is_open_ || !audio_enabled_ || audioData.empty()) {
  99 + return false;
  100 + }
  101 +
  102 + writeAudioChunk(audioData);
  103 + audio_sample_count_ += audioData.size();
  104 + return true;
  105 +}
  106 +
  107 +void AVWriter::writeAVIHeader() {
  108 + // Calculate frame size
  109 + int frame_size = width_ * height_ * 3; // RGB24
  110 + int microsec_per_frame = 1000000 / fps_;
  111 +
  112 + // RIFF header
  113 + file_.write("RIFF", 4);
  114 + file_size_pos_ = file_.tellp();
  115 + uint32_t file_size = 0; // Will be updated later
  116 + file_.write(reinterpret_cast<const char*>(&file_size), 4);
  117 + file_.write("AVI ", 4);
  118 +
  119 + // LIST hdrl
  120 + file_.write("LIST", 4);
  121 + uint32_t hdrl_size = audio_enabled_ ? 308 : 244; // Approximate size
  122 + file_.write(reinterpret_cast<const char*>(&hdrl_size), 4);
  123 + file_.write("hdrl", 4);
  124 +
  125 + // avih (main AVI header)
  126 + file_.write("avih", 4);
  127 + uint32_t avih_size = 56;
  128 + file_.write(reinterpret_cast<const char*>(&avih_size), 4);
  129 +
  130 + uint32_t microsec_per_frame_val = microsec_per_frame;
  131 + file_.write(reinterpret_cast<const char*>(&microsec_per_frame_val), 4);
  132 + uint32_t max_bytes_per_sec = frame_size * fps_;
  133 + file_.write(reinterpret_cast<const char*>(&max_bytes_per_sec), 4);
  134 + uint32_t padding_granularity = 0;
  135 + file_.write(reinterpret_cast<const char*>(&padding_granularity), 4);
  136 + uint32_t flags = 0x10; // AVIF_HASINDEX
  137 + file_.write(reinterpret_cast<const char*>(&flags), 4);
  138 +
  139 + video_frames_pos_ = file_.tellp();
  140 + uint32_t total_frames = 0; // Will be updated later
  141 + file_.write(reinterpret_cast<const char*>(&total_frames), 4);
  142 + uint32_t initial_frames = 0;
  143 + file_.write(reinterpret_cast<const char*>(&initial_frames), 4);
  144 + uint32_t streams = audio_enabled_ ? 2 : 1;
  145 + file_.write(reinterpret_cast<const char*>(&streams), 4);
  146 + uint32_t suggested_buffer_size = frame_size;
  147 + file_.write(reinterpret_cast<const char*>(&suggested_buffer_size), 4);
  148 + uint32_t width = width_;
  149 + file_.write(reinterpret_cast<const char*>(&width), 4);
  150 + uint32_t height = height_;
  151 + file_.write(reinterpret_cast<const char*>(&height), 4);
  152 + uint32_t reserved[4] = {0, 0, 0, 0};
  153 + file_.write(reinterpret_cast<const char*>(reserved), 16);
  154 +
  155 + // Video stream header
  156 + file_.write("LIST", 4);
  157 + uint32_t strl_size = 116;
  158 + file_.write(reinterpret_cast<const char*>(&strl_size), 4);
  159 + file_.write("strl", 4);
  160 +
  161 + // strh (stream header)
  162 + file_.write("strh", 4);
  163 + uint32_t strh_size = 56;
  164 + file_.write(reinterpret_cast<const char*>(&strh_size), 4);
  165 + file_.write("vids", 4); // fccType
  166 + file_.write("DIB ", 4); // fccHandler
  167 + uint32_t stream_flags = 0;
  168 + file_.write(reinterpret_cast<const char*>(&stream_flags), 4);
  169 + uint16_t priority = 0;
  170 + file_.write(reinterpret_cast<const char*>(&priority), 2);
  171 + uint16_t language = 0;
  172 + file_.write(reinterpret_cast<const char*>(&language), 2);
  173 + uint32_t initial_frames_stream = 0;
  174 + file_.write(reinterpret_cast<const char*>(&initial_frames_stream), 4);
  175 + uint32_t scale = 1;
  176 + file_.write(reinterpret_cast<const char*>(&scale), 4);
  177 + uint32_t rate = fps_;
  178 + file_.write(reinterpret_cast<const char*>(&rate), 4);
  179 + uint32_t start = 0;
  180 + file_.write(reinterpret_cast<const char*>(&start), 4);
  181 + video_length_pos_ = file_.tellp(); // Save position to update later
  182 + uint32_t length = 0; // Will be updated later
  183 + file_.write(reinterpret_cast<const char*>(&length), 4);
  184 + uint32_t suggested_buffer_size_stream = frame_size;
  185 + file_.write(reinterpret_cast<const char*>(&suggested_buffer_size_stream), 4);
  186 + uint32_t quality = 0;
  187 + file_.write(reinterpret_cast<const char*>(&quality), 4);
  188 + uint32_t sample_size = 0;
  189 + file_.write(reinterpret_cast<const char*>(&sample_size), 4);
  190 + uint16_t left = 0, top = 0, right = width_, bottom = height_;
  191 + file_.write(reinterpret_cast<const char*>(&left), 2);
  192 + file_.write(reinterpret_cast<const char*>(&top), 2);
  193 + file_.write(reinterpret_cast<const char*>(&right), 2);
  194 + file_.write(reinterpret_cast<const char*>(&bottom), 2);
  195 +
  196 + // strf (stream format)
  197 + file_.write("strf", 4);
  198 + uint32_t strf_size = 40;
  199 + file_.write(reinterpret_cast<const char*>(&strf_size), 4);
  200 +
  201 + // BITMAPINFOHEADER
  202 + uint32_t bi_size = 40;
  203 + file_.write(reinterpret_cast<const char*>(&bi_size), 4);
  204 + int32_t bi_width = width_;
  205 + file_.write(reinterpret_cast<const char*>(&bi_width), 4);
  206 + int32_t bi_height = height_;
  207 + file_.write(reinterpret_cast<const char*>(&bi_height), 4);
  208 + uint16_t bi_planes = 1;
  209 + file_.write(reinterpret_cast<const char*>(&bi_planes), 2);
  210 + uint16_t bi_bit_count = 24;
  211 + file_.write(reinterpret_cast<const char*>(&bi_bit_count), 2);
  212 + uint32_t bi_compression = 0; // BI_RGB
  213 + file_.write(reinterpret_cast<const char*>(&bi_compression), 4);
  214 + uint32_t bi_size_image = frame_size;
  215 + file_.write(reinterpret_cast<const char*>(&bi_size_image), 4);
  216 + int32_t bi_x_pels_per_meter = 0;
  217 + file_.write(reinterpret_cast<const char*>(&bi_x_pels_per_meter), 4);
  218 + int32_t bi_y_pels_per_meter = 0;
  219 + file_.write(reinterpret_cast<const char*>(&bi_y_pels_per_meter), 4);
  220 + uint32_t bi_clr_used = 0;
  221 + file_.write(reinterpret_cast<const char*>(&bi_clr_used), 4);
  222 + uint32_t bi_clr_important = 0;
  223 + file_.write(reinterpret_cast<const char*>(&bi_clr_important), 4);
  224 +
  225 + // Audio stream header (if enabled)
  226 + if (audio_enabled_) {
  227 + file_.write("LIST", 4);
  228 + uint32_t audio_strl_size = 92;
  229 + file_.write(reinterpret_cast<const char*>(&audio_strl_size), 4);
  230 + file_.write("strl", 4);
  231 +
  232 + // Audio strh
  233 + file_.write("strh", 4);
  234 + uint32_t audio_strh_size = 56;
  235 + file_.write(reinterpret_cast<const char*>(&audio_strh_size), 4);
  236 + file_.write("auds", 4); // fccType
  237 + uint32_t audio_handler = 0;
  238 + file_.write(reinterpret_cast<const char*>(&audio_handler), 4);
  239 + uint32_t audio_stream_flags = 0;
  240 + file_.write(reinterpret_cast<const char*>(&audio_stream_flags), 4);
  241 + uint16_t audio_priority = 0;
  242 + file_.write(reinterpret_cast<const char*>(&audio_priority), 2);
  243 + uint16_t audio_language = 0;
  244 + file_.write(reinterpret_cast<const char*>(&audio_language), 2);
  245 + uint32_t audio_initial_frames = 0;
  246 + file_.write(reinterpret_cast<const char*>(&audio_initial_frames), 4);
  247 + uint32_t audio_scale = 1;
  248 + file_.write(reinterpret_cast<const char*>(&audio_scale), 4);
  249 + uint32_t audio_rate = sample_rate_;
  250 + file_.write(reinterpret_cast<const char*>(&audio_rate), 4);
  251 + uint32_t audio_start = 0;
  252 + file_.write(reinterpret_cast<const char*>(&audio_start), 4);
  253 +
  254 + audio_samples_pos_ = file_.tellp();
  255 + uint32_t audio_length = 0; // Will be updated later
  256 + file_.write(reinterpret_cast<const char*>(&audio_length), 4);
  257 + uint32_t audio_suggested_buffer_size = sample_rate_ * channels_ * 2; // 1 second buffer
  258 + file_.write(reinterpret_cast<const char*>(&audio_suggested_buffer_size), 4);
  259 + uint32_t audio_quality = 0;
  260 + file_.write(reinterpret_cast<const char*>(&audio_quality), 4);
  261 + uint32_t audio_sample_size = channels_ * 2; // 16-bit samples
  262 + file_.write(reinterpret_cast<const char*>(&audio_sample_size), 4);
  263 + uint32_t audio_reserved[2] = {0, 0};
  264 + file_.write(reinterpret_cast<const char*>(audio_reserved), 8);
  265 +
  266 + // Audio strf (WAVEFORMATEX)
  267 + file_.write("strf", 4);
  268 + uint32_t audio_strf_size = 16;
  269 + file_.write(reinterpret_cast<const char*>(&audio_strf_size), 4);
  270 +
  271 + uint16_t format_tag = 1; // PCM
  272 + file_.write(reinterpret_cast<const char*>(&format_tag), 2);
  273 + uint16_t audio_channels = channels_;
  274 + file_.write(reinterpret_cast<const char*>(&audio_channels), 2);
  275 + uint32_t samples_per_sec = sample_rate_;
  276 + file_.write(reinterpret_cast<const char*>(&samples_per_sec), 4);
  277 + uint32_t avg_bytes_per_sec = sample_rate_ * channels_ * 2;
  278 + file_.write(reinterpret_cast<const char*>(&avg_bytes_per_sec), 4);
  279 + uint16_t block_align = channels_ * 2;
  280 + file_.write(reinterpret_cast<const char*>(&block_align), 2);
  281 + uint16_t bits_per_sample = 16;
  282 + file_.write(reinterpret_cast<const char*>(&bits_per_sample), 2);
  283 + }
  284 +
  285 + // LIST movi
  286 + file_.write("LIST", 4);
  287 + movi_size_pos_ = file_.tellp();
  288 + uint32_t movi_size = 0; // Will be updated later
  289 + file_.write(reinterpret_cast<const char*>(&movi_size), 4);
  290 + file_.write("movi", 4);
  291 +
  292 + movi_list_pos_ = file_.tellp();
  293 +}
  294 +
  295 +void AVWriter::writeVideoFrameInternal(const cv::Mat& frame) {
  296 + if (frame.empty()) return;
  297 +
  298 + // Convert frame to BGR if needed and flip vertically (AVI requirement)
  299 + cv::Mat bgr_frame;
  300 + if (frame.channels() == 3) {
  301 + cv::flip(frame, bgr_frame, 0); // Flip vertically
  302 + } else if (frame.channels() == 4) {
  303 + cv::Mat temp;
  304 + cv::cvtColor(frame, temp, cv::COLOR_RGBA2BGR);
  305 + cv::flip(temp, bgr_frame, 0);
  306 + } else {
  307 + LOGE("Unsupported frame format");
  308 + return;
  309 + }
  310 +
  311 + // Resize if necessary
  312 + if (bgr_frame.cols != width_ || bgr_frame.rows != height_) {
  313 + cv::resize(bgr_frame, bgr_frame, cv::Size(width_, height_));
  314 + }
  315 +
  316 + // Write video chunk
  317 + file_.write("00dc", 4); // Video chunk ID
  318 + uint32_t chunk_size = bgr_frame.total() * bgr_frame.elemSize();
  319 + file_.write(reinterpret_cast<const char*>(&chunk_size), 4);
  320 + file_.write(reinterpret_cast<const char*>(bgr_frame.data), chunk_size);
  321 +
  322 + // Pad to even boundary
  323 + if (chunk_size % 2 == 1) {
  324 + char pad = 0;
  325 + file_.write(&pad, 1);
  326 + }
  327 +
  328 + total_video_size_ += chunk_size + 8 + (chunk_size % 2);
  329 +}
  330 +
  331 +void AVWriter::writeAudioChunk(const std::vector<short>& audioData) {
  332 + if (audioData.empty()) return;
  333 +
  334 + // Write audio chunk
  335 + file_.write("01wb", 4); // Audio chunk ID
  336 + uint32_t chunk_size = audioData.size() * sizeof(short);
  337 + file_.write(reinterpret_cast<const char*>(&chunk_size), 4);
  338 + file_.write(reinterpret_cast<const char*>(audioData.data()), chunk_size);
  339 +
  340 + // Pad to even boundary
  341 + if (chunk_size % 2 == 1) {
  342 + char pad = 0;
  343 + file_.write(&pad, 1);
  344 + }
  345 +
  346 + total_audio_size_ += chunk_size + 8 + (chunk_size % 2);
  347 +}
  348 +
  349 +void AVWriter::close() {
  350 + if (!is_open_) {
  351 + return;
  352 + }
  353 +
  354 + finalize();
  355 + file_.close();
  356 + is_open_ = false;
  357 +
  358 + LOGI("Closed AV file: %s (%d video frames, %d audio samples)",
  359 + filename_.c_str(), video_frame_count_, audio_sample_count_);
  360 +}
  361 +
  362 +void AVWriter::finalize() {
  363 + updateHeaders();
  364 +}
  365 +
  366 +void AVWriter::updateHeaders() {
  367 + std::streampos current_pos = file_.tellp();
  368 +
  369 + // Update file size
  370 + file_.seekp(file_size_pos_);
  371 + uint32_t file_size = static_cast<uint32_t>(current_pos) - 8;
  372 + file_.write(reinterpret_cast<const char*>(&file_size), 4);
  373 +
  374 + // Update total frames
  375 + file_.seekp(video_frames_pos_);
  376 + uint32_t total_frames = video_frame_count_;
  377 + file_.write(reinterpret_cast<const char*>(&total_frames), 4);
  378 +
  379 + // Update video stream length (critical for correct duration calculation)
  380 + file_.seekp(video_length_pos_);
  381 + uint32_t video_length = video_frame_count_;
  382 + file_.write(reinterpret_cast<const char*>(&video_length), 4);
  383 +
  384 + // Update audio samples if audio is enabled
  385 + if (audio_enabled_) {
  386 + file_.seekp(audio_samples_pos_);
  387 + uint32_t audio_length = audio_sample_count_;
  388 + file_.write(reinterpret_cast<const char*>(&audio_length), 4);
  389 + }
  390 +
  391 + // Update movi size
  392 + file_.seekp(movi_size_pos_);
  393 + uint32_t movi_size = total_video_size_ + total_audio_size_ + 4; // +4 for "movi"
  394 + file_.write(reinterpret_cast<const char*>(&movi_size), 4);
  395 +
  396 + // Restore position
  397 + file_.seekp(current_pos);
  398 +}
  1 +#ifndef AV_WRITER_H
  2 +#define AV_WRITER_H
  3 +
  4 +#include <string>
  5 +#include <vector>
  6 +#include <fstream>
  7 +#include <opencv2/opencv.hpp>
  8 +
  9 +class AVWriter {
  10 +public:
  11 + AVWriter();
  12 + ~AVWriter();
  13 +
  14 + bool open(const std::string& filename, int width, int height, int fps,
  15 + bool enableAudio = true, int sampleRate = 44100, int channels = 1);
  16 + bool writeVideoFrame(const cv::Mat& frame);
  17 + bool writeAudioData(const std::vector<short>& audioData);
  18 + void close();
  19 +
  20 +private:
  21 + void writeAVIHeader();
  22 + void writeVideoFrameInternal(const cv::Mat& frame);
  23 + void writeAudioChunk(const std::vector<short>& audioData);
  24 + void finalize();
  25 + void updateHeaders();
  26 +
  27 + // File handling
  28 + std::ofstream file_;
  29 + std::string filename_;
  30 + bool is_open_;
  31 +
  32 + // Video parameters
  33 + int width_;
  34 + int height_;
  35 + int fps_;
  36 + int video_frame_count_;
  37 +
  38 + // Audio parameters
  39 + bool audio_enabled_;
  40 + int sample_rate_;
  41 + int channels_;
  42 + int audio_sample_count_;
  43 +
  44 + // AVI structure tracking
  45 + std::streampos movi_list_pos_;
  46 + std::streampos file_size_pos_;
  47 + std::streampos movi_size_pos_;
  48 + std::streampos video_frames_pos_;
  49 + std::streampos video_length_pos_; // Position of video stream length field
  50 + std::streampos audio_samples_pos_;
  51 +
  52 + // Data buffers
  53 + std::vector<uint8_t> frame_buffer_;
  54 + size_t total_video_size_;
  55 + size_t total_audio_size_;
  56 +};
  57 +
  58 +#endif // AV_WRITER_H
  1 +#include "mp4recorder.h"
  2 +#include <android/log.h>
  3 +#include <chrono>
  4 +
  5 +#define LOG_TAG "MP4Recorder"
  6 +#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
  7 +#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
  8 +
  9 +MP4Recorder::MP4Recorder()
  10 + : recording_(false)
  11 + , should_stop_(false)
  12 + , audio_enabled_(false)
  13 + , frame_width_(0)
  14 + , frame_height_(0)
  15 + , target_fps_(30)
  16 + , current_fps_(1.0f) // Start with low FPS assumption
  17 + , frame_count_(0)
  18 + , fps_smoothing_factor_(0.3f) // Faster adaptation
  19 + , max_recent_frames_(10)
  20 + , use_fast_detection_(true)
  21 + , has_last_frame_(false)
  22 + , min_fps_threshold_(5)
  23 +{
  24 + recent_frame_times_.reserve(max_recent_frames_);
  25 +}
  26 +
  27 +MP4Recorder::~MP4Recorder() {
  28 + stopRecording();
  29 +}
  30 +
  31 +bool MP4Recorder::startRecording(const std::string& outputPath, int width, int height, int fps, bool enableAudio) {
  32 + if (recording_) {
  33 + LOGE("Already recording");
  34 + return false;
  35 + }
  36 +
  37 + output_path_ = outputPath;
  38 + frame_width_ = width;
  39 + frame_height_ = height;
  40 + target_fps_ = fps;
  41 + current_fps_ = fps;
  42 + audio_enabled_ = enableAudio;
  43 + should_stop_ = false;
  44 +
  45 + // Reset frame rate monitoring
  46 + frame_count_ = 0;
  47 + has_last_frame_ = false;
  48 + use_fast_detection_ = true;
  49 + current_fps_ = 1.0f; // Conservative start
  50 + recent_frame_times_.clear();
  51 + recording_start_time_ = std::chrono::steady_clock::now();
  52 + last_frame_time_ = recording_start_time_;
  53 +
  54 + // Clear frame queue
  55 + {
  56 + std::lock_guard<std::mutex> lock(queue_mutex_);
  57 + while (!frame_queue_.empty()) {
  58 + frame_queue_.pop();
  59 + }
  60 + }
  61 +
  62 + // Open AV writer (only if width/height are specified)
  63 + if (width > 0 && height > 0) {
  64 + std::lock_guard<std::mutex> lock(writer_mutex_);
  65 + if (!av_writer_.open(output_path_, width, height, fps, audio_enabled_)) {
  66 + LOGE("Failed to open AV writer");
  67 + return false;
  68 + }
  69 + }
  70 +
  71 + // Start audio recording if enabled
  72 + if (audio_enabled_) {
  73 + if (!audio_recorder_.startRecording()) {
  74 + LOGE("Failed to start audio recording");
  75 + return false;
  76 + }
  77 + // Start audio thread
  78 + audio_thread_ = std::thread(&MP4Recorder::audioThread, this);
  79 + }
  80 +
  81 + recording_ = true;
  82 +
  83 + // Start writer thread
  84 + writer_thread_ = std::thread(&MP4Recorder::writerThread, this);
  85 +
  86 + LOGI("Recording started: %s (%dx%d @ %dfps) Audio: %s",
  87 + output_path_.c_str(), width, height, fps, audio_enabled_ ? "ON" : "OFF");
  88 + return true;
  89 +}
  90 +
  91 +bool MP4Recorder::stopRecording() {
  92 + if (!recording_) {
  93 + return true;
  94 + }
  95 +
  96 + recording_ = false;
  97 + should_stop_ = true;
  98 +
  99 + // Stop audio recording
  100 + if (audio_enabled_) {
  101 + audio_recorder_.stopRecording();
  102 + if (audio_thread_.joinable()) {
  103 + audio_thread_.join();
  104 + }
  105 + }
  106 +
  107 + // Notify writer thread
  108 + queue_cv_.notify_all();
  109 +
  110 + // Wait for writer thread to finish
  111 + if (writer_thread_.joinable()) {
  112 + writer_thread_.join();
  113 + }
  114 +
  115 + // Close AV writer
  116 + {
  117 + std::lock_guard<std::mutex> lock(writer_mutex_);
  118 + av_writer_.close();
  119 + }
  120 +
  121 + LOGI("Recording stopped: %s", output_path_.c_str());
  122 + return true;
  123 +}
  124 +
  125 +bool MP4Recorder::writeFrame(const cv::Mat& frame) {
  126 + if (!recording_) {
  127 + return false;
  128 + }
  129 +
  130 + // Initialize video writer with actual frame size if not done yet
  131 + if (frame_width_ == 0 || frame_height_ == 0) {
  132 + setFrameSize(frame.cols, frame.rows);
  133 + }
  134 +
  135 + // Update frame rate monitoring
  136 + updateFrameRate();
  137 +
  138 + // Convert and resize frame
  139 + cv::Mat processed_frame;
  140 + if (frame.channels() == 3) {
  141 + // Convert BGR to RGB for correct color channels
  142 + cv::cvtColor(frame, processed_frame, cv::COLOR_BGR2RGB);
  143 + } else if (frame.channels() == 4) {
  144 + // Convert RGBA to RGB for correct color channels
  145 + cv::cvtColor(frame, processed_frame, cv::COLOR_RGBA2RGB);
  146 + } else {
  147 + LOGE("Unsupported frame format");
  148 + return false;
  149 + }
  150 +
  151 + // Store the last frame for duplication if needed
  152 + last_frame_ = processed_frame.clone();
  153 + has_last_frame_ = true;
  154 +
  155 + // Write frame with duplication if FPS is too low
  156 + writeFrameWithDuplication(processed_frame);
  157 +
  158 + return true;
  159 +}
  160 +
  161 +void MP4Recorder::writerThread() {
  162 + LOGI("Writer thread started");
  163 +
  164 + while (!should_stop_) {
  165 + cv::Mat frame;
  166 +
  167 + // Wait for frame or stop signal
  168 + {
  169 + std::unique_lock<std::mutex> lock(queue_mutex_);
  170 + queue_cv_.wait(lock, [this] { return !frame_queue_.empty() || should_stop_; });
  171 +
  172 + if (should_stop_ && frame_queue_.empty()) {
  173 + break;
  174 + }
  175 +
  176 + if (!frame_queue_.empty()) {
  177 + frame = frame_queue_.front();
  178 + frame_queue_.pop();
  179 + }
  180 + }
  181 +
  182 + if (!frame.empty()) {
  183 + // Write frame to AV file
  184 + std::lock_guard<std::mutex> lock(writer_mutex_);
  185 + av_writer_.writeVideoFrame(frame);
  186 + }
  187 + }
  188 +
  189 + LOGI("Writer thread finished");
  190 +}
  191 +
  192 +void MP4Recorder::setFrameSize(int width, int height) {
  193 + if (frame_width_ != 0 && frame_height_ != 0) {
  194 + // Already set, don't change
  195 + return;
  196 + }
  197 +
  198 + frame_width_ = width;
  199 + frame_height_ = height;
  200 +
  201 + // Initialize AV writer with actual frame size
  202 + std::lock_guard<std::mutex> lock(writer_mutex_);
  203 + if (!av_writer_.open(output_path_, width, height, target_fps_, audio_enabled_)) {
  204 + LOGE("Failed to open AV writer with size %dx%d", width, height);
  205 + } else {
  206 + LOGI("AV writer initialized with actual frame size: %dx%d", width, height);
  207 + }
  208 +}
  209 +
  210 +void MP4Recorder::audioThread() {
  211 + LOGI("Audio thread started");
  212 +
  213 + std::vector<short> audioData;
  214 +
  215 + while (!should_stop_) {
  216 + if (audio_recorder_.getAudioData(audioData)) {
  217 + if (audioData.size() > 0) {
  218 + // Write audio data to AV file
  219 + std::lock_guard<std::mutex> lock(writer_mutex_);
  220 + av_writer_.writeAudioData(audioData);
  221 + }
  222 + } else {
  223 + // No audio data available, small delay to prevent busy waiting
  224 + std::this_thread::sleep_for(std::chrono::milliseconds(10));
  225 + }
  226 + }
  227 +
  228 + LOGI("Audio thread finished");
  229 +}
  230 +
  231 +void MP4Recorder::updateFrameRate() {
  232 + auto current_time = std::chrono::steady_clock::now();
  233 + frame_count_++;
  234 +
  235 + // Add current time to recent frames
  236 + recent_frame_times_.push_back(current_time);
  237 + if (recent_frame_times_.size() > max_recent_frames_) {
  238 + recent_frame_times_.erase(recent_frame_times_.begin());
  239 + }
  240 +
  241 + // Enhanced fast detection for initial frames
  242 + if (use_fast_detection_ && frame_count_ <= 20) {
  243 + // Special handling for first 3 frames to prevent initial acceleration
  244 + if (frame_count_ <= 3) {
  245 + // Conservative approach: assume worst-case scenario (1fps)
  246 + current_fps_ = 1.0f;
  247 + LOGI("Initial frame %d: Conservative FPS=1.0", frame_count_);
  248 + } else if (recent_frame_times_.size() >= 3) {
  249 + // Fast window-based detection for frames 4-20
  250 + auto time_span = std::chrono::duration_cast<std::chrono::milliseconds>(
  251 + recent_frame_times_.back() - recent_frame_times_.front()).count();
  252 + if (time_span > 0) {
  253 + float window_fps = (recent_frame_times_.size() - 1) * 1000.0f / time_span;
  254 + // Use direct window FPS for fast response
  255 + current_fps_ = window_fps;
  256 + LOGI("Fast detection frame %d: Window FPS=%.2f", frame_count_, current_fps_);
  257 + }
  258 + }
  259 +
  260 + // Switch to smooth detection after 20 frames
  261 + if (frame_count_ >= 20) {
  262 + use_fast_detection_ = false;
  263 + LOGI("Switching to smooth FPS detection, current FPS: %.2f", current_fps_);
  264 + }
  265 + } else {
  266 + // Smooth detection for stable operation
  267 + if (recent_frame_times_.size() >= 5) {
  268 + auto time_span = std::chrono::duration_cast<std::chrono::milliseconds>(
  269 + recent_frame_times_.back() - recent_frame_times_.front()).count();
  270 + if (time_span > 0) {
  271 + float recent_fps = (recent_frame_times_.size() - 1) * 1000.0f / time_span;
  272 + // Use faster smoothing for better responsiveness
  273 + current_fps_ = current_fps_ * (1.0f - fps_smoothing_factor_) + recent_fps * fps_smoothing_factor_;
  274 + }
  275 + }
  276 +
  277 + // Log FPS periodically
  278 + if (frame_count_ % 30 == 0) {
  279 + LOGI("Smooth FPS: %.2f, Target FPS: %d", current_fps_, target_fps_);
  280 + }
  281 + }
  282 +
  283 + last_frame_time_ = current_time;
  284 +}
  285 +
  286 +void MP4Recorder::writeFrameWithDuplication(const cv::Mat& frame) {
  287 + // Add original frame to queue
  288 + {
  289 + std::lock_guard<std::mutex> lock(queue_mutex_);
  290 + if (frame_queue_.size() < 100) {
  291 + frame_queue_.push(frame.clone());
  292 + queue_cv_.notify_one();
  293 + }
  294 + }
  295 +
  296 + // Calculate duplication count based on current FPS and frame count
  297 + int duplication_count = 0;
  298 +
  299 + if (use_fast_detection_) {
  300 + // Fast detection mode - more aggressive duplication to prevent initial acceleration
  301 + if (frame_count_ <= 3) {
  302 + // First 3 frames: maximum duplication to ensure smooth start
  303 + duplication_count = target_fps_ - 1;
  304 + LOGI("Initial frame %d: Duplicating %d times (conservative start)", frame_count_, duplication_count);
  305 + } else if (frame_count_ <= 10) {
  306 + // Frames 4-10: adaptive duplication based on detected FPS
  307 + duplication_count = static_cast<int>(target_fps_ / std::max(current_fps_, 0.5f)) - 1;
  308 + duplication_count = std::min(duplication_count, 20);
  309 + if (duplication_count > 0) {
  310 + LOGI("Fast adaptation frame %d: Duplicating %d times for FPS %.2f", frame_count_, duplication_count, current_fps_);
  311 + }
  312 + } else {
  313 + // Frames 11-20: normal duplication
  314 + if (current_fps_ < min_fps_threshold_) {
  315 + duplication_count = static_cast<int>(target_fps_ / std::max(current_fps_, 0.5f)) - 1;
  316 + duplication_count = std::min(duplication_count, 15);
  317 + if (duplication_count > 0) {
  318 + LOGI("Fast mode frame %d: Duplicating %d times for FPS %.2f", frame_count_, duplication_count, current_fps_);
  319 + }
  320 + }
  321 + }
  322 + } else {
  323 + // Smooth detection mode - normal duplication
  324 + if (current_fps_ < min_fps_threshold_) {
  325 + duplication_count = static_cast<int>(target_fps_ / std::max(current_fps_, 0.5f)) - 1;
  326 + duplication_count = std::min(duplication_count, 10);
  327 + if (duplication_count > 0 && frame_count_ % 30 == 0) {
  328 + LOGI("Smooth mode: Duplicating %d times for FPS %.2f", duplication_count, current_fps_);
  329 + }
  330 + }
  331 + }
  332 +
  333 + // Add duplicated frames
  334 + for (int i = 0; i < duplication_count; i++) {
  335 + std::lock_guard<std::mutex> lock(queue_mutex_);
  336 + if (frame_queue_.size() < 150) { // Increased queue size for initial frames
  337 + frame_queue_.push(frame.clone());
  338 + queue_cv_.notify_one();
  339 + } else {
  340 + break; // Prevent memory overflow
  341 + }
  342 + }
  343 +}
  344 +
  345 +void MP4Recorder::cleanup() {
  346 + // Reset frame rate monitoring
  347 + frame_count_ = 0;
  348 + current_fps_ = target_fps_;
  349 + has_last_frame_ = false;
  350 +
  351 + // Cleanup is handled in stopRecording()
  352 +}
  1 +#ifndef MP4RECORDER_H
  2 +#define MP4RECORDER_H
  3 +
  4 +#include <string>
  5 +#include <queue>
  6 +#include <thread>
  7 +#include <mutex>
  8 +#include <condition_variable>
  9 +#include <opencv2/opencv.hpp>
  10 +#include "av_writer.h"
  11 +#include "audio_recorder.h"
  12 +
  13 +class MP4Recorder {
  14 +public:
  15 + MP4Recorder();
  16 + ~MP4Recorder();
  17 +
  18 + bool startRecording(const std::string& outputPath, int width, int height, int fps = 30, bool enableAudio = true);
  19 + bool stopRecording();
  20 + bool isRecording() const { return recording_; }
  21 +
  22 + bool writeFrame(const cv::Mat& frame);
  23 + void setFrameSize(int width, int height);
  24 +
  25 + // Audio recording control
  26 + bool isAudioEnabled() const { return audio_enabled_; }
  27 +
  28 + // Frame rate monitoring
  29 + float getCurrentFPS() const { return current_fps_; }
  30 +
  31 +private:
  32 + void writerThread();
  33 + void audioThread();
  34 + void cleanup();
  35 + void updateFrameRate();
  36 + void writeFrameWithDuplication(const cv::Mat& frame);
  37 +
  38 + std::string output_path_;
  39 + bool recording_;
  40 + bool should_stop_;
  41 + bool audio_enabled_;
  42 +
  43 + int frame_width_;
  44 + int frame_height_;
  45 + int target_fps_;
  46 + float current_fps_;
  47 +
  48 + // Frame rate monitoring
  49 + std::chrono::steady_clock::time_point last_frame_time_;
  50 + std::chrono::steady_clock::time_point recording_start_time_;
  51 + int frame_count_;
  52 + float fps_smoothing_factor_;
  53 +
  54 + // Fast FPS detection for initial frames
  55 + std::vector<std::chrono::steady_clock::time_point> recent_frame_times_;
  56 + int max_recent_frames_;
  57 + bool use_fast_detection_;
  58 +
  59 + // Frame duplication for low FPS
  60 + cv::Mat last_frame_;
  61 + bool has_last_frame_;
  62 + int min_fps_threshold_;
  63 +
  64 + std::queue<cv::Mat> frame_queue_;
  65 + std::mutex queue_mutex_;
  66 + std::condition_variable queue_cv_;
  67 + std::thread writer_thread_;
  68 +
  69 + // Audio recording
  70 + AudioRecorder audio_recorder_;
  71 + std::thread audio_thread_;
  72 + std::mutex audio_mutex_;
  73 +
  74 + AVWriter av_writer_;
  75 + std::mutex writer_mutex_;
  76 +};
  77 +
  78 +#endif // MP4RECORDER_H
  1 +#include "simple_mp4_writer.h"
  2 +#include <android/log.h>
  3 +
  4 +#define LOG_TAG "SimpleMP4Writer"
  5 +#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
  6 +#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
  7 +
  8 +SimpleMP4Writer::SimpleMP4Writer()
  9 + : width_(0)
  10 + , height_(0)
  11 + , fps_(30)
  12 + , frame_count_(0)
  13 + , is_open_(false)
  14 +{
  15 +}
  16 +
  17 +SimpleMP4Writer::~SimpleMP4Writer() {
  18 + close();
  19 +}
  20 +
  21 +bool SimpleMP4Writer::open(const std::string& filename, int width, int height, int fps) {
  22 + if (is_open_) {
  23 + close();
  24 + }
  25 +
  26 + filename_ = filename;
  27 + width_ = width;
  28 + height_ = height;
  29 + fps_ = fps;
  30 + frame_count_ = 0;
  31 +
  32 + // For simplicity, we'll create an AVI file instead of MP4
  33 + // This is easier to implement without external libraries
  34 + std::string avi_filename = filename;
  35 + size_t pos = avi_filename.find_last_of('.');
  36 + if (pos != std::string::npos) {
  37 + avi_filename = avi_filename.substr(0, pos) + ".avi";
  38 + }
  39 +
  40 + file_.open(avi_filename, std::ios::binary);
  41 + if (!file_.is_open()) {
  42 + LOGE("Failed to open file: %s", avi_filename.c_str());
  43 + return false;
  44 + }
  45 +
  46 + writeHeader();
  47 + is_open_ = true;
  48 +
  49 + LOGI("Opened video file: %s (%dx%d @ %dfps)", avi_filename.c_str(), width, height, fps);
  50 + return true;
  51 +}
  52 +
  53 +bool SimpleMP4Writer::write(const cv::Mat& frame) {
  54 + if (!is_open_) {
  55 + return false;
  56 + }
  57 +
  58 + cv::Mat bgr_frame;
  59 + if (frame.channels() == 4) {
  60 + cv::cvtColor(frame, bgr_frame, cv::COLOR_RGBA2BGR);
  61 + } else if (frame.channels() == 3) {
  62 + bgr_frame = frame;
  63 + } else {
  64 + LOGE("Unsupported frame format");
  65 + return false;
  66 + }
  67 +
  68 + // Resize if necessary
  69 + if (bgr_frame.cols != width_ || bgr_frame.rows != height_) {
  70 + cv::resize(bgr_frame, bgr_frame, cv::Size(width_, height_));
  71 + }
  72 +
  73 + writeFrame(bgr_frame);
  74 + frame_count_++;
  75 + return true;
  76 +}
  77 +
  78 +void SimpleMP4Writer::close() {
  79 + if (is_open_) {
  80 + finalize();
  81 + file_.close();
  82 + is_open_ = false;
  83 + LOGI("Closed video file: %s (%d frames)", filename_.c_str(), frame_count_);
  84 + }
  85 +}
  86 +
  87 +void SimpleMP4Writer::writeHeader() {
  88 + // Write a simple AVI header
  89 + // This is a minimal implementation for demonstration
  90 +
  91 + // RIFF header
  92 + file_.write("RIFF", 4);
  93 + uint32_t file_size = 0; // Will be updated later
  94 + file_.write(reinterpret_cast<const char*>(&file_size), 4);
  95 + file_.write("AVI ", 4);
  96 +
  97 + // LIST hdrl
  98 + file_.write("LIST", 4);
  99 + uint32_t hdrl_size = 208; // Approximate size
  100 + file_.write(reinterpret_cast<const char*>(&hdrl_size), 4);
  101 + file_.write("hdrl", 4);
  102 +
  103 + // avih chunk
  104 + file_.write("avih", 4);
  105 + uint32_t avih_size = 56;
  106 + file_.write(reinterpret_cast<const char*>(&avih_size), 4);
  107 +
  108 + // AVI main header
  109 + uint32_t microsec_per_frame = 1000000 / fps_;
  110 + file_.write(reinterpret_cast<const char*>(&microsec_per_frame), 4);
  111 + uint32_t max_bytes_per_sec = width_ * height_ * 3 * fps_;
  112 + file_.write(reinterpret_cast<const char*>(&max_bytes_per_sec), 4);
  113 + uint32_t padding = 0;
  114 + file_.write(reinterpret_cast<const char*>(&padding), 4);
  115 + uint32_t flags = 0x10; // AVIF_HASINDEX
  116 + file_.write(reinterpret_cast<const char*>(&flags), 4);
  117 + uint32_t total_frames = 0; // Will be updated later
  118 + file_.write(reinterpret_cast<const char*>(&total_frames), 4);
  119 + uint32_t initial_frames = 0;
  120 + file_.write(reinterpret_cast<const char*>(&initial_frames), 4);
  121 + uint32_t streams = 1;
  122 + file_.write(reinterpret_cast<const char*>(&streams), 4);
  123 + uint32_t suggested_buffer_size = width_ * height_ * 3;
  124 + file_.write(reinterpret_cast<const char*>(&suggested_buffer_size), 4);
  125 + uint32_t width = width_;
  126 + file_.write(reinterpret_cast<const char*>(&width), 4);
  127 + uint32_t height = height_;
  128 + file_.write(reinterpret_cast<const char*>(&height), 4);
  129 + uint32_t reserved[4] = {0, 0, 0, 0};
  130 + file_.write(reinterpret_cast<const char*>(reserved), 16);
  131 +
  132 + // LIST strl
  133 + file_.write("LIST", 4);
  134 + uint32_t strl_size = 140;
  135 + file_.write(reinterpret_cast<const char*>(&strl_size), 4);
  136 + file_.write("strl", 4);
  137 +
  138 + // strh chunk
  139 + file_.write("strh", 4);
  140 + uint32_t strh_size = 56;
  141 + file_.write(reinterpret_cast<const char*>(&strh_size), 4);
  142 + file_.write("vids", 4); // Stream type
  143 + file_.write("DIB ", 4); // Handler
  144 + uint32_t strh_flags = 0;
  145 + file_.write(reinterpret_cast<const char*>(&strh_flags), 4);
  146 + uint16_t priority = 0;
  147 + file_.write(reinterpret_cast<const char*>(&priority), 2);
  148 + uint16_t language = 0;
  149 + file_.write(reinterpret_cast<const char*>(&language), 2);
  150 + uint32_t initial_frames_strh = 0;
  151 + file_.write(reinterpret_cast<const char*>(&initial_frames_strh), 4);
  152 + uint32_t scale = 1;
  153 + file_.write(reinterpret_cast<const char*>(&scale), 4);
  154 + uint32_t rate = fps_;
  155 + file_.write(reinterpret_cast<const char*>(&rate), 4);
  156 + uint32_t start = 0;
  157 + file_.write(reinterpret_cast<const char*>(&start), 4);
  158 + uint32_t length = 0; // Will be updated later
  159 + file_.write(reinterpret_cast<const char*>(&length), 4);
  160 + uint32_t suggested_buffer_size_strh = width_ * height_ * 3;
  161 + file_.write(reinterpret_cast<const char*>(&suggested_buffer_size_strh), 4);
  162 + uint32_t quality = 0;
  163 + file_.write(reinterpret_cast<const char*>(&quality), 4);
  164 + uint32_t sample_size = 0;
  165 + file_.write(reinterpret_cast<const char*>(&sample_size), 4);
  166 + uint16_t left = 0, top = 0, right = width_, bottom = height_;
  167 + file_.write(reinterpret_cast<const char*>(&left), 2);
  168 + file_.write(reinterpret_cast<const char*>(&top), 2);
  169 + file_.write(reinterpret_cast<const char*>(&right), 2);
  170 + file_.write(reinterpret_cast<const char*>(&bottom), 2);
  171 +
  172 + // strf chunk (BITMAPINFOHEADER)
  173 + file_.write("strf", 4);
  174 + uint32_t strf_size = 40;
  175 + file_.write(reinterpret_cast<const char*>(&strf_size), 4);
  176 + uint32_t bi_size = 40;
  177 + file_.write(reinterpret_cast<const char*>(&bi_size), 4);
  178 + int32_t bi_width = width_;
  179 + file_.write(reinterpret_cast<const char*>(&bi_width), 4);
  180 + int32_t bi_height = height_;
  181 + file_.write(reinterpret_cast<const char*>(&bi_height), 4);
  182 + uint16_t bi_planes = 1;
  183 + file_.write(reinterpret_cast<const char*>(&bi_planes), 2);
  184 + uint16_t bi_bit_count = 24;
  185 + file_.write(reinterpret_cast<const char*>(&bi_bit_count), 2);
  186 + uint32_t bi_compression = 0; // BI_RGB
  187 + file_.write(reinterpret_cast<const char*>(&bi_compression), 4);
  188 + uint32_t bi_size_image = width_ * height_ * 3;
  189 + file_.write(reinterpret_cast<const char*>(&bi_size_image), 4);
  190 + int32_t bi_x_pels_per_meter = 0;
  191 + file_.write(reinterpret_cast<const char*>(&bi_x_pels_per_meter), 4);
  192 + int32_t bi_y_pels_per_meter = 0;
  193 + file_.write(reinterpret_cast<const char*>(&bi_y_pels_per_meter), 4);
  194 + uint32_t bi_clr_used = 0;
  195 + file_.write(reinterpret_cast<const char*>(&bi_clr_used), 4);
  196 + uint32_t bi_clr_important = 0;
  197 + file_.write(reinterpret_cast<const char*>(&bi_clr_important), 4);
  198 +
  199 + // LIST movi
  200 + file_.write("LIST", 4);
  201 + uint32_t movi_size = 0; // Will be updated later
  202 + file_.write(reinterpret_cast<const char*>(&movi_size), 4);
  203 + file_.write("movi", 4);
  204 +}
  205 +
  206 +void SimpleMP4Writer::writeFrame(const cv::Mat& frame) {
  207 + // Write frame as uncompressed DIB
  208 + file_.write("00db", 4); // Chunk ID
  209 + uint32_t chunk_size = frame.total() * frame.elemSize();
  210 + file_.write(reinterpret_cast<const char*>(&chunk_size), 4);
  211 +
  212 + // Write frame data (RGB format, bottom-up)
  213 + // Note: frame should already be in RGB format from MP4Recorder
  214 + for (int y = frame.rows - 1; y >= 0; y--) {
  215 + file_.write(reinterpret_cast<const char*>(frame.ptr(y)), frame.cols * 3);
  216 + }
  217 +
  218 + // Pad to even boundary
  219 + if (chunk_size % 2 == 1) {
  220 + char pad = 0;
  221 + file_.write(&pad, 1);
  222 + }
  223 +}
  224 +
  225 +void SimpleMP4Writer::finalize() {
  226 + // Update file size and frame count in header
  227 + std::streampos current_pos = file_.tellp();
  228 + uint32_t file_size = static_cast<uint32_t>(current_pos) - 8;
  229 +
  230 + // Update RIFF size
  231 + file_.seekp(4);
  232 + file_.write(reinterpret_cast<const char*>(&file_size), 4);
  233 +
  234 + // Update total frames in avih
  235 + file_.seekp(48);
  236 + uint32_t total_frames = frame_count_;
  237 + file_.write(reinterpret_cast<const char*>(&total_frames), 4);
  238 +
  239 + // Update length in strh
  240 + file_.seekp(140);
  241 + file_.write(reinterpret_cast<const char*>(&total_frames), 4);
  242 +
  243 + // Update movi size
  244 + uint32_t movi_size = static_cast<uint32_t>(current_pos) - 220;
  245 + file_.seekp(216);
  246 + file_.write(reinterpret_cast<const char*>(&movi_size), 4);
  247 +
  248 + file_.seekp(current_pos);
  249 +}
  1 +#ifndef SIMPLE_MP4_WRITER_H
  2 +#define SIMPLE_MP4_WRITER_H
  3 +
  4 +#include <string>
  5 +#include <vector>
  6 +#include <fstream>
  7 +#include <opencv2/opencv.hpp>
  8 +
  9 +class SimpleMP4Writer {
  10 +public:
  11 + SimpleMP4Writer();
  12 + ~SimpleMP4Writer();
  13 +
  14 + bool open(const std::string& filename, int width, int height, int fps);
  15 + bool write(const cv::Mat& frame);
  16 + void close();
  17 +
  18 +private:
  19 + void writeHeader();
  20 + void writeFrame(const cv::Mat& frame);
  21 + void finalize();
  22 +
  23 + std::ofstream file_;
  24 + std::string filename_;
  25 + int width_;
  26 + int height_;
  27 + int fps_;
  28 + int frame_count_;
  29 + bool is_open_;
  30 +
  31 + std::vector<uint8_t> frame_buffer_;
  32 +};
  33 +
  34 +#endif // SIMPLE_MP4_WRITER_H
  1 +// Tencent is pleased to support the open source community by making ncnn available.
  2 +//
  3 +// Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.
  4 +//
  5 +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except
  6 +// in compliance with the License. You may obtain a copy of the License at
  7 +//
  8 +// https://opensource.org/licenses/BSD-3-Clause
  9 +//
  10 +// Unless required by applicable law or agreed to in writing, software distributed
  11 +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
  12 +// CONDITIONS OF ANY KIND, either express or implied. See the License for the
  13 +// specific language governing permissions and limitations under the License.
  14 +
  15 +#include <android/asset_manager_jni.h>
  16 +#include <android/native_window_jni.h>
  17 +#include <android/native_window.h>
  18 +
  19 +#include <android/log.h>
  20 +
  21 +#include <jni.h>
  22 +
  23 +#include <string>
  24 +#include <vector>
  25 +
  26 +#include <platform.h>
  27 +#include <benchmark.h>
  28 +
  29 +#include "yolov8.h"
  30 +
  31 +#include "ndkcamera.h"
  32 +#include "mp4recorder.h"
  33 +
  34 +#include <opencv2/core/core.hpp>
  35 +#include <opencv2/imgproc/imgproc.hpp>
  36 +
  37 +#if __ARM_NEON
  38 +#include <arm_neon.h>
  39 +#endif // __ARM_NEON
  40 +
  41 +static int draw_unsupported(cv::Mat& rgb)
  42 +{
  43 + const char text[] = "unsupported";
  44 +
  45 + int baseLine = 0;
  46 + cv::Size label_size = cv::getTextSize(text, cv::FONT_HERSHEY_SIMPLEX, 1.0, 1, &baseLine);
  47 +
  48 + int y = (rgb.rows - label_size.height) / 2;
  49 + int x = (rgb.cols - label_size.width) / 2;
  50 +
  51 + cv::rectangle(rgb, cv::Rect(cv::Point(x, y), cv::Size(label_size.width, label_size.height + baseLine)),
  52 + cv::Scalar(255, 255, 255), -1);
  53 +
  54 + cv::putText(rgb, text, cv::Point(x, y + label_size.height),
  55 + cv::FONT_HERSHEY_SIMPLEX, 1.0, cv::Scalar(0, 0, 0));
  56 +
  57 + return 0;
  58 +}
  59 +
  60 +static int draw_fps(cv::Mat& rgb)
  61 +{
  62 + // resolve moving average
  63 + float avg_fps = 0.f;
  64 + {
  65 + static double t0 = 0.f;
  66 + static float fps_history[10] = {0.f};
  67 +
  68 + double t1 = ncnn::get_current_time();
  69 + if (t0 == 0.f)
  70 + {
  71 + t0 = t1;
  72 + return 0;
  73 + }
  74 +
  75 + float fps = 1000.f / (t1 - t0);
  76 + t0 = t1;
  77 +
  78 + for (int i = 9; i >= 1; i--)
  79 + {
  80 + fps_history[i] = fps_history[i - 1];
  81 + }
  82 + fps_history[0] = fps;
  83 +
  84 + if (fps_history[9] == 0.f)
  85 + {
  86 + return 0;
  87 + }
  88 +
  89 + for (int i = 0; i < 10; i++)
  90 + {
  91 + avg_fps += fps_history[i];
  92 + }
  93 + avg_fps /= 10.f;
  94 + }
  95 +
  96 + char text[32];
  97 + sprintf(text, "FPS=%.2f", avg_fps);
  98 +
  99 + int baseLine = 0;
  100 + cv::Size label_size = cv::getTextSize(text, cv::FONT_HERSHEY_SIMPLEX, 0.5, 1, &baseLine);
  101 +
  102 + int y = 0;
  103 + int x = rgb.cols - label_size.width;
  104 +
  105 + cv::rectangle(rgb, cv::Rect(cv::Point(x, y), cv::Size(label_size.width, label_size.height + baseLine)),
  106 + cv::Scalar(255, 255, 255), -1);
  107 +
  108 + cv::putText(rgb, text, cv::Point(x, y + label_size.height),
  109 + cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(0, 0, 0));
  110 +
  111 + return 0;
  112 +}
  113 +
  114 +static YOLOv8* g_yolov8 = 0;
  115 +static ncnn::Mutex lock;
  116 +static MP4Recorder* g_recorder = 0;
  117 +static ncnn::Mutex recorder_lock;
  118 +
  119 +class MyNdkCamera : public NdkCameraWindow
  120 +{
  121 +public:
  122 + virtual void on_image_render(cv::Mat& rgb) const;
  123 +};
  124 +
  125 +void MyNdkCamera::on_image_render(cv::Mat& rgb) const
  126 +{
  127 + // yolov8
  128 + {
  129 + ncnn::MutexLockGuard g(lock);
  130 +
  131 + if (g_yolov8)
  132 + {
  133 + std::vector<Object> objects;
  134 + g_yolov8->detect(rgb, objects);
  135 +
  136 + g_yolov8->draw(rgb, objects);
  137 + }
  138 + else
  139 + {
  140 + draw_unsupported(rgb);
  141 + }
  142 + }
  143 +
  144 + draw_fps(rgb);
  145 +
  146 + // Record frame if recording is active
  147 + {
  148 + ncnn::MutexLockGuard g(recorder_lock);
  149 + if (g_recorder && g_recorder->isRecording()) {
  150 + g_recorder->writeFrame(rgb);
  151 + }
  152 + }
  153 +}
  154 +
  155 +static MyNdkCamera* g_camera = 0;
  156 +
  157 +extern "C" {
  158 +
  159 +JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved)
  160 +{
  161 + __android_log_print(ANDROID_LOG_DEBUG, "ncnn", "JNI_OnLoad");
  162 +
  163 + g_camera = new MyNdkCamera;
  164 + g_recorder = new MP4Recorder;
  165 +
  166 + ncnn::create_gpu_instance();
  167 +
  168 + return JNI_VERSION_1_4;
  169 +}
  170 +
  171 +JNIEXPORT void JNI_OnUnload(JavaVM* vm, void* reserved)
  172 +{
  173 + __android_log_print(ANDROID_LOG_DEBUG, "ncnn", "JNI_OnUnload");
  174 +
  175 + {
  176 + ncnn::MutexLockGuard g(lock);
  177 +
  178 + delete g_yolov8;
  179 + g_yolov8 = 0;
  180 + }
  181 +
  182 + {
  183 + ncnn::MutexLockGuard g(recorder_lock);
  184 + delete g_recorder;
  185 + g_recorder = 0;
  186 + }
  187 +
  188 + ncnn::destroy_gpu_instance();
  189 +
  190 + delete g_camera;
  191 + g_camera = 0;
  192 +}
  193 +
  194 +// public native boolean loadModel(AssetManager mgr, int taskid, int modelid, int cpugpu);
  195 +JNIEXPORT jboolean JNICALL Java_com_tencent_yolov8ncnn_YOLOv8Ncnn_loadModel(JNIEnv* env, jobject thiz, jobject assetManager, jint taskid, jint modelid, jint cpugpu)
  196 +{
  197 + if (taskid < 0 || taskid > 5 || modelid < 0 || modelid > 8 || cpugpu < 0 || cpugpu > 2)
  198 + {
  199 + return JNI_FALSE;
  200 + }
  201 +
  202 + AAssetManager* mgr = AAssetManager_fromJava(env, assetManager);
  203 +
  204 + __android_log_print(ANDROID_LOG_DEBUG, "ncnn", "loadModel %p", mgr);
  205 +
  206 + const char* tasknames[6] =
  207 + {
  208 + "",
  209 + "_oiv7",
  210 + "_seg",
  211 + "_pose",
  212 + "_cls",
  213 + "_obb"
  214 + };
  215 +
  216 + const char* modeltypes[9] =
  217 + {
  218 + "n",
  219 + "s",
  220 + "m",
  221 + "n",
  222 + "s",
  223 + "m",
  224 + "n",
  225 + "s",
  226 + "m"
  227 + };
  228 +
  229 + std::string parampath = std::string("yolov8") + modeltypes[(int)modelid] + tasknames[(int)taskid] + ".ncnn.param";
  230 + std::string modelpath = std::string("yolov8") + modeltypes[(int)modelid] + tasknames[(int)taskid] + ".ncnn.bin";
  231 + bool use_gpu = (int)cpugpu == 1;
  232 + bool use_turnip = (int)cpugpu == 2;
  233 +
  234 + // reload
  235 + {
  236 + ncnn::MutexLockGuard g(lock);
  237 +
  238 + {
  239 + static int old_taskid = 0;
  240 + static int old_modelid = 0;
  241 + static int old_cpugpu = 0;
  242 + if (taskid != old_taskid || (modelid % 3) != old_modelid || cpugpu != old_cpugpu)
  243 + {
  244 + // taskid or model or cpugpu changed
  245 + delete g_yolov8;
  246 + g_yolov8 = 0;
  247 + }
  248 + old_taskid = taskid;
  249 + old_modelid = modelid % 3;
  250 + old_cpugpu = cpugpu;
  251 +
  252 + ncnn::destroy_gpu_instance();
  253 +
  254 + if (use_turnip)
  255 + {
  256 + ncnn::create_gpu_instance("libvulkan_freedreno.so");
  257 + }
  258 + else if (use_gpu)
  259 + {
  260 + ncnn::create_gpu_instance();
  261 + }
  262 +
  263 + if (!g_yolov8)
  264 + {
  265 + if (taskid == 0) g_yolov8 = new YOLOv8_det_coco;
  266 + if (taskid == 1) g_yolov8 = new YOLOv8_det_oiv7;
  267 + if (taskid == 2) g_yolov8 = new YOLOv8_seg;
  268 + if (taskid == 3) g_yolov8 = new YOLOv8_pose;
  269 + if (taskid == 4) g_yolov8 = new YOLOv8_cls;
  270 + if (taskid == 5) g_yolov8 = new YOLOv8_obb;
  271 +
  272 + g_yolov8->load(mgr, parampath.c_str(), modelpath.c_str(), use_gpu || use_turnip);
  273 + }
  274 + int target_size = 320;
  275 + if ((int)modelid >= 3)
  276 + target_size = 480;
  277 + if ((int)modelid >= 6)
  278 + target_size = 640;
  279 + g_yolov8->set_det_target_size(target_size);
  280 + }
  281 + }
  282 +
  283 + return JNI_TRUE;
  284 +}
  285 +
  286 +// public native boolean openCamera(int facing);
  287 +JNIEXPORT jboolean JNICALL Java_com_tencent_yolov8ncnn_YOLOv8Ncnn_openCamera(JNIEnv* env, jobject thiz, jint facing)
  288 +{
  289 + if (facing < 0 || facing > 1)
  290 + return JNI_FALSE;
  291 +
  292 + __android_log_print(ANDROID_LOG_DEBUG, "ncnn", "openCamera %d", facing);
  293 +
  294 + g_camera->open((int)facing);
  295 +
  296 + return JNI_TRUE;
  297 +}
  298 +
  299 +// public native boolean closeCamera();
  300 +JNIEXPORT jboolean JNICALL Java_com_tencent_yolov8ncnn_YOLOv8Ncnn_closeCamera(JNIEnv* env, jobject thiz)
  301 +{
  302 + __android_log_print(ANDROID_LOG_DEBUG, "ncnn", "closeCamera");
  303 +
  304 + g_camera->close();
  305 +
  306 + return JNI_TRUE;
  307 +}
  308 +
  309 +// public native boolean setOutputWindow(Surface surface);
  310 +JNIEXPORT jboolean JNICALL Java_com_tencent_yolov8ncnn_YOLOv8Ncnn_setOutputWindow(JNIEnv* env, jobject thiz, jobject surface)
  311 +{
  312 + ANativeWindow* win = ANativeWindow_fromSurface(env, surface);
  313 +
  314 + __android_log_print(ANDROID_LOG_DEBUG, "ncnn", "setOutputWindow %p", win);
  315 +
  316 + g_camera->set_window(win);
  317 +
  318 + return JNI_TRUE;
  319 +}
  320 +
  321 +// public native boolean startRecording(String outputPath);
  322 +JNIEXPORT jboolean JNICALL Java_com_tencent_yolov8ncnn_YOLOv8Ncnn_startRecording(JNIEnv* env, jobject thiz, jstring outputPath)
  323 +{
  324 + if (!g_recorder) {
  325 + __android_log_print(ANDROID_LOG_ERROR, "ncnn", "Recorder not initialized");
  326 + return JNI_FALSE;
  327 + }
  328 +
  329 + const char* path = env->GetStringUTFChars(outputPath, nullptr);
  330 + if (!path) {
  331 + __android_log_print(ANDROID_LOG_ERROR, "ncnn", "Failed to get output path");
  332 + return JNI_FALSE;
  333 + }
  334 +
  335 + __android_log_print(ANDROID_LOG_DEBUG, "ncnn", "startRecording %s", path);
  336 +
  337 + bool success = false;
  338 + {
  339 + ncnn::MutexLockGuard g(recorder_lock);
  340 + // Use dynamic resolution based on actual frame size
  341 + // Will be set when first frame is received
  342 + // Enable audio recording by default
  343 + success = g_recorder->startRecording(std::string(path), 0, 0, 30, true);
  344 + }
  345 +
  346 + env->ReleaseStringUTFChars(outputPath, path);
  347 +
  348 + return success ? JNI_TRUE : JNI_FALSE;
  349 +}
  350 +
  351 +// public native boolean stopRecording();
  352 +JNIEXPORT jboolean JNICALL Java_com_tencent_yolov8ncnn_YOLOv8Ncnn_stopRecording(JNIEnv* env, jobject thiz)
  353 +{
  354 + if (!g_recorder) {
  355 + __android_log_print(ANDROID_LOG_ERROR, "ncnn", "Recorder not initialized");
  356 + return JNI_FALSE;
  357 + }
  358 +
  359 + __android_log_print(ANDROID_LOG_DEBUG, "ncnn", "stopRecording");
  360 +
  361 + bool success = false;
  362 + {
  363 + ncnn::MutexLockGuard g(recorder_lock);
  364 + success = g_recorder->stopRecording();
  365 + }
  366 +
  367 + return success ? JNI_TRUE : JNI_FALSE;
  368 +}
  369 +
  370 +}