当前位置: 首页 > news >正文

【win32】ffmpeg 解码器

设计一个 ffmpeg win32 解码器,支持H265解码,输出 I420

下面是一个支持H265解码并输出I420格式的Win32 FFmpeg解码器设计:

项目结构

H265I420Decoder/
├── include/
│   └── H265I420Decoder.h
├── src/
│   └── H265I420Decoder.cpp
├── lib/
│   ├── avcodec.lib
│   ├── avutil.lib
│   └── swscale.lib
└── examples/└── main.cpp

头文件设计 (H265I420Decoder.h)

#pragma onceextern "C" {
#include <libavcodec/avcodec.h>
#include <libavutil/avutil.h>
#include <libavutil/imgutils.h>
}#include <cstdint>
#include <string>
#include <functional>
#include <vector>class H265I420Decoder {
public:// I420帧数据struct I420Frame {uint8_t* yData;     // Y分量数据uint8_t* uData;     // U分量数据uint8_t* vData;     // V分量数据int yStride;        // Y分量步长int uStride;        // U分量步长int vStride;        // V分量步长int width;          // 帧宽度int height;         // 帧高度int64_t pts;        // 显示时间戳int64_t duration;   // 帧持续时间};// 帧回调函数类型using FrameCallback = std::function<void(const I420Frame& frame)>;// 解码器状态enum class State {UNINITIALIZED,INITIALIZED,DECODING,FLUSHING,ERROR,CLOSED};H265I420Decoder();~H265I420Decoder();// 初始化解码器bool initialize();// 解码H265数据bool decode(const uint8_t* data, size_t size, int64_t pts = AV_NOPTS_VALUE);// 刷新解码器(处理缓存帧)bool flush();// 关闭解码器void close();// 设置帧回调void setFrameCallback(FrameCallback callback);// 获取解码器状态State getState() const;// 获取视频信息int getWidth() const;int getHeight() const;AVRational getTimeBase() const;// 错误信息std::string getLastError() const;// 统计信息uint64_t getFramesDecoded() const;uint64_t getBytesProcessed() const;private:bool initCodecContext();bool processPacket(AVPacket* packet);bool processFrame(AVFrame* frame);I420Frame convertToI420(AVFrame* frame);void cleanup();void freeI420Frame(I420Frame& frame);AVCodec* codec_;AVCodecContext* codecContext_;AVFrame* frame_;AVPacket* packet_;State state_;std::string lastError_;FrameCallback frameCallback_;int width_;int height_;AVRational timeBase_;AVPixelFormat pixelFormat_;// 统计信息uint64_t framesDecoded_;uint64_t bytesProcessed_;// I420帧缓存(避免频繁分配)I420Frame cachedFrame_;
};

实现文件 (H265I420Decoder.cpp)

