正在显示
10 个修改的文件
包含
1874 行增加
和
0 行删除
app/src/main/jni/olderversion/CMakeLists.txt
0 → 100644
| 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 |
app/src/main/jni/olderversion/av_writer.cpp
0 → 100644
| 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*>(µsec_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 | +} |
app/src/main/jni/olderversion/av_writer.h
0 → 100644
| 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 | +} |
app/src/main/jni/olderversion/mp4recorder.h
0 → 100644
| 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*>(µsec_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 |
app/src/main/jni/olderversion/yolov8ncnn.cpp
0 → 100644
| 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 | +} |
-
请 注册 或 登录 后发表评论