FOURCC的生成

博客围绕FOURCC的生成展开,介绍了FOURCC是编程中常用的32位标识符。探讨了用宏、内联函数和模板生成FOURCC的方法。指出宏虽简单但不安全,内联函数无法在编译期得到结果,最终利用C++模板和enum特性实现编译期计算,解决了FOURCC生成问题。

FOURCC的生成

什么是FOURCC

FOURCC全称Four-Character Codes,是在编程中非常常用的东西,一般用作标示符。比如wav、avi等RIFF文件的标签头标示,Quake 3的模型文件.md3中也大量存在等于“IDP3”的FOURCC。它是一个32位的标示符,其实就是

typedef unsigned long FOURCC

用宏生成FOURCC

FOURCC是由四个字符拼接而成的。生成FOURCC的传统方法是:

// 来自mmsystem.h
#define MAKEFOURCC(ch0, ch1, ch2, ch3)    / 
 ((DWORD)(BYTE)(ch0) | ((DWORD)(BYTE)(ch1) << 8) |  / 
 ((DWORD)(BYTE)(ch2) << 16) | ((DWORD)(BYTE)(ch3) << 24 ))

这种方法很简单直观,而且以下代码:

switch (val)
{
case MAKEFOURCC('f', 'm', 't', ' '):
 ...
 break;

case MAKEFOURCC('d', 'a', 't', 'a'):
 ...
 break;

 ...
}

能顺利通过编译,因为宏能在编译期生成常量,符合case的条件。

内联函数?

如果你是一个纯粹的C++用户,那么你一定会对带参数的宏非常反感,无论是《The C++ Programming Language》、《Effective C++》还是其他好的C++书籍,都会劝导你不要使用带参数的宏,还会列举出很多很多例子,来说明宏的坏处。想到这里,你就会觉得应该用内联函数来代替带参数的宏。于是,你写下如下的代码:

inline FOURCC MakeFOURCC(char ch0, char ch1, char ch2, char ch3)
{
 return (ch0 << 0) + (ch1 << 8) + (ch2 << 16) + (ch3 << 24);
}

OK,它实现了原来的那个宏的行为,并能得到正确的结果。你也许会认为这就是完美的答案了。慢着,当你把新代码加入工程,重新编译时就会发现——编译失败了!case那行出错。这是因为case后面的值必须是常量。于是,你修改了代码:

const FOURCC fccFMT  = MakeFOURCC('f', 'm', 't', ' ');
const FOURCC fccDATA = MakeFOURCC('d', 'a', 't', 'a');
...
switch (val)
{
case fccFMT:
 ...
 break;

case fccDATA:
 ...
 break;

 ...
}

你试图通过定义const常量来把计算结果存放在fccFMT和fccDATA里。但是,错误照旧。“但是我带入MakeFOURCC的四个参数的都是常量啊?应该在编译的时候就能计算出MakeFOURCC的返回值才对!”我听见你在大声喊着。的确,在优化阶段,MakeFOURCC能计算出结果并存放在常量fccFMT和fccDATA里,并有可能在使用fccFMT和fccDATA的地方直接带入它们的值。但是请注意,编译是在优化之前的,也就是说,case后面的值必须是在编译阶段就已经知道具体的值,而不能等到优化阶段。况且,debug版默认是没有优化没有内联的,因此也否定了该方案。更要说明的是,“inline”关键字只是一个建议,而不是一个命令。它只能建议编译器:“如果允许的话,就请把它内联吧。”而编译器完全有权不去内联一个函数。所以,内联函数是不能再编译期得到结果的。

用模板!

难道要退回古老的宏?当然不是,否则还要这篇文章干什么:) C++的模板机制给程序员带来的无限的空间,它不光能让类型作为参数,还能将常量作为参数(这一点常常被人遗忘),而且这一切都是编译期决定的!这是我们用它来生成FOURCC的第一个基础。

于是,你就迫不及待的利用模板来改写上面的函数:

template <char ch0, char ch1, char ch2, char ch3>
inline FOURCC MakeFOURCC()
{
 return (ch0 << 0) + (ch1 << 8) + (ch2 << 16) + (ch3 << 24);
}

可是错误照旧。虽然这次可以保证返回值能在编译期计算出来,但可惜的是那个return语句却要等到运行才能运行(也有可能在优化阶段就能消除这个语句,但肯定不能再编译期就全部完成)。

别急,还有第二个基础才可以。那是什么?是一个从C语言继承来的东西——enum。很多朋友认为,它不是很重要,因为很多情况下可以用别的方法来取代它,比如const。但是它有一个经常被人忽略的特性,而且这个特性非常重要,那就是——它的值必须在编译期就得出,即它是个编译期常量!这不是正符合我们的需要吗?请看下面的模板:

