#include "VideoEncoder.h" #include VideoEncoder::VideoEncoder() = default; VideoEncoder::~VideoEncoder() { Release(); } bool VideoEncoder::Initialize(ID3D11Device* device, int width, int height, int fps, int bitrate) { device_ = device; device_->GetImmediateContext(&context_); width_ = width; height_ = height; // 1. Create Staging Texture for CPU access D3D11_TEXTURE2D_DESC desc = {}; desc.Width = width; desc.Height = height; desc.MipLevels = 1; desc.ArraySize = 1; desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; desc.SampleDesc.Count = 1; desc.Usage = D3D11_USAGE_STAGING; desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; HRESULT hr = device_->CreateTexture2D(&desc, nullptr, &stagingTexture_); if (FAILED(hr)) { std::cerr << "Failed to create staging texture" << std::endl; return false; } // 2. Initialize Encoder #ifndef NO_FFMPEG const AVCodec* codec = avcodec_find_encoder(AV_CODEC_ID_H264); if (!codec) { std::cerr << "Codec H.264 not found" << std::endl; return false; } codecContext_ = avcodec_alloc_context3(codec); if (!codecContext_) { std::cerr << "Could not allocate video codec context" << std::endl; return false; } codecContext_->bit_rate = bitrate; codecContext_->width = width; codecContext_->height = height; codecContext_->time_base = {1, fps}; codecContext_->framerate = {fps, 1}; codecContext_->gop_size = 10; codecContext_->max_b_frames = 1; codecContext_->pix_fmt = AV_PIX_FMT_YUV420P; // Set H.264 specific options (latency vs quality) av_opt_set(codecContext_->priv_data, "preset", "ultrafast", 0); av_opt_set(codecContext_->priv_data, "tune", "zerolatency", 0); if (avcodec_open2(codecContext_, codec, nullptr) < 0) { std::cerr << "Could not open codec" << std::endl; return false; } frame_ = av_frame_alloc(); if (!frame_) { std::cerr << "Could not allocate video frame" << std::endl; return false; } frame_->format = codecContext_->pix_fmt; frame_->width = codecContext_->width; frame_->height = codecContext_->height; if (av_frame_get_buffer(frame_, 32) < 0) { std::cerr << "Could not allocate the video frame data" << std::endl; return false; } packet_ = av_packet_alloc(); if (!packet_) { std::cerr << "Could not allocate packet" << std::endl; return false; } #else // Stub path without FFmpeg #endif return true; } void VideoEncoder::Release() { if (swsContext_) { sws_freeContext(swsContext_); swsContext_ = nullptr; } if (codecContext_) { avcodec_free_context(&codecContext_); codecContext_ = nullptr; } if (frame_) { av_frame_free(&frame_); frame_ = nullptr; } if (packet_) { av_packet_free(&packet_); packet_ = nullptr; } if (stagingTexture_) { stagingTexture_.Reset(); } pts_ = 0; } bool VideoEncoder::Reinitialize(int width, int height, int fps, int bitrate) { Release(); return Initialize(device_, width, height, fps, bitrate); } bool VideoEncoder::EncodeFrame(ID3D11Texture2D* texture, std::vector& outputData, bool& isKeyFrame) { if (!texture || !stagingTexture_ || !context_) return false; // 1. Copy GPU texture to Staging texture context_->CopyResource(stagingTexture_.Get(), texture); // 2. Map Staging texture to read data D3D11_MAPPED_SUBRESOURCE mapped = {}; HRESULT hr = context_->Map(stagingTexture_.Get(), 0, D3D11_MAP_READ, 0, &mapped); if (FAILED(hr)) return false; #ifndef NO_FFMPEG // 3. Convert BGRA to YUV420P if (!swsContext_) { swsContext_ = sws_getContext( width_, height_, AV_PIX_FMT_BGRA, width_, height_, AV_PIX_FMT_YUV420P, SWS_BILINEAR, nullptr, nullptr, nullptr ); } uint8_t* srcSlice[] = { (uint8_t*)mapped.pData }; int srcStride[] = { (int)mapped.RowPitch }; sws_scale(swsContext_, srcSlice, srcStride, 0, height_, frame_->data, frame_->linesize); #endif context_->Unmap(stagingTexture_.Get(), 0); #ifndef NO_FFMPEG // 4. Encode frame_->pts = pts_++; int ret = avcodec_send_frame(codecContext_, frame_); if (ret < 0) { std::cerr << "Error sending a frame for encoding" << std::endl; return false; } while (ret >= 0) { ret = avcodec_receive_packet(codecContext_, packet_); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) break; else if (ret < 0) { std::cerr << "Error during encoding" << std::endl; return false; } outputData.insert(outputData.end(), packet_->data, packet_->data + packet_->size); if (packet_->flags & AV_PKT_FLAG_KEY) isKeyFrame = true; av_packet_unref(packet_); } #else // Stub: no encoding, but pipeline succeeds isKeyFrame = false; #endif return true; }