mp4recorder.cpp 11.4 KB
#include "mp4recorder.h"
#include <android/log.h>
#include <chrono>

#define LOG_TAG "MP4Recorder"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)

MP4Recorder::MP4Recorder() 
    : recording_(false)
    , should_stop_(false)
    , audio_enabled_(false)
    , frame_width_(0)
    , frame_height_(0)
    , target_fps_(30)
    , current_fps_(1.0f)  // Start with low FPS assumption
    , frame_count_(0)
    , fps_smoothing_factor_(0.3f)  // Faster adaptation
    , max_recent_frames_(10)
    , use_fast_detection_(true)
    , has_last_frame_(false)
    , min_fps_threshold_(5)
{
    recent_frame_times_.reserve(max_recent_frames_);
}

MP4Recorder::~MP4Recorder() {
    stopRecording();
}

bool MP4Recorder::startRecording(const std::string& outputPath, int width, int height, int fps, bool enableAudio) {
    if (recording_) {
        LOGE("Already recording");
        return false;
    }

    output_path_ = outputPath;
    frame_width_ = width;
    frame_height_ = height;
    target_fps_ = fps;
    current_fps_ = fps;
    audio_enabled_ = enableAudio;
    should_stop_ = false;
    
    // Reset frame rate monitoring
    frame_count_ = 0;
    has_last_frame_ = false;
    use_fast_detection_ = true;
    current_fps_ = 1.0f;  // Conservative start
    recent_frame_times_.clear();
    recording_start_time_ = std::chrono::steady_clock::now();
    last_frame_time_ = recording_start_time_;

    // Clear frame queue
    {
        std::lock_guard<std::mutex> lock(queue_mutex_);
        while (!frame_queue_.empty()) {
            frame_queue_.pop();
        }
    }

    // Open AV writer (only if width/height are specified)
    if (width > 0 && height > 0) {
        std::lock_guard<std::mutex> lock(writer_mutex_);
        if (!av_writer_.open(output_path_, width, height, fps, audio_enabled_)) {
            LOGE("Failed to open AV writer");
            return false;
        }
    }

    // Start audio recording if enabled
    if (audio_enabled_) {
        if (!audio_recorder_.startRecording()) {
            LOGE("Failed to start audio recording");
            return false;
        }
        // Start audio thread
        audio_thread_ = std::thread(&MP4Recorder::audioThread, this);
    }

    recording_ = true;
    
    // Start writer thread
    writer_thread_ = std::thread(&MP4Recorder::writerThread, this);

    LOGI("Recording started: %s (%dx%d @ %dfps) Audio: %s", 
         output_path_.c_str(), width, height, fps, audio_enabled_ ? "ON" : "OFF");
    return true;
}

bool MP4Recorder::stopRecording() {
    if (!recording_) {
        return true;
    }

    recording_ = false;
    should_stop_ = true;

    // Stop audio recording
    if (audio_enabled_) {
        audio_recorder_.stopRecording();
        if (audio_thread_.joinable()) {
            audio_thread_.join();
        }
    }

    // Notify writer thread
    queue_cv_.notify_all();

    // Wait for writer thread to finish
    if (writer_thread_.joinable()) {
        writer_thread_.join();
    }

    // Close AV writer
    {
        std::lock_guard<std::mutex> lock(writer_mutex_);
        av_writer_.close();
    }

    LOGI("Recording stopped: %s", output_path_.c_str());
    return true;
}

