【win32】ffmpeg 解码器2

设计使用 ffmpeg 头文件和库 实现 win32 上 H265 解码器

下面是一个使用FFmpeg在Win32上实现H265解码器的完整设计:

项目结构

复制代码
H265Decoder/
├── include/
│   └── H265Decoder.h
├── src/
│   └── H265Decoder.cpp
├── lib/
│   ├── avcodec.lib
│   ├── avformat.lib
│   ├── avutil.lib
│   └── swscale.lib
├── dll/
│   ├── avcodec-58.dll
│   ├── avformat-58.dll
│   ├── avutil-56.dll
│   └── swscale-5.dll
└── examples/
    └── main.cpp

头文件设计 (H265Decoder.h)

cpp 复制代码
#pragma once

extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/avutil.h>
#include <libswscale/swscale.h>
}

#include <string>
#include <vector>
#include <functional>

class H265Decoder {
public:
    // 解码器状态
    enum class DecoderState {
        UNINITIALIZED,
        INITIALIZED,
        DECODING,
        ERROR,
        CLOSED
    };

    // 帧数据回调函数
    using FrameCallback = std::function<void(const uint8_t* data, int width, int height, int stride)>;

    H265Decoder();
    ~H265Decoder();

    // 初始化解码器
    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);
    
    // 获取解码器状态
    DecoderState getState() const;
    
    // 获取视频信息
    int getWidth() const;
    int getHeight() const;
    AVPixelFormat getPixelFormat() const;
    
    // 错误信息
    std::string getLastError() const;

private:
    bool initCodec();
    bool processPacket(AVPacket* packet);
    bool processFrame(AVFrame* frame);
    void cleanup();

    AVCodec* codec_;
    AVCodecContext* codecContext_;
    AVFrame* frame_;
    AVPacket* packet_;
    SwsContext* swsContext_;
    
    DecoderState state_;
    std::string lastError_;
    FrameCallback frameCallback_;
    
    int width_;
    int height_;
    AVPixelFormat pixelFormat_;
    
    // 用于颜色空间转换
    uint8_t* swsBuffer_;
    int swsBufferSize_;
};

实现文件 (H265Decoder.cpp)

cpp 复制代码
#include "H265Decoder.h"
#include <iostream>
#include <cstring>

#define LOG_ERROR(msg) lastError_ = std::string(__FUNCTION__) + ": " + (msg)

H265Decoder::H265Decoder()
    : codec_(nullptr)
    , codecContext_(nullptr)
    , frame_(nullptr)
    , packet_(nullptr)
    , swsContext_(nullptr)
    , state_(DecoderState::UNINITIALIZED)
    , width_(0)
    , height_(0)
    , pixelFormat_(AV_PIX_FMT_NONE)
    , swsBuffer_(nullptr)
    , swsBufferSize_(0) {
}

H265Decoder::~H265Decoder() {
    close();
}