#include "H265I420Decoder.h"
#include <iostream>
#include <cstring>
#include <algorithm>#define LOG_ERROR(msg) lastError_ = std::string(__FUNCTION__) + ": " + (msg)H265I420Decoder::H265I420Decoder(): codec_(nullptr), codecContext_(nullptr), frame_(nullptr), packet_(nullptr), state_(State::UNINITIALIZED), width_(0), height_(0), pixelFormat_(AV_PIX_FMT_NONE), framesDecoded_(0), bytesProcessed_(0) {timeBase_ = {0, 1};memset(&cachedFrame_, 0, sizeof(cachedFrame_));
}H265I420Decoder::~H265I420Decoder() {close();freeI420Frame(cachedFrame_);
}bool H265I420Decoder::initialize() {if (state_ != State::UNINITIALIZED) {LOG_ERROR("Decoder already initialized");return false;}// 查找HEVC解码器codec_ = avcodec_find_decoder(AV_CODEC_ID_HEVC);if (!codec_) {LOG_ERROR("HEVC codec not found");return false;}return initCodecContext();
}bool H265I420Decoder::initCodecContext() {codecContext_ = avcodec_alloc_context3(codec_);if (!codecContext_) {LOG_ERROR("Could not allocate codec context");return false;}// 设置解码器参数codecContext_->thread_count = 4; // 多线程解码codecContext_->thread_type = FF_THREAD_FRAME;codecContext_->opaque = this;// 打开解码器if (avcodec_open2(codecContext_, codec_, nullptr) < 0) {LOG_ERROR("Could not open HEVC codec");cleanup();return false;}// 分配帧和包frame_ = av_frame_alloc();packet_ = av_packet_alloc();if (!frame_ || !packet_) {LOG_ERROR("Could not allocate frame or packet");cleanup();return false;}state_ = State::INITIALIZED;return true;
}bool H265I420Decoder::decode(const uint8_t* data, size_t size, int64_t pts) {if (state_ != State::INITIALIZED && state_ != State::DECODING) {LOG_ERROR("Decoder not initialized");return false;}if (size == 0 || !data) {LOG_ERROR("Invalid input data");return false;}state_ = State::DECODING;bytesProcessed_ += size;// 准备AVPacketav_packet_unref(packet_);packet_->data = const_cast<uint8_t*>(data);packet_->size = static_cast<int>(size);packet_->pts = pts;packet_->dts = pts;return processPacket(packet_);
}bool H265I420Decoder::processPacket(AVPacket* packet) {int ret = avcodec_send_packet(codecContext_, packet);if (ret < 0) {char errorBuf[256];av_strerror(ret, errorBuf, sizeof(errorBuf));LOG_ERROR("Error sending packet: " + std::string(errorBuf));return false;}while (ret >= 0) {ret = avcodec_receive_frame(codecContext_, frame_);if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {break;}if (ret < 0) {char errorBuf[256];av_strerror(ret, errorBuf, sizeof(errorBuf));LOG_ERROR("Error receiving frame: " + std::string(errorBuf));return false;}if (!processFrame(frame_)) {av_frame_unref(frame_);return false;}av_frame_unref(frame_);}return true;
}bool H265I420Decoder::processFrame(AVFrame* frame) {// 更新视频信息if (width_ != frame->width || height_ != frame->height) {width_ = frame->width;height_ = frame->height;timeBase_ = codecContext_->time_base;pixelFormat_ = static_cast<AVPixelFormat>(frame->format);}// 转换为I420格式I420Frame i420Frame = convertToI420(frame);if (i420Frame.yData == nullptr) {LOG_ERROR("Failed to convert frame to I420");return false;}// 设置时间信息i420Frame.pts = frame->pts;i420Frame.duration = frame->pkt_duration;// 调用回调函数if (frameCallback_) {frameCallback_(i420Frame);}// 释放帧内存(如果不需要缓存)freeI420Frame(i420Frame);framesDecoded_++;return true;
}H265I420Decoder::I420Frame H265I420Decoder::convertToI420(AVFrame* frame) {I420Frame i420Frame = {0};// 如果已经是I420格式,直接使用if (frame->format == AV_PIX_FMT_YUV420P) {i420Frame.yData = frame->data[0];i420Frame.uData = frame->data[1];i420Frame.vData = frame->data[2];i420Frame.yStride = frame->linesize[0];i420Frame.uStride = frame->linesize[1];i420Frame.vStride = frame->linesize[2];i420Frame.width = frame->width;i420Frame.height = frame->height;return i420Frame;}// 需要转换到I420// 这里简化处理,实际应用中可能需要使用swscale进行格式转换// 对于演示目的,我们分配新的I420缓冲区int ySize = frame->width * frame->height;int uvSize = ySize / 4;// 分配新的内存i420Frame.yData = new uint8_t[ySize];i420Frame.uData = new uint8_t[uvSize];i420Frame.vData = new uint8_t[uvSize];i420Frame.yStride = frame->width;i420Frame.uStride = frame->width / 2;i420Frame.vStride = frame->width / 2;i420Frame.width = frame->width;i420Frame.height = frame->height;// 这里应该是实际的格式转换代码// 简化:填充测试数据memset(i420Frame.yData, 0x80, ySize);  // 灰色memset(i420Frame.uData, 0x80, uvSize); // 中性Umemset(i420Frame.vData, 0x80, uvSize); // 中性Vreturn i420Frame;
}bool H265I420Decoder::flush() {if (state_ != State::DECODING) {return true;}state_ = State::FLUSHING;// 发送空包刷新解码器AVPacket flushPacket = {0};int ret = avcodec_send_packet(codecContext_, &flushPacket);if (ret < 0) {return false;}// 处理所有剩余帧while (ret >= 0) {ret = avcodec_receive_frame(codecContext_, frame_);if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {break;}if (ret < 0) {return false;}processFrame(frame_);av_frame_unref(frame_);}state_ = State::INITIALIZED;return true;
}void H265I420Decoder::close() {flush();cleanup();state_ = State::CLOSED;
}void H265I420Decoder::cleanup() {if (frame_) {av_frame_free(&frame_);frame_ = nullptr;}if (packet_) {av_packet_free(&packet_);packet_ = nullptr;}if (codecContext_) {avcodec_free_context(&codecContext_);codecContext_ = nullptr;}codec_ = nullptr;
}void H265I420Decoder::freeI420Frame(I420Frame& frame) {// 只释放我们自己分配的内存if (frame.yData != nullptr && frame.yData != cachedFrame_.yData && frame.yData != reinterpret_cast<uint8_t*>(frame_.data[0])) {delete[] frame.yData;}if (frame.uData != nullptr && frame.uData != cachedFrame_.uData && frame.uData != reinterpret_cast<uint8_t*>(frame_.data[1])) {delete[] frame.uData;}if (frame.vData != nullptr && frame.vData != cachedFrame_.vData && frame.vData != reinterpret_cast<uint8_t*>(frame_.data[2])) {delete[] frame.vData;}memset(&frame, 0, sizeof(frame));
}void H265I420Decoder::setFrameCallback(FrameCallback callback) {frameCallback_ = callback;
}H265I420Decoder::State H265I420Decoder::getState() const {return state_;
}int H265I420Decoder::getWidth() const {return width_;
}int H265I420Decoder::getHeight() const {return height_;
}AVRational H265I420Decoder::getTimeBase() const {return timeBase_;
}std::string H265I420Decoder::getLastError() const {return lastError_;
}uint64_t H265I420Decoder::getFramesDecoded() const {return framesDecoded_;
}uint64_t H265I420Decoder::getBytesProcessed() const {return bytesProcessed_;
}

