227 lines
6.4 KiB
C++
227 lines
6.4 KiB
C++
#include "video_decoder.h"
|
|
#include <iostream>
|
|
#include <cstring>
|
|
|
|
extern "C" {
|
|
#include <libavutil/imgutils.h>
|
|
}
|
|
|
|
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<uint8_t> VideoDecoder::decode(const std::vector<uint8_t>& encoded_data, int& out_width, int& out_height) {
|
|
std::lock_guard<std::mutex> 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<uint8_t> 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
|