这是我作为Linux的.wav音频记录库编写的程序。它是在Raspberry Pi上开发的,因此可能会影响所需的依赖项。(1)
wav.h
主程序:
#include <stdint.h>
typedef struct
{
char RIFF_marker[4];
uint32_t file_size;
char filetype_header[4];
char format_marker[4];
uint32_t data_header_length;
uint16_t format_type;
uint16_t number_of_channels;
uint32_t sample_rate;
uint32_t bytes_per_second;
uint16_t bytes_per_frame;
uint16_t bits_per_sample;
} WaveHeader;
WaveHeader *genericWAVHeader(uint32_t sample_rate, uint16_t bit_depth, uint16_t channels);
WaveHeader *retrieveWAVHeader(const void *ptr);
int writeWAVHeader(int fd, WaveHeader *hdr);
int recordWAV(const char *fileName, WaveHeader *hdr, uint32_t duration);
(1):现在程序需要一个USB设备进行输入。我在声明输入设备的位置留下了注释,以便可以根据需要进行更改。
此外,如果安装了PulseAudio,我不确定该程序是否可以正常运行。
#1 楼
一些事情立即对我产生影响:当您需要大小不固定的东西时,请使用
sizeof()
。换句话说,在write
结构成员上的所有WaveHeader
调用都应使用sizeof
,而不是硬编码的大小。不需要使用用于初始化结构的相同方法来分配它(除非它是不透明的指针,否则您实际上会在意它的分配位置)。没有理由不能将您的WaveHeader
放在堆栈中。 作为一般规则,首先使用自动分配(“堆栈”),只有在您有一些令人信服的理由(原因通常是大小变化,太大而无法使用)时才转向动态分配
分开初始化和分配可以让API的用户决定如何处理内存。除非您需要为他们做出决定,否则就不要这样做。
如果您感到懒惰,可以始终使用
genericWavHeaderInit
和genericWaveHeaderCreate
代码永远不要输出任何东西。使用错误代码,并允许调用方处理错误报告。如果由于某种原因应忽略该错误怎么办?好吧,为时已晚。您已经排除了错误。
想象一下,如果标准库函数将错误输出到
stderr
。这将是荒唐的:)。操作资源,而不是创建那些资源所需的资源
RecordWAV
应该采取一个snd_pcm_t
并对其进行分配/初始化。自己总结一下
RecordWAV
的功能。真正地完成所有步骤,向自己解释每个代码块的功能。它不仅可以录制WAV,而且功能还很多。那确实应该来自命令行参数或配置文件之类的东西。想要使用不同的USB端口,则无需重新编译。另一个令人高兴的副作用:您的资源清理代码不必在每个出口点重复一万亿次(重复性更低,但更重要的是:更少的错误发生!)
这是相当主观的,但我不喜欢您的命名方案。
subjectVerb
具有提供各种“命名空间”的良好效果(每个广泛使用的C库都具有标准前缀(curl_
,qt_
,glib_
,apr_
等)。 >后缀在技术上可以实现相同的命名间隔效果,但它要稀少得多。同样,当函数的唯一目的是对通常应该结构化的某个对象(即结构)进行操作第一个参数(
writeWAVHeader
)。 writeWavHeader
)。它在视觉上不那么刺耳,更易于键入,并且熟悉WAV的任何人都可以理解。/>先包含您自己的标头,然后再包含其他标头。如果您的
RIFF_marker
对文件具有隐藏的依赖关系,则它可能会被包括它的源文件隐藏。包括可能会导致突然神秘的未声明符号错误的隐藏依赖性。wav.h
未定义。它似乎也是retrieveWAVHeader
的不必要版本如果
(WaveHeader*) ptr
的返回值是一个ALSA常量(因为它是错误代码之一),则应使用任何ALSA的成功常量。我无法想象它不会是0,但是与其他可能的收益保持一致会很好。#2 楼
在C语言中,我宁愿接受一个指向要初始化的结构的指针,也不愿返回新分配的堆内存。显然,调用者拥有该内存。调用方可以灵活地向您传递指向其堆栈上的struct
的指针。void initWAVHeader(WaveHeader *hdr, uint32_t sample_rate, uint16_t bit_depth, uint16_t channels);
其余功能也是如此。我会将
WaveHeader *
作为所有函数的第一个参数,以使您的库具有设计一致性感。要填充该结构,请考虑保留全局
static
原型。无需设置每个字段的零散内容,而是整个结构,然后填写您需要更改的部分。 ,只要该结构正确地包装在内存中。将memcpy()
放在__attribute__((__packed__))
声明的末尾应确保GCC不会在成员之间添加填充。我对这种样式有疑问: br />
我看到的问题是:
struct
产品线太长,无法满足我的口味。主体应该在单独的行上。(对
if
的调用)重复太多,但是并不能立即清楚代码分支的共同点。每个库调用上的错误代码,我想养成一个习惯,从不在snd_pcm_hw_params_set_format()
条件之外调用库函数。这是一个习惯用法-if
。 #3 楼
您的recordWAV
应该会收到使用genericWAVHeader
设置的标头。该函数采用所需的采样率,并将其应用于标题。但是,在recordWAV
中,设置PCM句柄参数的函数确定了实际采样率(您对
snd_pcm_hw_params_set_rate_near
的调用会修改建议的采样率),因此在传递的标头中编码的速率到
recordWAV
可能不正确。根据您当前的安排,无法解决此问题。这使我
......我认为公共接口是错误的。
标头不应是公共的。调用方不需要了解
标头,因为它是WAV文件的实现细节。
没有理由要求函数打开文件以及所有
它必须做的其他事情。只需传入一个文件描述符(或一个
FILE*
),那么我将函数原型更改为:
太大,错误处理就会经常重复
。我希望看到一个简单的函数,例如:
open_pcm_channel()
是唯一了解标头的知识,而record_wav
是唯一了解获取标头和写入数据的知识,它会在本地创建并释放必要的缓冲区。在上面的
write_header
中,已在该函数中分配,使用和释放了write_data
(不是您目前似乎不释放它)。对'snd'函数的调用链可以是类似的东西::
请注意,对
open_pcm_channel()
的调用似乎是多余的,并且其调用参数
snd_pcm_hw_params_t *params;
的命名错误(函数获取时间不是)。同样,对
snd_pcm_hw_params_get_period_time
的调用似乎是多余的因为
sampleRate
返回了其调用参数中使用的大小。如果您
在大端CPU上运行,则您的函数将无法正确写入标头
。 />
没有检查您的
snd_pcm_hw_params_get_period_size
分配错误,也没有释放它。评论
\ $ \ begingroup \ $
那条长链可能是err = snd_pcm_func1()|| snd_pcm_func2()|| snd_pcm_func_last()
\ $ \ endgroup \ $
– 200_success
2014年1月18日23:30
\ $ \ begingroup \ $
@ 200_success IMO,它将失去int值,并在err中保留一个布尔值。
\ $ \ endgroup \ $
– ChristW
2014年1月19日,0:57
\ $ \ begingroup \ $
@ 200_success-如果您希望err的最终值等于失败函数返回的值,则不这样做。在您的示例中,err将保持0或1。
\ $ \ endgroup \ $
–威廉·莫里斯(William Morris)
2014年1月19日,0:57
\ $ \ begingroup \ $
哦,对。我在想||例如Perl或JavaScript,这是第一个真实值。
\ $ \ endgroup \ $
– 200_success
2014年1月19日,下午2:56
#4 楼
可移植性现在,您已经在第一行盯着您了。
>
通过该声明,我现在知道您的“库”是针对特定声音体系结构量身定制的。尝试在Windows或Mac上运行代码,您的库就不会被剪掉。
相反,您可能需要为一个名为PortAudio的出色库编写包装。如果您不相信我认为最好的C库之一,请阅读他们网站上的第一段。
PortAudio是免费的,跨平台的,开放的,源,音频I / O库。
它使您可以用“ C”或C ++编写简单的音频程序,这些程序可以在许多平台上编译并运行,包括Windows,Macintosh OS X,
和Unix(OSS / ALSA)。旨在促进不同平台上的开发人员之间的音频软件交换。许多应用程序
使用PortAudio进行音频I / O。
我认为您的选择很明确。放弃处理大量声音架构API的DIY方法,并为PortAudio库创建包装器。您不会感到失望。
在声音体系结构依赖性之上,然后将另一个依赖性分配给特定的设备。
#include <alsa/asoundlib.h>
这是一个什么库?幸运的是,PortAudio还可以使用功能
Pa_GetDefaultInputDevice()
在这里为您完成所有艰苦的工作。Standards
您可能不应该再使用
write()
函数,因为它们尚未标准化;取而代之的是fwrite()
。C99和C11§7.19
C运行时环境的许多实现,尤其是UNIX操作系统,提供了:除了标准I / O库的
fopen
,fclose
,fread
,fwrite
和fseek
以外,一组未缓冲的I / O 服务
open
,close
,read
,write
和lseek
。 C89委员会决定不对后一组功能进行标准化。此外,缓冲的I / O总是比未缓冲的I / O更快。在某些情况下,您可能希望使用无缓冲功能,例如,每当您要确保在继续输出之前已写入输出时。但是在这种情况下,您将要使用
fwrite()
。fopen()
是您正在使用的一种广泛使用的文件I / O函数,在C11中得到了改头换面。现在,它支持新的独占创建和打开模式(“...x“
)。新模式的行为类似于POSIX中的O_CREAT|O_EXCL
,通常用于锁定文件。 “...x”
系列模式包括以下选项:wx
创建用于独占访问的文本文件。wbx
创建用于独占访问的二进制文件。w+x
创建文本文件以具有独占访问权限进行更新。w+bx
或wb+x
创建二进制文件以具有独占访问权限进行更新。如果文件已存在,则无法使用上述任何独占模式打开文件或无法创建。否则,将创建具有独占(非共享)访问权限的文件。此外,还提供了更安全的
fopen()
版本,称为fopen_s()
。如果您是我,那就是我会在您的代码中使用的内容,但我会留给您自己决定和更改。库设计
您的整个库都可以将音频写到
.wav
文件中,并且可以正常工作。但是,如果我想以Apple编写.wav
文件的方式写出该怎么办? (是的,由于某些原因它们与Windows编写它们的方式不同)或者如果我想要一个.flac
文件怎么办?我看到您已经将.wav
转换为.flac
转换器,但是在格式之间进行转换会花费不必要的时间。 作为程序员,您应该始终在寻找使程序过程更加动态和高效(同时优化)的方法。例如,将音频直接存储到
.flac
文件中比将音频文件转换为特定编解码器更为有效。那么我们该怎么做呢?输入libsndfile。该库可以读写您能想到的几乎每种形式的音频编解码器。它比当前的实现更具动态性,并且更容易在输出音频文件类型之间进行切换。
语法/样式/>声明变量时,几乎总是应该初始化变量。
const char *device = "plughw:1,0"; // USB microphone
在camelCase和snake_case之间切换。您需要保持一致。
评论
\ $ \ begingroup \ $
“这是我作为Linux的.wav录音库编写的程序。”我看不到这些建议中的大多数如何有用。如果要为Linux创建库,那么使用POSIX函数(写等)有什么问题?
\ $ \ endgroup \ $
–杰瓦
15年7月16日在23:33
\ $ \ begingroup \ $
@jacwah我不是故意不礼貌,但您是否读过我的评论?我认为我在答案中使用了write()来解释这个问题。即使问题是针对Linux编写的,也没有提出建议以使库具有更高的可移植性(这就是库应努力做到的)。
\ $ \ endgroup \ $
–syb0rg
15年7月18日在18:34
\ $ \ begingroup \ $
对不起,我可能一直很粗鲁。我确实读过评论,但认为它并不是针对该问题而专门设计的。跨平台库很棒-但这并不一定总是目标。答案没有错,我意识到我的评论听起来很刺耳。
\ $ \ endgroup \ $
–杰瓦
15年7月18日在18:43
评论
显然,Raspberry Pi默认情况下为低端字节序。但可以是big-endian。