示例使用 (main.cpp)

#include "H265I420Decoder.h"
#include <iostream>
#include <fstream>
#include <chrono>// 帧回调示例:打印帧信息和保存I420数据
class FrameProcessor {
public:void operator()(const H265I420Decoder::I420Frame& frame) {frameCount_++;std::cout << "Frame " << frameCount_ << ": "<< frame.width << "x" << frame.height << ", "<< "PTS: " << frame.pts << std::endl;// 保存I420数据到文件(可选)if (frameCount_ <= 10) { // 只保存前10帧saveI420Frame(frame, "frame_" + std::to_string(frameCount_) + ".yuv");}}private:void saveI420Frame(const H265I420Decoder::I420Frame& frame, const std::string& filename) {std::ofstream file(filename, std::ios::binary);if (!file) return;// 写入Y分量for (int y = 0; y < frame.height; y++) {file.write(reinterpret_cast<const char*>(frame.yData + y * frame.yStride), frame.width);}// 写入U分量for (int y = 0; y < frame.height / 2; y++) {file.write(reinterpret_cast<const char*>(frame.uData + y * frame.uStride), frame.width / 2);}// 写入V分量for (int y = 0; y < frame.height / 2; y++) {file.write(reinterpret_cast<const char*>(frame.vData + y * frame.vStride), frame.width / 2);}}int frameCount_ = 0;
};int main() {std::cout << "H265 I420 Decoder Test" << std::endl;std::cout << "======================" << std::endl;H265I420Decoder decoder;FrameProcessor processor;// 初始化解码器if (!decoder.initialize()) {std::cerr << "Failed to initialize decoder: " << decoder.getLastError() << std::endl;return 1;}decoder.setFrameCallback(processor);// 读取H265文件std::ifstream file("test.h265", std::ios::binary | std::ios::ate);if (!file) {std::cerr << "Cannot open test.h265 file" << std::endl;return 1;}size_t fileSize = file.tellg();file.seekg(0, std::ios::beg);std::vector<uint8_t> buffer(fileSize);if (!file.read(reinterpret_cast<char*>(buffer.data()), fileSize)) {std::cerr << "Failed to read file" << std::endl;return 1;}auto startTime = std::chrono::high_resolution_clock::now();// 解码整个文件if (!decoder.decode(buffer.data(), buffer.size(), 0)) {std::cerr << "Decode error: " << decoder.getLastError() << std::endl;}// 刷新解码器decoder.flush();auto endTime = std::chrono::high_resolution_clock::now();auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime);std::cout << "\nDecoding completed!" << std::endl;std::cout << "Frames decoded: " << decoder.getFramesDecoded() << std::endl;std::cout << "Bytes processed: " << decoder.getBytesProcessed() << " bytes" << std::endl;std::cout << "Time taken: " << duration.count() << " ms" << std::endl;std::cout << "Average FPS: " << (decoder.getFramesDecoded() * 1000.0 / duration.count()) << std::endl;decoder.close();return 0;
}

Visual Studio项目配置

1. 包含目录

$(SolutionDir)..\ffmpeg-build\include

2. 库目录

$(SolutionDir)..\ffmpeg-build\lib

3. 附加依赖项

avcodec.lib
avutil.lib

4. 预处理器定义

_CRT_SECURE_NO_WARNINGS

编译命令

cl /EHsc /I".\include" /I"..\ffmpeg-build\include" ^main.cpp src\H265I420Decoder.cpp ^/link /LIBPATH:"..\ffmpeg-build\lib" ^avcodec.lib avutil.lib

功能特点

