#include "video_encoder.h" #include #include namespace scar { VideoEncoder::VideoEncoder() : codec_ctx_(nullptr), frame_(nullptr), packet_(nullptr), sws_ctx_(nullptr), width_(0), height_(0), frame_counter_(0), initialized_(false) { } VideoEncoder::~VideoEncoder() { cleanup(); } bool VideoEncoder::initialize(int width, int height, int fps, int bitrate) { cleanup(); // Clean up any previous state width_ = width; height_ = height; // Find H.264 encoder const AVCodec* codec = avcodec_find_encoder_by_name("libx264"); if (!codec) { // Try hardware encoders codec = avcodec_find_encoder_by_name("h264_vaapi"); if (!codec) { codec = avcodec_find_encoder_by_name("h264_nvenc"); } if (!codec) { std::cerr << "H.264 encoder not found" << std::endl; return false; } } std::cout << "Using encoder: " << codec->name << std::endl; // Create codec context codec_ctx_ = avcodec_alloc_context3(codec); if (!codec_ctx_) { std::cerr << "Failed to allocate codec context" << std::endl; return false; } // Set encoding parameters codec_ctx_->width = width_; codec_ctx_->height = height_; codec_ctx_->time_base = AVRational{1, fps}; codec_ctx_->framerate = AVRational{fps, 1}; codec_ctx_->bit_rate = bitrate; codec_ctx_->gop_size = fps; // One I-frame per second codec_ctx_->max_b_frames = 0; // Disable B-frames for low latency codec_ctx_->pix_fmt = AV_PIX_FMT_YUV420P; // Set encoding preset for low latency av_opt_set(codec_ctx_->priv_data, "preset", "ultrafast", 0); av_opt_set(codec_ctx_->priv_data, "tune", "zerolatency", 0); // Open codec if (avcodec_open2(codec_ctx_, codec, nullptr) < 0) { std::cerr << "Failed to open codec" << std::endl; cleanup(); return false; } // Allocate frame frame_ = av_frame_alloc(); if (!frame_) { std::cerr << "Failed to allocate frame" << std::endl; cleanup(); return false; } frame_->format = codec_ctx_->pix_fmt; frame_->width = width_; frame_->height = height_; if (av_frame_get_buffer(frame_, 0) < 0) { std::cerr << "Failed to allocate frame buffer" << std::endl; cleanup(); return false; } // Allocate packet packet_ = av_packet_alloc(); if (!packet_) { std::cerr << "Failed to allocate packet" << std::endl; cleanup(); return false; } // Initialize swscale context for BGR0 -> YUV420P conversion sws_ctx_ = sws_getContext( width_, height_, AV_PIX_FMT_BGR0, width_, height_, AV_PIX_FMT_YUV420P, SWS_FAST_BILINEAR, nullptr, nullptr, nullptr ); if (!sws_ctx_) { std::cerr << "Failed to create swscale context" << std::endl; cleanup(); return false; } initialized_ = true; frame_counter_ = 0; std::cout << "Video encoder initialized: " << width_ << "x" << height_ << " @ " << fps << " fps, " << bitrate << " bps" << std::endl; return true; } std::vector VideoEncoder::encode(const uint8_t* frame_data, uint32_t frame_size) { if (!initialized_) { std::cerr << "Encoder not initialized" << std::endl; return {}; } // Validate frame size (should be width * height * 4 for BGR0) uint32_t expected_size = width_ * height_ * 4; if (frame_size != expected_size) { std::cerr << "Invalid frame size: " << frame_size << " (expected " << expected_size << ")" << std::endl; return {}; } // Make frame writable if (av_frame_make_writable(frame_) < 0) { std::cerr << "Failed to make frame writable" << std::endl; return {}; } // Convert BGR0 to YUV420P const uint8_t* src_data[1] = { frame_data }; int src_linesize[1] = { width_ * 4 }; sws_scale( sws_ctx_, src_data, src_linesize, 0, height_, frame_->data, frame_->linesize ); frame_->pts = frame_counter_++; // Send frame to encoder int ret = avcodec_send_frame(codec_ctx_, frame_); if (ret < 0) { std::cerr << "Error sending frame to encoder" << std::endl; return {}; } // Receive encoded packet ret = avcodec_receive_packet(codec_ctx_, packet_); if (ret == AVERROR(EAGAIN)) { // Encoder needs more frames return {}; } else if (ret < 0) { std::cerr << "Error receiving packet from encoder" << std::endl; return {}; } // Copy packet data to vector std::vector encoded_data(packet_->data, packet_->data + packet_->size); av_packet_unref(packet_); return encoded_data; } std::vector VideoEncoder::flush() { if (!initialized_) { return {}; } // Send NULL frame to flush encoder avcodec_send_frame(codec_ctx_, nullptr); // Receive any remaining packets std::vector result; while (true) { int ret = avcodec_receive_packet(codec_ctx_, packet_); if (ret == AVERROR_EOF || ret == AVERROR(EAGAIN)) { break; } if (ret < 0) { break; } result.insert(result.end(), packet_->data, packet_->data + packet_->size); av_packet_unref(packet_); } return result; } void VideoEncoder::cleanup() { if (sws_ctx_) { sws_freeContext(sws_ctx_); sws_ctx_ = nullptr; } if (packet_) { av_packet_free(&packet_); packet_ = nullptr; } if (frame_) { av_frame_free(&frame_); frame_ = nullptr; } if (codec_ctx_) { avcodec_free_context(&codec_ctx_); codec_ctx_ = nullptr; } initialized_ = false; } } // namespace scar