bool MP4Recorder::writeFrame(const cv::Mat& frame) {
    if (!recording_) {
        return false;
    }

    // Initialize video writer with actual frame size if not done yet
    if (frame_width_ == 0 || frame_height_ == 0) {
        setFrameSize(frame.cols, frame.rows);
    }

    // Update frame rate monitoring
    updateFrameRate();

    // Convert and resize frame
    cv::Mat processed_frame;
    if (frame.channels() == 3) {
        // Convert BGR to RGB for correct color channels
        cv::cvtColor(frame, processed_frame, cv::COLOR_BGR2RGB);
    } else if (frame.channels() == 4) {
        // Convert RGBA to RGB for correct color channels
        cv::cvtColor(frame, processed_frame, cv::COLOR_RGBA2RGB);
    } else {
        LOGE("Unsupported frame format");
        return false;
    }

    // Store the last frame for duplication if needed
    last_frame_ = processed_frame.clone();
    has_last_frame_ = true;

    // Write frame with duplication if FPS is too low
    writeFrameWithDuplication(processed_frame);

    return true;
}

void MP4Recorder::writerThread() {
    LOGI("Writer thread started");
    
    while (!should_stop_) {
        cv::Mat frame;
        
        // Wait for frame or stop signal
        {
            std::unique_lock<std::mutex> lock(queue_mutex_);
            queue_cv_.wait(lock, [this] { return !frame_queue_.empty() || should_stop_; });
            
            if (should_stop_ && frame_queue_.empty()) {
                break;
            }
            
            if (!frame_queue_.empty()) {
                frame = frame_queue_.front();
                frame_queue_.pop();
            }
        }
        
        if (!frame.empty()) {
            // Write frame to AV file
            std::lock_guard<std::mutex> lock(writer_mutex_);
            av_writer_.writeVideoFrame(frame);
        }
    }
    
    LOGI("Writer thread finished");
}

void MP4Recorder::setFrameSize(int width, int height) {
    if (frame_width_ != 0 && frame_height_ != 0) {
        // Already set, don't change
        return;
    }
    
    frame_width_ = width;
    frame_height_ = height;
    
    // Initialize AV writer with actual frame size
    std::lock_guard<std::mutex> lock(writer_mutex_);
    if (!av_writer_.open(output_path_, width, height, target_fps_, audio_enabled_)) {
        LOGE("Failed to open AV writer with size %dx%d", width, height);
    } else {
        LOGI("AV writer initialized with actual frame size: %dx%d", width, height);
    }
}

void MP4Recorder::audioThread() {
    LOGI("Audio thread started");
    
    std::vector<short> audioData;
    
    while (!should_stop_) {
        if (audio_recorder_.getAudioData(audioData)) {
            if (audioData.size() > 0) {
                // Write audio data to AV file
                std::lock_guard<std::mutex> lock(writer_mutex_);
                av_writer_.writeAudioData(audioData);
            }
        } else {
            // No audio data available, small delay to prevent busy waiting
            std::this_thread::sleep_for(std::chrono::milliseconds(10));
        }
    }
    
    LOGI("Audio thread finished");
}

void MP4Recorder::updateFrameRate() {
    auto current_time = std::chrono::steady_clock::now();
    frame_count_++;
    
    // Add current time to recent frames
    recent_frame_times_.push_back(current_time);
    if (recent_frame_times_.size() > max_recent_frames_) {
        recent_frame_times_.erase(recent_frame_times_.begin());
    }
    
    // Enhanced fast detection for initial frames
    if (use_fast_detection_ && frame_count_ <= 20) {
        // Special handling for first 3 frames to prevent initial acceleration
        if (frame_count_ <= 3) {
            // Conservative approach: assume worst-case scenario (1fps)
            current_fps_ = 1.0f;
            LOGI("Initial frame %d: Conservative FPS=1.0", frame_count_);
        } else if (recent_frame_times_.size() >= 3) {
            // Fast window-based detection for frames 4-20
            auto time_span = std::chrono::duration_cast<std::chrono::milliseconds>(
                recent_frame_times_.back() - recent_frame_times_.front()).count();
            if (time_span > 0) {
                float window_fps = (recent_frame_times_.size() - 1) * 1000.0f / time_span;
                // Use direct window FPS for fast response
                current_fps_ = window_fps;
                LOGI("Fast detection frame %d: Window FPS=%.2f", frame_count_, current_fps_);
            }
        }
        
        // Switch to smooth detection after 20 frames
        if (frame_count_ >= 20) {
            use_fast_detection_ = false;
            LOGI("Switching to smooth FPS detection, current FPS: %.2f", current_fps_);
        }
    } else {
        // Smooth detection for stable operation
        if (recent_frame_times_.size() >= 5) {
            auto time_span = std::chrono::duration_cast<std::chrono::milliseconds>(
                recent_frame_times_.back() - recent_frame_times_.front()).count();
            if (time_span > 0) {
                float recent_fps = (recent_frame_times_.size() - 1) * 1000.0f / time_span;
                // Use faster smoothing for better responsiveness
                current_fps_ = current_fps_ * (1.0f - fps_smoothing_factor_) + recent_fps * fps_smoothing_factor_;
            }
        }
        
        // Log FPS periodically
        if (frame_count_ % 30 == 0) {
            LOGI("Smooth FPS: %.2f, Target FPS: %d", current_fps_, target_fps_);
        }
    }
    
    last_frame_time_ = current_time;
}

