You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

AVIFileOpenW与AVIStreamSetFormat参数误用及屏幕录制AVI写入问题求助

问题分析与修正方案

你的代码存在几个关键问题,直接导致AVI文件写入失败,以下是问题点和修复后的实现:

核心错误点

  • AVIFileOpenW参数类型不匹配:该函数需要传入PAVIFILE*类型变量,你错误传入了PAVISTREAM*,这是完全不同的对象类型。
  • 未正确创建视频流:AVI文件需要先打开文件对象,再创建对应的视频流,不能直接操作流对象。
  • 帧存储逻辑错误:你把同一个hBitmap指针重复存入buffer,所有帧最终都是最后一帧的内容,每个帧需要创建独立的HBITMAP来存储。
  • AVI格式与写入参数错误:AVIStreamSetFormat需要传入完整的BITMAPINFO结构,而非HBITMAP;AVIStreamWrite的帧数据大小、参数含义都不符合要求。
  • 无帧率控制:当前循环会尽可能快地截图,无法保证60fps的录制帧率。

修复后的完整代码

#include <Windows.h>
#include <Vfw.h>
#include <iostream>
#include <vector>
#include <chrono>
#include <thread>

#pragma comment(lib, "vfw32.lib")

const int BUFFER_SIZE = 30 * 60; // 30秒@60fps
const int FPS = 60;
const int FRAME_DELAY = 1000 / FPS; // 每帧间隔毫秒数

// 释放HBITMAP并清空指针
void SafeDeleteBitmap(HBITMAP& hBmp) {
    if (hBmp) {
        DeleteObject(hBmp);
        hBmp = nullptr;
    }
}