bool H265Decoder::initialize() {
    if (state_ != DecoderState::UNINITIALIZED) {
        LOG_ERROR("Decoder already initialized");
        return false;
    }

    // 注册所有编解码器
    avcodec_register_all();

    // 查找HEVC解码器
    codec_ = avcodec_find_decoder(AV_CODEC_ID_HEVC);
    if (!codec_) {
        LOG_ERROR("HEVC codec not found");
        return false;
    }

    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;

    // 打开解码器
    if (avcodec_open2(codecContext_, codec_, nullptr) < 0) {
        LOG_ERROR("Could not open 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_ = DecoderState::INITIALIZED;
    return true;
}

bool H265Decoder::decode(const uint8_t* data, size_t size, int64_t pts) {
    if (state_ != DecoderState::INITIALIZED && state_ != DecoderState::DECODING) {
        LOG_ERROR("Decoder not initialized");
        return false;
    }

    if (size == 0 || !data) {
        LOG_ERROR("Invalid input data");
        return false;
    }

    state_ = DecoderState::DECODING;

    // 准备AVPacket
    av_packet_unref(packet_);
    packet_->data = const_cast<uint8_t*>(data);
    packet_->size = static_cast<int>(size);
    packet_->pts = pts;

    return processPacket(packet_);
}

bool H265Decoder::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 H265Decoder::processFrame(AVFrame* frame) {
    // 更新视频信息
    if (width_ != frame->width || height_ != frame->height || 
        pixelFormat_ != static_cast<AVPixelFormat>(frame->format)) {
        width_ = frame->width;
        height_ = frame->height;
        pixelFormat_ = static_cast<AVPixelFormat>(frame->format);

        // 释放旧的转换上下文
        if (swsContext_) {
            sws_freeContext(swsContext_);
            swsContext_ = nullptr;
        }

        // 创建新的转换上下文(转换为RGB24)
        swsContext_ = sws_getContext(
            width_, height_, pixelFormat_,
            width_, height_, AV_PIX_FMT_RGB24,
            SWS_BILINEAR, nullptr, nullptr, nullptr);

        if (!swsContext_) {
            LOG_ERROR("Could not create scale context");
            return false;
        }

        // 重新分配缓冲区
        int requiredSize = av_image_get_buffer_size(AV_PIX_FMT_RGB24, width_, height_, 1);
        if (requiredSize > swsBufferSize_) {
            delete[] swsBuffer_;
            swsBuffer_ = new uint8_t[requiredSize];
            swsBufferSize_ = requiredSize;
        }
    }

    if (!frameCallback_) {
        return true; // 没有回调,直接返回成功
    }

    // 转换颜色空间到RGB24
    uint8_t* dstData[1] = { swsBuffer_ };
    int dstLinesize[1] = { width_ * 3 }; // RGB24 stride

    sws_scale(swsContext_, frame->data, frame->linesize, 0,
              height_, dstData, dstLinesize);

    // 调用回调函数
    frameCallback_(swsBuffer_, width_, height_, dstLinesize[0]);

    return true;
}

bool H265Decoder::flush() {
    if (state_ != DecoderState::DECODING) {
        return true;
    }

    // 发送空包刷新解码器
    AVPacket flushPacket = {0};
    flushPacket.data = nullptr;
    flushPacket.size = 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_);
    }

    return true;
}

void H265Decoder::close() {
    flush();
    cleanup();
    state_ = DecoderState::CLOSED;
}

void H265Decoder::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;
    }

    if (swsContext_) {
        sws_freeContext(swsContext_);
        swsContext_ = nullptr;
    }

    delete[] swsBuffer_;
    swsBuffer_ = nullptr;
    swsBufferSize_ = 0;

    codec_ = nullptr;
}

void H265Decoder::setFrameCallback(FrameCallback callback) {
    frameCallback_ = callback;
}

H265Decoder::DecoderState H265Decoder::getState() const {
    return state_;
}

int H265Decoder::getWidth() const {
    return width_;
}

int H265Decoder::getHeight() const {
    return height_;
}

AVPixelFormat H265Decoder::getPixelFormat() const {
    return pixelFormat_;
}

std::string H265Decoder::getLastError() const {
    return lastError_;
}

示例使用 (main.cpp)

cpp 复制代码
#include "H265Decoder.h"
#include <fstream>
#include <iostream>
#include <Windows.h>

// 简单的帧回调,保存为BMP文件
void saveFrameAsBMP(const uint8_t* rgbData, int width, int height, int stride, 
                   const std::string& filename) {
    std::ofstream file(filename, std::ios::binary);
    if (!file) return;

    // BMP文件头
    BITMAPFILEHEADER fileHeader = {0};
    BITMAPINFOHEADER infoHeader = {0};

    int imageSize = stride * height;
    
    fileHeader.bfType = 0x4D42; // "BM"
    fileHeader.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + imageSize;
    fileHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);

    infoHeader.biSize = sizeof(BITMAPINFOHEADER);
    infoHeader.biWidth = width;
    infoHeader.biHeight = -height; // 负值表示从上到下的位图
    infoHeader.biPlanes = 1;
    infoHeader.biBitCount = 24;
    infoHeader.biCompression = BI_RGB;
    infoHeader.biSizeImage = imageSize;

    file.write(reinterpret_cast<char*>(&fileHeader), sizeof(fileHeader));
    file.write(reinterpret_cast<char*>(&infoHeader), sizeof(infoHeader));
    file.write(reinterpret_cast<const char*>(rgbData), imageSize);
}