void MP4Recorder::writeFrameWithDuplication(const cv::Mat& frame) {
    // Add original frame to queue
    {
        std::lock_guard<std::mutex> lock(queue_mutex_);
        if (frame_queue_.size() < 100) {
            frame_queue_.push(frame.clone());
            queue_cv_.notify_one();
        }
    }
    
    // Calculate duplication count based on current FPS and frame count
    int duplication_count = 0;
    
    if (use_fast_detection_) {
        // Fast detection mode - more aggressive duplication to prevent initial acceleration
        if (frame_count_ <= 3) {
            // First 3 frames: maximum duplication to ensure smooth start
            duplication_count = target_fps_ - 1;
            LOGI("Initial frame %d: Duplicating %d times (conservative start)", frame_count_, duplication_count);
        } else if (frame_count_ <= 10) {
            // Frames 4-10: adaptive duplication based on detected FPS
            duplication_count = static_cast<int>(target_fps_ / std::max(current_fps_, 0.5f)) - 1;
            duplication_count = std::min(duplication_count, 20);
            if (duplication_count > 0) {
                LOGI("Fast adaptation frame %d: Duplicating %d times for FPS %.2f", frame_count_, duplication_count, current_fps_);
            }
        } else {
            // Frames 11-20: normal duplication
            if (current_fps_ < min_fps_threshold_) {
                duplication_count = static_cast<int>(target_fps_ / std::max(current_fps_, 0.5f)) - 1;
                duplication_count = std::min(duplication_count, 15);
                if (duplication_count > 0) {
                    LOGI("Fast mode frame %d: Duplicating %d times for FPS %.2f", frame_count_, duplication_count, current_fps_);
                }
            }
        }
    } else {
        // Smooth detection mode - normal duplication
        if (current_fps_ < min_fps_threshold_) {
            duplication_count = static_cast<int>(target_fps_ / std::max(current_fps_, 0.5f)) - 1;
            duplication_count = std::min(duplication_count, 10);
            if (duplication_count > 0 && frame_count_ % 30 == 0) {
                LOGI("Smooth mode: Duplicating %d times for FPS %.2f", duplication_count, current_fps_);
            }
        }
    }
    
    // Add duplicated frames
    for (int i = 0; i < duplication_count; i++) {
        std::lock_guard<std::mutex> lock(queue_mutex_);
        if (frame_queue_.size() < 150) { // Increased queue size for initial frames
            frame_queue_.push(frame.clone());
            queue_cv_.notify_one();
        } else {
            break; // Prevent memory overflow
        }
    }
}

void MP4Recorder::cleanup() {
    // Reset frame rate monitoring
    frame_count_ = 0;
    current_fps_ = target_fps_;
    has_last_frame_ = false;
    
    // Cleanup is handled in stopRecording()
}