|
|
@@ -0,0 +1,797 @@
|
|
|
+#include "codec_video_encoder.h"
|
|
|
+#include "../base/logger.h"
|
|
|
+#include <algorithm>
|
|
|
+#include <chrono>
|
|
|
+
|
|
|
+extern "C" {
|
|
|
+#include <libswscale/swscale.h>
|
|
|
+#include <libavutil/imgutils.h>
|
|
|
+}
|
|
|
+
|
|
|
+namespace av {
|
|
|
+namespace codec {
|
|
|
+
|
|
|
+// 静态成员初始化
|
|
|
+std::vector<std::string> VideoEncoder::supportedEncoders_;
|
|
|
+std::once_flag VideoEncoder::encodersInitFlag_;
|
|
|
+
|
|
|
+// PixelFormatConverter 实现
|
|
|
+PixelFormatConverter::PixelFormatConverter(AVPixelFormat srcFormat, AVPixelFormat dstFormat)
|
|
|
+ : swsCtx_(nullptr)
|
|
|
+ , srcFormat_(srcFormat)
|
|
|
+ , dstFormat_(dstFormat)
|
|
|
+ , width_(0)
|
|
|
+ , height_(0) {
|
|
|
+}
|
|
|
+
|
|
|
+PixelFormatConverter::~PixelFormatConverter() {
|
|
|
+ if (swsCtx_) {
|
|
|
+ sws_freeContext(swsCtx_);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+bool PixelFormatConverter::setSize(int width, int height) {
|
|
|
+ if (width_ == width && height_ == height && swsCtx_) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ width_ = width;
|
|
|
+ height_ = height;
|
|
|
+
|
|
|
+ if (swsCtx_) {
|
|
|
+ sws_freeContext(swsCtx_);
|
|
|
+ }
|
|
|
+
|
|
|
+ swsCtx_ = sws_getContext(
|
|
|
+ width, height, srcFormat_,
|
|
|
+ width, height, dstFormat_,
|
|
|
+ SWS_BILINEAR, nullptr, nullptr, nullptr
|
|
|
+ );
|
|
|
+
|
|
|
+ if (!swsCtx_) {
|
|
|
+ AV_LOGGER_ERROR("创建像素格式转换器失败");
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 创建目标帧
|
|
|
+ dstFrame_ = makeAVFrame();
|
|
|
+ if (!dstFrame_) {
|
|
|
+ AV_LOGGER_ERROR("分配目标帧失败");
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ dstFrame_->format = dstFormat_;
|
|
|
+ dstFrame_->width = width;
|
|
|
+ dstFrame_->height = height;
|
|
|
+
|
|
|
+ if (av_frame_get_buffer(dstFrame_.get(), 32) < 0) {
|
|
|
+ AV_LOGGER_ERROR("分配帧缓冲区失败");
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+AVFramePtr PixelFormatConverter::convert(const AVFramePtr& srcFrame) {
|
|
|
+ if (!srcFrame || !swsCtx_) {
|
|
|
+ return nullptr;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!setSize(srcFrame->width, srcFrame->height)) {
|
|
|
+ return nullptr;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 复制时间戳等信息
|
|
|
+ av_frame_copy_props(dstFrame_.get(), srcFrame.get());
|
|
|
+
|
|
|
+ // 执行格式转换
|
|
|
+ int ret = sws_scale(
|
|
|
+ swsCtx_,
|
|
|
+ srcFrame->data, srcFrame->linesize,
|
|
|
+ 0, srcFrame->height,
|
|
|
+ dstFrame_->data, dstFrame_->linesize
|
|
|
+ );
|
|
|
+
|
|
|
+ if (ret < 0) {
|
|
|
+ AV_LOGGER_ERRORF("像素格式转换失败: {}", ffmpeg_utils::errorToString(ret));
|
|
|
+ return nullptr;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 创建一个新的帧来返回,避免拷贝构造
|
|
|
+ AVFramePtr resultFrame = makeAVFrame();
|
|
|
+ if (!resultFrame) {
|
|
|
+ return nullptr;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 复制帧数据
|
|
|
+ if (av_frame_ref(resultFrame.get(), dstFrame_.get()) < 0) {
|
|
|
+ return nullptr;
|
|
|
+ }
|
|
|
+
|
|
|
+ return resultFrame;
|
|
|
+}
|
|
|
+
|
|
|
+// VideoEncoder 实现
|
|
|
+VideoEncoder::VideoEncoder()
|
|
|
+ : AbstractEncoder(MediaType::VIDEO)
|
|
|
+ , isHardwareEncoder_(false)
|
|
|
+ , hwDeviceCtx_(nullptr) {
|
|
|
+ AV_LOGGER_DEBUG("创建视频编码器");
|
|
|
+}
|
|
|
+
|
|
|
+VideoEncoder::~VideoEncoder() {
|
|
|
+ close();
|
|
|
+ AV_LOGGER_DEBUG("视频编码器已销毁");
|
|
|
+}
|
|
|
+
|
|
|
+ErrorCode VideoEncoder::open(const CodecParams& params) {
|
|
|
+ std::lock_guard<std::mutex> lock(encodeMutex_);
|
|
|
+
|
|
|
+ if (state_ != CodecState::IDLE && state_ != CodecState::CLOSED) {
|
|
|
+ AV_LOGGER_WARNING("编码器已打开,先关闭再重新打开");
|
|
|
+ close();
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!validateParams(params)) {
|
|
|
+ return ErrorCode::INVALID_ARGUMENT;
|
|
|
+ }
|
|
|
+
|
|
|
+ videoParams_ = static_cast<const VideoEncoderParams&>(params);
|
|
|
+ params_ = params;
|
|
|
+
|
|
|
+ ErrorCode result = initEncoder();
|
|
|
+ if (result != ErrorCode::OK) {
|
|
|
+ close();
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ setState(CodecState::OPENED);
|
|
|
+ AV_LOGGER_INFOF("视频编码器已打开: {} ({}x{} @ {}fps)",
|
|
|
+ videoParams_.codecName, videoParams_.width,
|
|
|
+ videoParams_.height, videoParams_.fps);
|
|
|
+
|
|
|
+ return ErrorCode::OK;
|
|
|
+}
|
|
|
+
|
|
|
+void VideoEncoder::close() {
|
|
|
+ std::lock_guard<std::mutex> lock(encodeMutex_);
|
|
|
+
|
|
|
+ if (state_ == CodecState::CLOSED || state_ == CodecState::IDLE) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 清理硬件资源
|
|
|
+ if (hwDeviceCtx_) {
|
|
|
+ av_buffer_unref(&hwDeviceCtx_);
|
|
|
+ hwDeviceCtx_ = nullptr;
|
|
|
+ }
|
|
|
+
|
|
|
+ hwFrame_.reset();
|
|
|
+ convertedFrame_.reset();
|
|
|
+ converter_.reset();
|
|
|
+
|
|
|
+ // 清理编解码上下文
|
|
|
+ codecCtx_.reset();
|
|
|
+ codec_ = nullptr;
|
|
|
+
|
|
|
+ setState(CodecState::CLOSED);
|
|
|
+ AV_LOGGER_DEBUG("视频编码器已关闭");
|
|
|
+}
|
|
|
+
|
|
|
+ErrorCode VideoEncoder::flush() {
|
|
|
+ std::lock_guard<std::mutex> lock(encodeMutex_);
|
|
|
+
|
|
|
+ if (state_ != CodecState::OPENED && state_ != CodecState::RUNNING) {
|
|
|
+ return ErrorCode::INVALID_STATE;
|
|
|
+ }
|
|
|
+
|
|
|
+ setState(CodecState::FLUSHING);
|
|
|
+
|
|
|
+ // 发送空帧来刷新编码器
|
|
|
+ int ret = avcodec_send_frame(codecCtx_.get(), nullptr);
|
|
|
+ if (ret < 0 && ret != AVERROR_EOF) {
|
|
|
+ AV_LOGGER_ERRORF("刷新编码器失败: {}", ffmpeg_utils::errorToString(ret));
|
|
|
+ reportError(static_cast<ErrorCode>(ret));
|
|
|
+ return static_cast<ErrorCode>(ret);
|
|
|
+ }
|
|
|
+
|
|
|
+ setState(CodecState::OPENED);
|
|
|
+ return ErrorCode::OK;
|
|
|
+}
|
|
|
+
|
|
|
+ErrorCode VideoEncoder::encode(const AVFramePtr& frame, std::vector<AVPacketPtr>& packets) {
|
|
|
+ std::lock_guard<std::mutex> lock(encodeMutex_);
|
|
|
+
|
|
|
+ if (state_ != CodecState::OPENED && state_ != CodecState::RUNNING) {
|
|
|
+ return ErrorCode::INVALID_STATE;
|
|
|
+ }
|
|
|
+
|
|
|
+ setState(CodecState::RUNNING);
|
|
|
+
|
|
|
+ auto startTime = std::chrono::high_resolution_clock::now();
|
|
|
+
|
|
|
+ ErrorCode result = encodeFrame(frame, packets);
|
|
|
+
|
|
|
+ auto endTime = std::chrono::high_resolution_clock::now();
|
|
|
+ double processTime = std::chrono::duration<double, std::milli>(endTime - startTime).count();
|
|
|
+
|
|
|
+ updateStats(result == ErrorCode::OK, processTime,
|
|
|
+ frame ? frame->width * frame->height * 3 / 2 : 0);
|
|
|
+
|
|
|
+ if (frameCallback_ && frame) {
|
|
|
+ frameCallback_(frame);
|
|
|
+ }
|
|
|
+
|
|
|
+ for (const auto& packet : packets) {
|
|
|
+ if (packetCallback_) {
|
|
|
+ packetCallback_(packet);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return result;
|
|
|
+}
|
|
|
+
|
|
|
+ErrorCode VideoEncoder::finishEncode(std::vector<AVPacketPtr>& packets) {
|
|
|
+ return flush();
|
|
|
+}
|
|
|
+
|
|
|
+bool VideoEncoder::validateParams(const CodecParams& params) {
|
|
|
+ if (params.type != MediaType::VIDEO) {
|
|
|
+ AV_LOGGER_ERROR("参数媒体类型不是视频");
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ const auto& videoParams = static_cast<const VideoEncoderParams&>(params);
|
|
|
+
|
|
|
+ if (videoParams.width <= 0 || videoParams.height <= 0) {
|
|
|
+ AV_LOGGER_ERROR("视频尺寸无效");
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (videoParams.fps <= 0) {
|
|
|
+ AV_LOGGER_ERROR("帧率无效");
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (videoParams.bitRate <= 0) {
|
|
|
+ AV_LOGGER_ERROR("比特率无效");
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (videoParams.codecName.empty()) {
|
|
|
+ AV_LOGGER_ERROR("编码器名称为空");
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+ErrorCode VideoEncoder::initEncoder() {
|
|
|
+ // 查找编码器
|
|
|
+ codec_ = avcodec_find_encoder_by_name(videoParams_.codecName.c_str());
|
|
|
+ if (!codec_) {
|
|
|
+ AV_LOGGER_ERRORF("未找到编码器: {}", videoParams_.codecName);
|
|
|
+ return ErrorCode::CODEC_NOT_FOUND;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (codec_->type != AVMEDIA_TYPE_VIDEO) {
|
|
|
+ AV_LOGGER_ERROR("编码器类型不是视频");
|
|
|
+ return ErrorCode::INVALID_ARGUMENT;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 创建编码上下文
|
|
|
+ codecCtx_ = makeAVCodecContext(codec_);
|
|
|
+ if (!codecCtx_) {
|
|
|
+ AV_LOGGER_ERROR("分配编码上下文失败");
|
|
|
+ return ErrorCode::OUT_OF_MEMORY;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 设置硬件加速
|
|
|
+ if (videoParams_.hardwareAccel && isHardwareEncoder(videoParams_.codecName)) {
|
|
|
+ ErrorCode result = setupHardwareAcceleration();
|
|
|
+ if (result != ErrorCode::OK) {
|
|
|
+ AV_LOGGER_WARNING("硬件加速设置失败,回退到软件编码");
|
|
|
+ isHardwareEncoder_ = false;
|
|
|
+
|
|
|
+ // 清理硬件资源
|
|
|
+ if (hwDeviceCtx_) {
|
|
|
+ av_buffer_unref(&hwDeviceCtx_);
|
|
|
+ hwDeviceCtx_ = nullptr;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 设置编码器参数(在硬件加速设置之后)
|
|
|
+ ErrorCode result = setupEncoderParams();
|
|
|
+ if (result != ErrorCode::OK) {
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 打开编码器前的详细日志
|
|
|
+ AV_LOGGER_INFOF("准备打开编码器: {}", videoParams_.codecName);
|
|
|
+ AV_LOGGER_INFOF("编码器参数: {}x{}, 比特率: {}, 帧率: {}, 像素格式: {}",
|
|
|
+ codecCtx_->width, codecCtx_->height, codecCtx_->bit_rate,
|
|
|
+ codecCtx_->framerate.num, static_cast<int>(codecCtx_->pix_fmt));
|
|
|
+
|
|
|
+ if (isHardwareEncoder_) {
|
|
|
+ AV_LOGGER_INFOF("硬件编码器状态: 设备上下文={}, 帧上下文={}",
|
|
|
+ hwDeviceCtx_ ? "已创建" : "未创建",
|
|
|
+ codecCtx_->hw_frames_ctx ? "已设置" : "未设置");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 验证编码器参数
|
|
|
+ if (codecCtx_->width <= 0 || codecCtx_->height <= 0) {
|
|
|
+ AV_LOGGER_ERROR("无效的视频尺寸参数");
|
|
|
+ return ErrorCode::INVALID_PARAMS;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (codecCtx_->bit_rate <= 0) {
|
|
|
+ AV_LOGGER_ERROR("无效的比特率参数");
|
|
|
+ return ErrorCode::INVALID_PARAMS;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 打开编码器
|
|
|
+ int ret = avcodec_open2(codecCtx_.get(), codec_, nullptr);
|
|
|
+ if (ret < 0) {
|
|
|
+ AV_LOGGER_ERRORF("打开编码器失败: {} (错误码: {})", ffmpeg_utils::errorToString(ret), ret);
|
|
|
+
|
|
|
+ // 详细错误分析
|
|
|
+ if (ret == AVERROR(EINVAL)) {
|
|
|
+ AV_LOGGER_ERROR("编码器参数无效 - 可能的原因:");
|
|
|
+ AV_LOGGER_ERROR(" 1. 不支持的像素格式或分辨率组合");
|
|
|
+ AV_LOGGER_ERROR(" 2. 硬件编码器参数配置错误");
|
|
|
+ AV_LOGGER_ERROR(" 3. 硬件设备上下文与编码器不匹配");
|
|
|
+ } else if (ret == AVERROR(EBUSY)) {
|
|
|
+ AV_LOGGER_ERROR("硬件设备忙碌 - 可能被其他进程占用");
|
|
|
+ } else if (ret == AVERROR(ENOMEM)) {
|
|
|
+ AV_LOGGER_ERROR("内存不足 - 无法分配编码器资源");
|
|
|
+ }
|
|
|
+
|
|
|
+ return static_cast<ErrorCode>(ret);
|
|
|
+ }
|
|
|
+
|
|
|
+ AV_LOGGER_INFOF("编码器打开成功: {}", videoParams_.codecName);
|
|
|
+
|
|
|
+ return ErrorCode::OK;
|
|
|
+}
|
|
|
+
|
|
|
+ErrorCode VideoEncoder::setupEncoderParams() {
|
|
|
+ codecCtx_->bit_rate = videoParams_.bitRate;
|
|
|
+ codecCtx_->width = videoParams_.width;
|
|
|
+ codecCtx_->height = videoParams_.height;
|
|
|
+ codecCtx_->time_base = {1, videoParams_.fps};
|
|
|
+ codecCtx_->framerate = {videoParams_.fps, 1};
|
|
|
+ codecCtx_->gop_size = videoParams_.gopSize;
|
|
|
+ codecCtx_->max_b_frames = videoParams_.maxBFrames;
|
|
|
+
|
|
|
+ // 设置像素格式
|
|
|
+ if (isHardwareEncoder_) {
|
|
|
+ AVPixelFormat hwPixFmt = getHardwarePixelFormat();
|
|
|
+ if (hwPixFmt == AV_PIX_FMT_NONE) {
|
|
|
+ AV_LOGGER_ERRORF("获取硬件像素格式失败,编码器: {}", videoParams_.codecName);
|
|
|
+ return ErrorCode::NOT_SUPPORTED;
|
|
|
+ }
|
|
|
+ codecCtx_->pix_fmt = hwPixFmt;
|
|
|
+ AV_LOGGER_INFOF("设置硬件编码器像素格式: {} ({})",
|
|
|
+ static_cast<int>(hwPixFmt), av_get_pix_fmt_name(hwPixFmt));
|
|
|
+ } else {
|
|
|
+ codecCtx_->pix_fmt = videoParams_.pixelFormat;
|
|
|
+ AV_LOGGER_INFOF("设置软件编码器像素格式: {} ({})",
|
|
|
+ static_cast<int>(videoParams_.pixelFormat),
|
|
|
+ av_get_pix_fmt_name(videoParams_.pixelFormat));
|
|
|
+ }
|
|
|
+
|
|
|
+ // 针对不同编码器设置特定参数
|
|
|
+ if (videoParams_.codecName == "h264_nvenc") {
|
|
|
+ AV_LOGGER_INFO("设置NVENC编码器参数...");
|
|
|
+
|
|
|
+ // NVENC 特定参数 - 使用更保守的设置
|
|
|
+ int ret;
|
|
|
+ ret = av_opt_set(codecCtx_->priv_data, "preset", "medium", 0);
|
|
|
+ AV_LOGGER_INFOF("设置preset=medium: {}", ret == 0 ? "成功" : "失败");
|
|
|
+
|
|
|
+ ret = av_opt_set(codecCtx_->priv_data, "profile", "main", 0);
|
|
|
+ AV_LOGGER_INFOF("设置profile=main: {}", ret == 0 ? "成功" : "失败");
|
|
|
+
|
|
|
+ ret = av_opt_set(codecCtx_->priv_data, "rc", "vbr", 0);
|
|
|
+ AV_LOGGER_INFOF("设置rc=vbr: {}", ret == 0 ? "成功" : "失败");
|
|
|
+
|
|
|
+ ret = av_opt_set_int(codecCtx_->priv_data, "surfaces", 16, 0);
|
|
|
+ AV_LOGGER_INFOF("设置surfaces=16: {}", ret == 0 ? "成功" : "失败");
|
|
|
+
|
|
|
+ ret = av_opt_set_int(codecCtx_->priv_data, "delay", 0, 0);
|
|
|
+ AV_LOGGER_INFOF("设置delay=0: {}", ret == 0 ? "成功" : "失败");
|
|
|
+
|
|
|
+ if (videoParams_.lowLatency) {
|
|
|
+ AV_LOGGER_INFO("启用低延迟模式");
|
|
|
+ ret = av_opt_set(codecCtx_->priv_data, "preset", "fast", 0);
|
|
|
+ AV_LOGGER_INFOF("设置preset=fast: {}", ret == 0 ? "成功" : "失败");
|
|
|
+
|
|
|
+ ret = av_opt_set(codecCtx_->priv_data, "tune", "ll", 0);
|
|
|
+ AV_LOGGER_INFOF("设置tune=ll: {}", ret == 0 ? "成功" : "失败");
|
|
|
+ }
|
|
|
+ } else if (videoParams_.codecName == "h264_qsv") {
|
|
|
+ // QSV 特定参数
|
|
|
+ av_opt_set(codecCtx_->priv_data, "preset", "fast", 0);
|
|
|
+ av_opt_set(codecCtx_->priv_data, "profile", "high", 0);
|
|
|
+
|
|
|
+ if (videoParams_.lowLatency) {
|
|
|
+ av_opt_set(codecCtx_->priv_data, "preset", "veryfast", 0);
|
|
|
+ }
|
|
|
+ } else if (videoParams_.codecName == "h264_amf") {
|
|
|
+ // AMF 特定参数
|
|
|
+ av_opt_set(codecCtx_->priv_data, "quality", "speed", 0);
|
|
|
+ av_opt_set(codecCtx_->priv_data, "profile", "high", 0);
|
|
|
+
|
|
|
+ if (videoParams_.lowLatency) {
|
|
|
+ av_opt_set(codecCtx_->priv_data, "usage", "lowlatency", 0);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 软件编码器参数
|
|
|
+ if (!videoParams_.preset.empty()) {
|
|
|
+ av_opt_set(codecCtx_->priv_data, "preset", videoParams_.preset.c_str(), 0);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!videoParams_.tune.empty()) {
|
|
|
+ av_opt_set(codecCtx_->priv_data, "tune", videoParams_.tune.c_str(), 0);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (videoParams_.lowLatency) {
|
|
|
+ av_opt_set(codecCtx_->priv_data, "preset", "ultrafast", 0);
|
|
|
+ av_opt_set(codecCtx_->priv_data, "tune", "zerolatency", 0);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return ErrorCode::OK;
|
|
|
+}
|
|
|
+
|
|
|
+ErrorCode VideoEncoder::setupHardwareAcceleration() {
|
|
|
+ isHardwareEncoder_ = true;
|
|
|
+
|
|
|
+ AVHWDeviceType hwType = getHardwareDeviceType();
|
|
|
+ if (hwType == AV_HWDEVICE_TYPE_NONE) {
|
|
|
+ AV_LOGGER_ERRORF("不支持的硬件编码器: {}", videoParams_.codecName);
|
|
|
+ return ErrorCode::NOT_SUPPORTED;
|
|
|
+ }
|
|
|
+
|
|
|
+ AV_LOGGER_INFOF("开始设置硬件加速: 编码器={}, 设备类型={}",
|
|
|
+ videoParams_.codecName, static_cast<int>(hwType));
|
|
|
+
|
|
|
+ // 对于CUDA设备,检查可用性
|
|
|
+ if (hwType == AV_HWDEVICE_TYPE_CUDA) {
|
|
|
+ AV_LOGGER_INFO("检查CUDA设备可用性...");
|
|
|
+ // 这里可以添加CUDA设备检查逻辑
|
|
|
+ }
|
|
|
+
|
|
|
+ // 创建硬件设备上下文
|
|
|
+ AV_LOGGER_INFO("创建硬件设备上下文...");
|
|
|
+ int ret = av_hwdevice_ctx_create(&hwDeviceCtx_, hwType, nullptr, nullptr, 0);
|
|
|
+ if (ret < 0) {
|
|
|
+ AV_LOGGER_ERRORF("创建硬件设备上下文失败: {} (编码器: {}, 错误码: {})",
|
|
|
+ ffmpeg_utils::errorToString(ret), videoParams_.codecName, ret);
|
|
|
+
|
|
|
+ // 特定错误处理
|
|
|
+ if (ret == AVERROR(ENOENT)) {
|
|
|
+ AV_LOGGER_ERROR("硬件设备不存在或驱动未安装");
|
|
|
+ if (hwType == AV_HWDEVICE_TYPE_CUDA) {
|
|
|
+ AV_LOGGER_ERROR("请检查NVIDIA驱动和CUDA是否正确安装");
|
|
|
+ }
|
|
|
+ } else if (ret == AVERROR(EBUSY)) {
|
|
|
+ AV_LOGGER_ERROR("硬件设备正在被其他进程使用");
|
|
|
+ } else if (ret == AVERROR(EINVAL)) {
|
|
|
+ AV_LOGGER_ERROR("硬件设备参数无效");
|
|
|
+ } else if (ret == AVERROR(ENOMEM)) {
|
|
|
+ AV_LOGGER_ERROR("内存不足,无法创建硬件设备上下文");
|
|
|
+ }
|
|
|
+
|
|
|
+ return static_cast<ErrorCode>(ret);
|
|
|
+ }
|
|
|
+
|
|
|
+ AV_LOGGER_INFOF("硬件设备上下文创建成功: {}", videoParams_.codecName);
|
|
|
+
|
|
|
+ // 设置硬件帧上下文
|
|
|
+ return setupHardwareFrameContext();
|
|
|
+}
|
|
|
+
|
|
|
+ErrorCode VideoEncoder::setupHardwareFrameContext() {
|
|
|
+ AVBufferRef* hwFramesRef = av_hwframe_ctx_alloc(hwDeviceCtx_);
|
|
|
+ if (!hwFramesRef) {
|
|
|
+ AV_LOGGER_ERROR("分配硬件帧上下文失败");
|
|
|
+ return ErrorCode::OUT_OF_MEMORY;
|
|
|
+ }
|
|
|
+
|
|
|
+ AVHWFramesContext* framesCtx = reinterpret_cast<AVHWFramesContext*>(hwFramesRef->data);
|
|
|
+ framesCtx->format = getHardwarePixelFormat();
|
|
|
+
|
|
|
+ // 设置软件格式 - 统一使用NV12格式,这是大多数硬件编码器的首选格式
|
|
|
+ framesCtx->sw_format = AV_PIX_FMT_NV12;
|
|
|
+
|
|
|
+ framesCtx->width = videoParams_.width;
|
|
|
+ framesCtx->height = videoParams_.height;
|
|
|
+ framesCtx->initial_pool_size = 8; // 增加池大小以提高稳定性
|
|
|
+
|
|
|
+ AV_LOGGER_INFOF("设置硬件帧上下文: {}x{}, 硬件格式: {}, 软件格式: {}, 池大小: {}",
|
|
|
+ framesCtx->width, framesCtx->height,
|
|
|
+ static_cast<int>(framesCtx->format),
|
|
|
+ static_cast<int>(framesCtx->sw_format),
|
|
|
+ framesCtx->initial_pool_size);
|
|
|
+
|
|
|
+ int ret = av_hwframe_ctx_init(hwFramesRef);
|
|
|
+ if (ret < 0) {
|
|
|
+ AV_LOGGER_ERRORF("初始化硬件帧上下文失败: {}", ffmpeg_utils::errorToString(ret));
|
|
|
+ av_buffer_unref(&hwFramesRef);
|
|
|
+ return static_cast<ErrorCode>(ret);
|
|
|
+ }
|
|
|
+
|
|
|
+ codecCtx_->hw_frames_ctx = av_buffer_ref(hwFramesRef);
|
|
|
+ av_buffer_unref(&hwFramesRef);
|
|
|
+
|
|
|
+ AV_LOGGER_INFO("硬件帧上下文初始化成功");
|
|
|
+ return ErrorCode::OK;
|
|
|
+}
|
|
|
+
|
|
|
+AVFramePtr VideoEncoder::convertFrame(const AVFramePtr& frame) {
|
|
|
+ if (!frame) {
|
|
|
+ return nullptr;
|
|
|
+ }
|
|
|
+
|
|
|
+ AVPixelFormat targetFormat;
|
|
|
+ if (isHardwareEncoder_) {
|
|
|
+ // 硬件编码器统一使用NV12格式,与硬件帧上下文保持一致
|
|
|
+ targetFormat = AV_PIX_FMT_NV12;
|
|
|
+ } else {
|
|
|
+ targetFormat = videoParams_.pixelFormat;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果格式已经匹配,创建新帧返回
|
|
|
+ if (frame->format == targetFormat) {
|
|
|
+ // 创建一个新的帧来返回,避免拷贝构造
|
|
|
+ AVFramePtr resultFrame = makeAVFrame();
|
|
|
+ if (!resultFrame) {
|
|
|
+ return nullptr;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 复制帧数据
|
|
|
+ if (av_frame_ref(resultFrame.get(), frame.get()) < 0) {
|
|
|
+ return nullptr;
|
|
|
+ }
|
|
|
+
|
|
|
+ return resultFrame;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 创建转换器
|
|
|
+ if (!converter_ ||
|
|
|
+ converter_->srcFormat_ != static_cast<AVPixelFormat>(frame->format) ||
|
|
|
+ converter_->dstFormat_ != targetFormat) {
|
|
|
+ converter_ = std::make_unique<PixelFormatConverter>(
|
|
|
+ static_cast<AVPixelFormat>(frame->format), targetFormat);
|
|
|
+ }
|
|
|
+
|
|
|
+ return converter_->convert(frame);
|
|
|
+}
|
|
|
+
|
|
|
+AVFramePtr VideoEncoder::transferToHardware(const AVFramePtr& frame) {
|
|
|
+ if (!isHardwareEncoder_ || !frame) {
|
|
|
+ // 创建一个新的帧来返回,避免拷贝构造
|
|
|
+ if (frame) {
|
|
|
+ AVFramePtr resultFrame = makeAVFrame();
|
|
|
+ if (!resultFrame) {
|
|
|
+ return nullptr;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 复制帧数据
|
|
|
+ if (av_frame_ref(resultFrame.get(), frame.get()) < 0) {
|
|
|
+ return nullptr;
|
|
|
+ }
|
|
|
+
|
|
|
+ return resultFrame;
|
|
|
+ }
|
|
|
+ return nullptr;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 分配硬件帧
|
|
|
+ hwFrame_ = makeAVFrame();
|
|
|
+ if (!hwFrame_) {
|
|
|
+ AV_LOGGER_ERROR("分配硬件帧失败");
|
|
|
+ return nullptr;
|
|
|
+ }
|
|
|
+
|
|
|
+ int ret = av_hwframe_get_buffer(codecCtx_->hw_frames_ctx, hwFrame_.get(), 0);
|
|
|
+ if (ret < 0) {
|
|
|
+ AV_LOGGER_ERRORF("获取硬件帧缓冲区失败: {}", ffmpeg_utils::errorToString(ret));
|
|
|
+ return nullptr;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 传输数据到硬件
|
|
|
+ ret = av_hwframe_transfer_data(hwFrame_.get(), frame.get(), 0);
|
|
|
+ if (ret < 0) {
|
|
|
+ AV_LOGGER_ERRORF("传输数据到硬件失败: {}", ffmpeg_utils::errorToString(ret));
|
|
|
+ return nullptr;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 复制时间戳等信息
|
|
|
+ av_frame_copy_props(hwFrame_.get(), frame.get());
|
|
|
+
|
|
|
+ return std::move(hwFrame_);
|
|
|
+}
|
|
|
+
|
|
|
+ErrorCode VideoEncoder::encodeFrame(const AVFramePtr& frame, std::vector<AVPacketPtr>& packets) {
|
|
|
+ AVFramePtr processedFrame;
|
|
|
+
|
|
|
+ if (frame) {
|
|
|
+ // 格式转换
|
|
|
+ auto convertedFrame = convertFrame(frame);
|
|
|
+ if (!convertedFrame) {
|
|
|
+ AV_LOGGER_ERROR("帧格式转换失败");
|
|
|
+ return ErrorCode::CONVERSION_FAILED;
|
|
|
+ }
|
|
|
+ processedFrame = std::move(convertedFrame);
|
|
|
+
|
|
|
+ // 硬件传输
|
|
|
+ auto hwFrame = transferToHardware(processedFrame);
|
|
|
+ if (!hwFrame) {
|
|
|
+ AV_LOGGER_ERROR("硬件传输失败");
|
|
|
+ return ErrorCode::HARDWARE_ERROR;
|
|
|
+ }
|
|
|
+ processedFrame = std::move(hwFrame);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 发送帧到编码器
|
|
|
+ int ret = avcodec_send_frame(codecCtx_.get(), processedFrame ? processedFrame.get() : nullptr);
|
|
|
+ if (ret < 0) {
|
|
|
+ AV_LOGGER_ERRORF("发送帧到编码器失败: {}", ffmpeg_utils::errorToString(ret));
|
|
|
+ return static_cast<ErrorCode>(ret);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 接收编码后的包
|
|
|
+ return receivePackets(packets);
|
|
|
+}
|
|
|
+
|
|
|
+ErrorCode VideoEncoder::receivePackets(std::vector<AVPacketPtr>& packets) {
|
|
|
+ while (true) {
|
|
|
+ AVPacketPtr packet = makeAVPacket();
|
|
|
+ if (!packet) {
|
|
|
+ return ErrorCode::OUT_OF_MEMORY;
|
|
|
+ }
|
|
|
+
|
|
|
+ int ret = avcodec_receive_packet(codecCtx_.get(), packet.get());
|
|
|
+ if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
|
|
|
+ break; // 需要更多输入或已结束
|
|
|
+ }
|
|
|
+
|
|
|
+ if (ret < 0) {
|
|
|
+ AV_LOGGER_ERRORF("接收编码包失败: {}", ffmpeg_utils::errorToString(ret));
|
|
|
+ return static_cast<ErrorCode>(ret);
|
|
|
+ }
|
|
|
+
|
|
|
+ packets.push_back(std::move(packet));
|
|
|
+ }
|
|
|
+
|
|
|
+ return ErrorCode::OK;
|
|
|
+}
|
|
|
+
|
|
|
+AVHWDeviceType VideoEncoder::getHardwareDeviceType() const {
|
|
|
+ if (videoParams_.codecName == "h264_nvenc") {
|
|
|
+ return AV_HWDEVICE_TYPE_CUDA;
|
|
|
+ } else if (videoParams_.codecName == "h264_qsv") {
|
|
|
+ return AV_HWDEVICE_TYPE_QSV;
|
|
|
+ } else if (videoParams_.codecName == "h264_amf") {
|
|
|
+ return AV_HWDEVICE_TYPE_D3D11VA;
|
|
|
+ } else if (videoParams_.codecName == "h264_videotoolbox") {
|
|
|
+ return AV_HWDEVICE_TYPE_VIDEOTOOLBOX;
|
|
|
+ }
|
|
|
+
|
|
|
+ return AV_HWDEVICE_TYPE_NONE;
|
|
|
+}
|
|
|
+
|
|
|
+AVPixelFormat VideoEncoder::getHardwarePixelFormat() const {
|
|
|
+ if (videoParams_.codecName == "h264_nvenc") {
|
|
|
+ return AV_PIX_FMT_CUDA;
|
|
|
+ } else if (videoParams_.codecName == "h264_qsv") {
|
|
|
+ return AV_PIX_FMT_QSV;
|
|
|
+ } else if (videoParams_.codecName == "h264_amf") {
|
|
|
+ return AV_PIX_FMT_D3D11;
|
|
|
+ } else if (videoParams_.codecName == "h264_videotoolbox") {
|
|
|
+ return AV_PIX_FMT_VIDEOTOOLBOX;
|
|
|
+ }
|
|
|
+
|
|
|
+ AV_LOGGER_ERRORF("未知的硬件编码器: {}", videoParams_.codecName);
|
|
|
+ return AV_PIX_FMT_NONE;
|
|
|
+}
|
|
|
+
|
|
|
+std::vector<std::string> VideoEncoder::getSupportedEncoders() {
|
|
|
+ std::call_once(encodersInitFlag_, findUsableEncoders);
|
|
|
+ return supportedEncoders_;
|
|
|
+}
|
|
|
+
|
|
|
+bool VideoEncoder::isHardwareEncoder(const std::string& codecName) {
|
|
|
+ for (const char* hwEncoder : HARDWARE_ENCODERS) {
|
|
|
+ if (codecName == hwEncoder) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+}
|
|
|
+
|
|
|
+std::string VideoEncoder::getRecommendedEncoder() {
|
|
|
+ auto encoders = getSupportedEncoders();
|
|
|
+
|
|
|
+ // 优先选择硬件编码器
|
|
|
+ for (const char* hwEncoder : HARDWARE_ENCODERS) {
|
|
|
+ if (std::find(encoders.begin(), encoders.end(), hwEncoder) != encoders.end()) {
|
|
|
+ return hwEncoder;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 回退到软件编码器
|
|
|
+ for (const char* swEncoder : SOFTWARE_ENCODERS) {
|
|
|
+ if (std::find(encoders.begin(), encoders.end(), swEncoder) != encoders.end()) {
|
|
|
+ return swEncoder;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return encoders.empty() ? "" : encoders[0];
|
|
|
+}
|
|
|
+
|
|
|
+void VideoEncoder::findUsableEncoders() {
|
|
|
+ AV_LOGGER_INFO("查找可用的视频编码器...");
|
|
|
+
|
|
|
+ // 测试硬件编码器
|
|
|
+ for (const char* encoder : HARDWARE_ENCODERS) {
|
|
|
+ if (CodecFactory::isCodecSupported(encoder, CodecType::ENCODER, MediaType::VIDEO)) {
|
|
|
+ supportedEncoders_.emplace_back(encoder);
|
|
|
+ AV_LOGGER_INFOF("找到硬件编码器: {}", encoder);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 测试软件编码器
|
|
|
+ for (const char* encoder : SOFTWARE_ENCODERS) {
|
|
|
+ if (CodecFactory::isCodecSupported(encoder, CodecType::ENCODER, MediaType::VIDEO)) {
|
|
|
+ supportedEncoders_.emplace_back(encoder);
|
|
|
+ AV_LOGGER_INFOF("找到软件编码器: {}", encoder);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ AV_LOGGER_INFOF("总共找到 {} 个可用的视频编码器", supportedEncoders_.size());
|
|
|
+}
|
|
|
+
|
|
|
+// VideoEncoderFactory 实现
|
|
|
+std::unique_ptr<VideoEncoder> VideoEncoderFactory::create(const std::string& codecName) {
|
|
|
+ auto encoder = std::make_unique<VideoEncoder>();
|
|
|
+
|
|
|
+ if (!codecName.empty()) {
|
|
|
+ if (!CodecFactory::isCodecSupported(codecName, CodecType::ENCODER, MediaType::VIDEO)) {
|
|
|
+ AV_LOGGER_ERRORF("不支持的编码器: {}", codecName);
|
|
|
+ return nullptr;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return encoder;
|
|
|
+}
|
|
|
+
|
|
|
+std::unique_ptr<VideoEncoder> VideoEncoderFactory::createBest(bool preferHardware) {
|
|
|
+ std::string codecName;
|
|
|
+
|
|
|
+ if (preferHardware) {
|
|
|
+ codecName = VideoEncoder::getRecommendedEncoder();
|
|
|
+ } else {
|
|
|
+ // 优先选择软件编码器
|
|
|
+ auto encoders = VideoEncoder::getSupportedEncoders();
|
|
|
+ for (const char* swEncoder : VideoEncoder::SOFTWARE_ENCODERS) {
|
|
|
+ if (std::find(encoders.begin(), encoders.end(), swEncoder) != encoders.end()) {
|
|
|
+ codecName = swEncoder;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (codecName.empty()) {
|
|
|
+ AV_LOGGER_ERROR("未找到可用的视频编码器");
|
|
|
+ return nullptr;
|
|
|
+ }
|
|
|
+
|
|
|
+ return create(codecName);
|
|
|
+}
|
|
|
+
|
|
|
+} // namespace codec
|
|
|
+} // namespace av
|