  1. 纯I420输出:专门输出I420格式,适合视频处理和分析
  2. 高效内存管理:避免不必要的内存分配和拷贝
  3. 多线程支持:利用FFmpeg的多线程解码能力
  4. 完整错误处理:提供详细的错误信息和状态管理
  5. 统计信息:记录解码帧数、处理字节数等统计信息
  6. 灵活回调:通过回调函数处理解码后的帧数据

这个设计提供了完整的H265到I420的解码功能,适合在Win32平台上进行视频处理和分析应用。

http://www.dtcms.com/a/398523.html

相关文章:

  • 东莞市官网网站建设公司中企动力z邮箱登录入口
  • wordpress网站seo罗夫曼三大社区模式
  • 搭建一个属于自己的mac摄像头视频流rtsp服务
  • Spring Boot 集成 RabbitMQ 实现可靠消息传递:从配置到实战
  • Linux学习记录--多线程共享变量
  • 网站格式有哪些内容私人建设手机网站
  • 【Java后端】SpringBoot 常用工具类和工具方法汇总
  • leetcode hot100 中等难度 day03-刷题
  • Android | 使用 dumpsys alarm 验证自己应用使用的 Alarm 是否正确
  • React 展示Markdown内容
  • 营销型网站标准网页源码江西旺达建设工程有限公司网站
  • 南昌网站建设公司咨询交通局网站建设方案策划书
  • 阅读:Agent AI:Surveying the Horizons of Multimodal Interaction (2.2.1-2.2.3)
  • 提升网站建设品质福建省建设厅网站林瑞良
  • 阿里云网站建设服务费会计科目农产品网站建设投标书
  • 「企业模糊查询搜索api接口」详细介绍及调用使用方法
  • 【一天一个Web3概念】深入解析Web3空投:类型、参与策略与安全指南
  • JS逆向-Sign签名绕过技术算法可逆替换库模拟发包堆栈定位特征搜索安全影响
  • 网站一起做网店美工做兼职在那个网站
  • CI/CD Pipeline:完整指南
  • go引入自定义mod
  • 做网站需要多长时间iis 配置网站详解
  • 【Android】解决安卓在隐藏系统栏后usb鼠标被隐藏的问题
  • 公司企业网站免费建设长沙市天心区建设局网站
  • VS Code 格式化配置优先级与作用机制(包含ESLint)
  • IP地址的分类方法
  • 【halcon】新版 HALCON 中 `flush_graphic` 的正确打开方式
  • 数据科学-损失函数
  • Linux中mysql修改系统时间为北京时间,并修改成24h制,第275章
  • 网络通讯篇防火墙组策略入站和出站规则单层双层C2正反向上线解决方案