class FrameSaver {
public:
    FrameSaver() : frameCount_(0) {}
    
    void operator()(const uint8_t* data, int width, int height, int stride) {
        std::string filename = "frame_" + std::to_string(frameCount_++) + ".bmp";
        saveFrameAsBMP(data, width, height, stride, filename);
        std::cout << "Saved frame " << frameCount_ << " as " << filename << std::endl;
    }

private:
    int frameCount_;
};

int main() {
    // 初始化FFmpeg
    av_register_all();
    avcodec_register_all();

    H265Decoder decoder;
    FrameSaver frameSaver;

    if (!decoder.initialize()) {
        std::cerr << "Failed to initialize decoder: " << decoder.getLastError() << std::endl;
        return 1;
    }

    decoder.setFrameCallback(frameSaver);

    // 读取H265文件并解码
    std::ifstream file("test.h265", std::ios::binary);
    if (!file) {
        std::cerr << "Cannot open test.h265 file" << std::endl;
        return 1;
    }

    const size_t bufferSize = 1024 * 1024; // 1MB缓冲区
    std::vector<uint8_t> buffer(bufferSize);
    int64_t pts = 0;

    while (file.read(reinterpret_cast<char*>(buffer.data()), bufferSize)) {
        size_t bytesRead = file.gcount();
        
        if (!decoder.decode(buffer.data(), bytesRead, pts)) {
            std::cerr << "Decode error: " << decoder.getLastError() << std::endl;
            break;
        }

        pts += 1000; // 假设每帧33ms
    }

    // 刷新解码器
    decoder.flush();
    decoder.close();

    std::cout << "Decoding completed successfully!" << std::endl;
    return 0;
}

Visual Studio项目配置

1. 包含目录设置

复制代码
$(SolutionDir)..\ffmpeg-build\include

2. 库目录设置

复制代码
$(SolutionDir)..\ffmpeg-build\lib

3. 附加依赖项

复制代码
avcodec.lib
avformat.lib
avutil.lib
swscale.lib

4. 预处理器定义

复制代码
_CRT_SECURE_NO_WARNINGS

编译命令

batch 复制代码
cl /EHsc /I".\include" /I"..\ffmpeg-build\include" ^
  main.cpp src\H265Decoder.cpp ^
  /link /LIBPATH:"..\ffmpeg-build\lib" ^
  avcodec.lib avformat.lib avutil.lib swscale.lib

这个设计提供了完整的H265解码功能,包括错误处理、内存管理和帧回调机制,适合在Win32平台上使用。

相关推荐
喝呜昂_黄7 小时前
【 嵌入式Linux应用开发项目 | Rockit + FFmpeg+ Nginx】基于泰山派的IPC网络摄像头
linux·c语言·nginx·ffmpeg
陈旭金-小金子7 小时前
FFmpeg 5.x 编译 so 文件的记录
ffmpeg
huluang19 小时前
ppt视频极致压缩参数
ffmpeg·powerpoint·音视频
在狂风暴雨中奔跑5 天前
厌倦了复杂的编译?一键集成 AeroFFmpeg,让Android音视频开发更简单!
ffmpeg·开源
Java陈序员6 天前
直播录制神器!一款多平台直播流自动录制客户端!
python·docker·ffmpeg
简鹿办公10 天前
FFmpeg vs 去水印软件:哪种方式更适合你?
ffmpeg·怎样去除视频水印·如何去视频logo视频水印
小狮子安度因11 天前
ffplay数据结构分析
数据结构·ffmpeg
小狮子安度因11 天前
ffplay音频重采样
ffmpeg·音视频
小狮子安度因11 天前
AAC ADTS格式分析
网络·ffmpeg·aac