#include "video_decoder.h" #include #include extern "C" { #include } namespace scar { VideoDecoder::VideoDecoder() : codec_ctx_(nullptr), frame_(nullptr), sws_ctx_(nullptr), width_(0), height_(0), initialized_(false) { } VideoDecoder::~VideoDecoder() { cleanup(); } bool VideoDecoder::initialize() { cleanup(); // Clean up any previous state // Find H.264 decoder const AVCodec* codec = avcodec_find_decoder(AV_CODEC_ID_H264); if (!codec) { std::cerr << "H.264 decoder not found" << std::endl; return false; } std::cout << "Using H.264 decoder" << std::endl; // Create codec context codec_ctx_ = avcodec_alloc_context3(codec); if (!codec_ctx_) { std::cerr << "Failed to allocate decoder context" << std::endl; return false; } // Open codec if (avcodec_open2(codec_ctx_, codec, nullptr) < 0) { std::cerr << "Failed to open decoder" << std::endl; cleanup(); return false; } // Allocate frame frame_ = av_frame_alloc(); if (!frame_) { std::cerr << "Failed to allocate frame" << std::endl; cleanup(); return false; } initialized_ = true; std::cout << "Video decoder initialized" << std::endl; return true; } std::vector VideoDecoder::decode(const std::vector& encoded_data, int& out_width, int& out_height) { std::lock_guard lock(decode_mutex_); if (!initialized_) { std::cerr << "Decoder not initialized" << std::endl; return {}; } if (encoded_data.empty()) { std::cerr << "Empty encoded data" << std::endl; return {}; } static int decode_count = 0; decode_count++; // Create packet and copy data (FFmpeg will manage the memory) AVPacket* pkt = av_packet_alloc(); if (!pkt) { std::cerr << "Failed to allocate packet" << std::endl; return {}; } // Allocate buffer and copy data - FFmpeg will own and free this memory if (av_new_packet(pkt, encoded_data.size()) < 0) { std::cerr << "Failed to allocate packet buffer" << std::endl; av_packet_free(&pkt); return {}; } memcpy(pkt->data, encoded_data.data(), encoded_data.size()); // Send packet to decoder int ret = avcodec_send_packet(codec_ctx_, pkt); if (ret < 0) { char errbuf[256]; av_strerror(ret, errbuf, sizeof(errbuf)); std::cerr << "Error sending packet to decoder: " << errbuf << std::endl; av_packet_free(&pkt); return {}; } // Receive decoded frame ret = avcodec_receive_frame(codec_ctx_, frame_); // Free packet immediately after sending av_packet_free(&pkt); if (ret == AVERROR(EAGAIN)) { // Decoder needs more data return {}; } else if (ret < 0) { char errbuf[256]; av_strerror(ret, errbuf, sizeof(errbuf)); std::cerr << "Error receiving frame from decoder: " << errbuf << std::endl; return {}; } std::cout << "Decode #" << decode_count << ": frame " << frame_->width << "x" << frame_->height << std::endl; // Update dimensions if this is the first frame or they changed if (width_ != frame_->width || height_ != frame_->height) { width_ = frame_->width; height_ = frame_->height; // Recreate swscale context for new dimensions if (sws_ctx_) { sws_freeContext(sws_ctx_); } sws_ctx_ = sws_getContext( width_, height_, AV_PIX_FMT_YUV420P, width_, height_, AV_PIX_FMT_RGB24, SWS_FAST_BILINEAR, nullptr, nullptr, nullptr ); if (!sws_ctx_) { std::cerr << "Failed to create swscale context" << std::endl; return {}; } std::cout << "Decoder dimensions: " << width_ << "x" << height_ << std::endl; } // Calculate aligned linesize for RGB24 (4-byte alignment) int linesize = width_ * 3; linesize = (linesize + 3) & ~3; // Use av_image_get_buffer_size to get the exact size FFmpeg expects int buffer_size = av_image_get_buffer_size(AV_PIX_FMT_RGB24, width_, height_, 1); if (buffer_size < 0) { std::cerr << "Failed to get buffer size" << std::endl; av_frame_unref(frame_); return {}; } std::cout << "FFmpeg buffer size: " << buffer_size << " vs our calculation: " << (linesize * height_) << std::endl; // Allocate using FFmpeg's calculated size std::vector rgb_buffer(buffer_size); std::cout << "Allocated buffer: " << buffer_size << " bytes (linesize=" << linesize << ", width=" << width_ << ", height=" << height_ << ")" << std::endl; // Use av_image_fill_arrays to properly set up the buffer pointers uint8_t* dest[4] = {nullptr}; int dest_linesize[4] = {0}; av_image_fill_arrays(dest, dest_linesize, rgb_buffer.data(), AV_PIX_FMT_RGB24, width_, height_, 1); std::cout << "av_image_fill_arrays: linesize[0]=" << dest_linesize[0] << std::endl; std::cout << "Calling sws_scale..." << std::endl; int converted_height = sws_scale( sws_ctx_, frame_->data, frame_->linesize, 0, height_, dest, dest_linesize ); std::cout << "sws_scale returned: " << converted_height << std::endl; if (converted_height != height_) { std::cerr << "sws_scale failed: converted " << converted_height << " lines, expected " << height_ << std::endl; av_frame_unref(frame_); return {}; } // Clean up frame av_frame_unref(frame_); // Set output dimensions out_width = width_; out_height = height_; std::cout << "About to return buffer, size=" << rgb_buffer.size() << std::endl; // Use move semantics explicitly to avoid any copy return std::move(rgb_buffer); } void VideoDecoder::cleanup() { if (sws_ctx_) { sws_freeContext(sws_ctx_); sws_ctx_ = nullptr; } if (frame_) { av_frame_free(&frame_); frame_ = nullptr; } if (codec_ctx_) { avcodec_free_context(&codec_ctx_); codec_ctx_ = nullptr; } initialized_ = false; width_ = 0; height_ = 0; } } // namespace scar