template <char ch0, char ch1, char ch2, char ch3>
struct MakeFOURCC
{
 enum { value = (ch0 << 0) + (ch1 << 8) + (ch2 << 16) + (ch3 << 24) };
};

核心还是和上面一样,通过表达式(ch0 << 0) + (ch1 << 8) + (ch2 << 16) + (ch3 << 24)计算FOURCC(那当然是一样的)。但是计算的时机从运行期或者优化期移到了编译期。编译器在编译时,通过模板带入的char常量计算出表达式的值,并把它保存在枚举值value里。看看现在的代码:

const FOURCC fccFMT  = MakeFOURCC<'f', 'm', 't', ' '>::value;
const FOURCC fccDATA = MakeFOURCC<'d', 'a', 't', 'a'>::value;
...
switch (val)
{
case fccFMT:
 ...
 break;

case fccDATA:
 ...
 break;

 ...
}

成功了,MakeFOURCC模板顺利地完成了任务。FOURCC的模板生成法既让我们抛弃了那个不安全的宏,又让我们看到了inline的局限性,还让我们重新认识了enum的一些特性。其它许多类似的问题也能通过template + enum来解决。它们可以带你进入奇妙的“编译期编程”的世界。

祝大家编程愉快:)

### OpenCV FourCC 无法正常工作的原因及解决方案 在使用 OpenCV 的 `VideoWriter` 类时,FourCC 编码器可能会遇到无法正常工作的问题。以下是一些常见原因及解决方法: 1. **确保正确设置 FourCC 编码器** 在某些平台上,FourCC 编码器的设置方式可能有所不同。例如,在 Python 中,可以使用以下两种方法之一来定义 FourCC 编码器: - 使用字符串解包的方式:`fourcc = cv2.VideoWriter_fourcc(*'XVID')`[^2]。 - 明确指定字符:`fourcc = cv2.VideoWriter_fourcc('m', 'p', '4', 'v')`[^2]。 如果直接使用类似 `cv2.CV_FOURCC` 的旧式方法,可能会导致错误,因为该方法已被弃用。 2. **检查视频编码器的兼容性** 不同的操作系统和安装环境支持的视频编码器可能不同。例如,在 Windows 上,`XVID` 是一个常见的选择,但在 macOS 或 Linux 上,可能需要尝试其他编码器(如 `MJPG`)。可以通过以下代码列出系统支持的所有 FourCC 编码器: ```python import cv2 fourcc_list = [cv2.VideoWriter_fourcc(*f) for f in ['XVID', 'MJPG', 'DIVX']] print(fourcc_list) ``` 3. **验证输出路径的有效性** 确保保存视频文件的路径是有效的,并且程序具有写入权限。如果路径无效或没有写入权限,可能导致视频无法正确生成。 4. **确保帧尺寸与视频写入器匹配** 在初始化 `VideoWriter` 时,必须提供与输入帧大小一致的参数。如果帧大小不匹配,视频可能无法正常写入。例如: ```python rows, cols, channels = frame.shape my_writer = cv2.VideoWriter('output.avi', fourcc, 10, (cols, rows)) ``` 5. **调试视频录制过程** 在录制过程中,可以通过打印日志来确认每一帧是否成功写入。例如: ```python while ret and frames_remaining > 0: ret, frame = cap.read() if not ret: break my_writer.write(frame) frames_remaining -= 1 print(f"Frames remaining: {frames_remaining}") ``` 6. **检查 OpenCV 安装版本** 某些 FourCC 问题可能是由于 OpenCV 版本不兼容引起的。建议升级到最新版本的 OpenCV,并确保所有依赖项已正确安装。例如: ```bash pip install --upgrade opencv-python ``` ### 示例代码 以下是一个完整的示例代码,展示如何正确使用 FourCC 编码器录制视频: ```python import cv2 def write_video(): cap = cv2.VideoCapture(0) ret, frame = cap.read() if not ret: print("Failed to capture the first frame.") return rows, cols, channels = frame.shape fourcc = cv2.VideoWriter_fourcc(*'XVID') # 使用 XVID 编码器 my_writer = cv2.VideoWriter('output.avi', fourcc, 10, (cols, rows)) fps = 30 seconds = 5 # 设置录制时间(秒) frames_remaining = seconds * fps while ret and frames_remaining > 0: ret, frame = cap.read() if not ret: break my_writer.write(frame) frames_remaining -= 1 print(f"Frames remaining: {frames_remaining}") cap.release() my_writer.release() cv2.destroyAllWindows() write_video() ``` ### 注意事项 - 如果仍然遇到问题,可以尝试更换编码器(如 `MJPG`)或调整帧率和分辨率。 - 确保摄像头设备正常工作,并且 OpenCV 能够正确访问设备。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值