int main() {
    // 获取主显示器尺寸
    HMONITOR hMonitor = MonitorFromWindow(NULL, MONITOR_DEFAULTTOPRIMARY);
    MONITORINFO info{};
    info.cbSize = sizeof(MONITORINFO);
    GetMonitorInfo(hMonitor, &info);
    int width = info.rcMonitor.right - info.rcMonitor.left;
    int height = info.rcMonitor.bottom - info.rcMonitor.top;

    HDC hScreenDC = GetDC(NULL);
    HDC hMemDC = CreateCompatibleDC(hScreenDC);

    std::vector<HBITMAP> buffer(BUFFER_SIZE, nullptr);
    int index = 0;

    // 录制循环:控制60fps,存储每帧独立的HBITMAP
    while (true) {
        auto start = std::chrono::steady_clock::now();

        // 为当前帧创建独立的位图
        HBITMAP hFrameBmp = CreateCompatibleBitmap(hScreenDC, width, height);
        HBITMAP hOldBmp = (HBITMAP)SelectObject(hMemDC, hFrameBmp);

        // 截取屏幕到当前帧位图
        BitBlt(hMemDC, 0, 0, width, height, hScreenDC, 0, 0, SRCCOPY);

        // 恢复MemDC的原有位图,保存当前帧到buffer
        SelectObject(hMemDC, hOldBmp);
        SafeDeleteBitmap(buffer[index]);
        buffer[index] = hFrameBmp;

        index = (index + 1) % BUFFER_SIZE;

        // 检查退出按键
        if (GetAsyncKeyState(VK_ESCAPE) & 0x8000) {
            break;
        }

        // 控制帧率
        auto end = std::chrono::steady_clock::now();
        auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
        if (elapsed < FRAME_DELAY) {
            std::this_thread::sleep_for(std::chrono::milliseconds(FRAME_DELAY - elapsed));
        }
    }

    // AVI文件写入流程
    PAVIFILE pFile = nullptr;
    PAVISTREAM pStream = nullptr;
    AVISTREAMINFO streamInfo{};

    AVIFileInit();

    // 1. 打开AVI文件
    if (AVIFileOpenW(&pFile, L"screen_recording.avi", OF_WRITE | OF_CREATE, NULL) != AVIERR_OK) {
        std::cerr << "Failed to open AVI file" << std::endl;
        goto Cleanup;
    }

    // 2. 设置视频流信息
    streamInfo.fccType = streamtypeVIDEO;
    streamInfo.fccHandler = mmioFOURCC('D', 'I', 'B', ' '); // 未压缩DIB格式
    streamInfo.dwScale = 1;
    streamInfo.dwRate = FPS;
    streamInfo.dwSuggestedBufferSize = width * height * 4; // 32位色深的帧大小
    SetRect(&streamInfo.rcFrame, 0, 0, width, height);

    // 3. 创建视频流
    if (AVIFileCreateStream(pFile, &pStream, &streamInfo) != AVIERR_OK) {
        std::cerr << "Failed to create AVI stream" << std::endl;
        goto Cleanup;
    }

    // 4. 设置视频流格式:构造BITMAPINFO
    BITMAPINFO bmpInfo{};
    bmpInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    bmpInfo.bmiHeader.biWidth = width;
    bmpInfo.bmiHeader.biHeight = -height; // 负数值表示顶行在前(符合AVI规范)
    bmpInfo.bmiHeader.biPlanes = 1;
    bmpInfo.bmiHeader.biBitCount = 32;
    bmpInfo.bmiHeader.biCompression = BI_RGB;
    bmpInfo.bmiHeader.biSizeImage = width * height * 4;

    if (AVIStreamSetFormat(pStream, 0, &bmpInfo, sizeof(BITMAPINFO)) != AVIERR_OK) {
        std::cerr << "Failed to set stream format" << std::endl;
        goto Cleanup;
    }

    // 5. 写入所有有效帧
    int startIndex = index; // 找到buffer中最早的帧
    for (int i = 0; i < BUFFER_SIZE; i++) {
        int frameIdx = (startIndex + i) % BUFFER_SIZE;
        HBITMAP hFrame = buffer[frameIdx];
        if (!hFrame) continue;

        // 获取帧像素数据
        BITMAP bmp{};
        GetObject(hFrame, sizeof(BITMAP), &bmp);
        char* pFrameData = new char[bmp.bmWidthBytes * bmp.bmHeight];
        
        BITMAPINFOHEADER bi{};
        bi.biSize = sizeof(BITMAPINFOHEADER);
        bi.biWidth = bmp.bmWidth;
        bi.biHeight = -bmp.bmHeight;
        bi.biPlanes = 1;
        bi.biBitCount = 32;
        bi.biCompression = BI_RGB;

        GetDIBits(hMemDC, hFrame, 0, bmp.bmHeight, pFrameData, (BITMAPINFO*)&bi, DIB_RGB_COLORS);

        // 写入帧数据
        if (AVIStreamWrite(pStream, i, 1, pFrameData, bmp.bmWidthBytes * bmp.bmHeight, AVIIF_KEYFRAME, NULL, NULL) != AVIERR_OK) {
            std::cerr << "Failed to write frame " << i << std::endl;
        }

        delete[] pFrameData;
    }

Cleanup:
    // 资源释放
    if (pStream) AVIStreamClose(pStream);
    if (pFile) AVIFileClose(pFile);
    AVIFileExit();

    // 释放所有存储的帧
    for (auto& hBmp : buffer) {
        SafeDeleteBitmap(hBmp);
    }

    DeleteDC(hMemDC);
    ReleaseDC(NULL, hScreenDC);

    return 0;
}

关键修复说明

  1. AVI对象模型修正:严格遵循AVIFileInit() → AVIFileOpenW()(操作PAVIFILE)→ AVIFileCreateStream()(创建PAVISTREAM)→ 写入 → 关闭流 → 关闭文件 → AVIFileExit()的流程。
  2. 独立帧存储:每帧创建新的HBITMAP,避免所有帧指向同一个位图对象。
  3. 帧率控制:用chrono和thread实现固定60fps的录制间隔。
  4. 正确的格式设置:使用BITMAPINFO结构描述视频格式,注意biHeight设为负数以符合AVI的顶行优先规范。
  5. 帧数据提取:通过GetDIBits()将HBITMAP转换为DIB格式数据,这是AVIStreamWrite可接受的像素数据格式。
  6. 安全资源释放:添加SafeDeleteBitmap工具函数,确保所有HBITMAP和AVI对象被正确释放,避免内存泄漏。

内容的提问来源于stack exchange,提问作者joeymalvinni

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 23:00:57