Gio::DBus 中创建复合类型的参数
首先,需要知道两个基本知识:
- 每一个参数都需要被包装为 Variant。
- 全体参数需要被整体包装成一个 Tuple。
即使只有一个参数也需要包装为 Tuple,例如一个 string:
const auto arg1 = Glib::Variant<Glib::ustring>::create("hello world"); Glib::VariantContainerBase args = Glib::VariantContainerBase::create_tuple(arg1);
Glib::Variant
是模板,是不完全的类型,所以要使用它的基类 Glib::VariantBase
来表示任意类型的 Variant 对象。
因此,一个 a{sv}
类型的参数类型为 Glib::Variant<std::map<Glib::ustring, Glib::VariantBase>>
Linux C API 获取广播地址
广播地址通常为网段的最后一个地址
通过对一个 socket 进行 ioctl(SIOCGIFBRDADDR)
即可获取广播地址。
struct ifreq req;
strcpy(req.ifr_name, "网卡名");
ioctl(sock, SIOCGIFBRDADDR, &req);
inet_ntoa(((struct sockaddr_in*)&(req.ifr_addr))->sin_addr)
遍历网卡的方法可以参考 Linux C API 获取本机的 IP 地址
示例:
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cerrno>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <sys/ioctl.h>
bool getBoardCastAddress(const char* iface, char* buffer, size_t n)
{
int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (sock < 0)
{
fprintf(stderr, "socket: %s\n", strerror(errno));
return false;
}
struct ifreq req;
strcpy(req.ifr_name, iface);
if (ioctl(sock, SIOCGIFBRDADDR, &req) < 0)
{
fprintf(stderr, "ioctl: %s\n", strerror(errno));
close(sock);
return false;
}
strncpy(buffer, inet_ntoa(((struct sockaddr_in*)&(req.ifr_addr))->sin_addr), n);
close(sock);
return true;
}
int main(void)
{
char addr[INET_ADDRSTRLEN];
if (!getBoardCastAddress("enp2s0", addr, INET_ADDRSTRLEN))
{
return EXIT_FAILURE;
}
printf("broadcast %s\n", addr);
return EXIT_SUCCESS;
}
Linux C API 获取本机的 IP 地址
方法1:遍历网卡
#include <stdio.h>
#include <ifaddrs.h>
#include <arpa/inet.h>
int main (void)
{
struct ifaddrs* ifAddrStruct = NULL;
getifaddrs(&ifAddrStruct); // 获得网卡的链表
while (ifAddrStruct!=NULL)
{
if (ifAddrStruct->ifa_addr->sa_family==AF_INET)
{
// IPv4
char address[INET_ADDRSTRLEN];
void* inAddr = &((struct sockaddr_in *)ifAddrStruct->ifa_addr)->sin_addr;
inet_ntop(AF_INET, inAddr, address, INET_ADDRSTRLEN);
char netMask[INET_ADDRSTRLEN];
void* inNetMask = &((struct sockaddr_in *)ifAddrStruct->ifa_netmask)->sin_addr;
inet_ntop(AF_INET, inNetMask, netMask, INET_ADDRSTRLEN);
char broadAddr[INET_ADDRSTRLEN];
void* inBroadAddr = &((struct sockaddr_in *)ifAddrStruct->ifa_ifu.ifu_broadaddr)->sin_addr;
inet_ntop(AF_INET, inBroadAddr, broadAddr, INET_ADDRSTRLEN);
printf("IPv4 %8s: %31s \t%31s \t%31s\n", ifAddrStruct->ifa_name, address, netMask, broadAddr);
}
else if (ifAddrStruct->ifa_addr->sa_family==AF_INET6)
{
// IPv6
char address[INET6_ADDRSTRLEN];
void* inAddr = &((struct sockaddr_in *)ifAddrStruct->ifa_addr)->sin_addr;
inet_ntop(AF_INET6, inAddr, address, INET6_ADDRSTRLEN);
char prefixMask[INET6_ADDRSTRLEN];
void* inPrefixMask = &((struct sockaddr_in *)ifAddrStruct->ifa_netmask)->sin_addr;
inet_ntop(AF_INET6, inPrefixMask, prefixMask, INET6_ADDRSTRLEN);
printf("IPv6 %8s: %31s \t%31s\n", ifAddrStruct->ifa_name, address, prefixMask);
}
ifAddrStruct=ifAddrStruct->ifa_next;
}
return 0;
}
方法2:gethostbyname
这个方法不能支持 IPv6
#include <stdio.h>
#include <unistd.h>
#include <netdb.h>
#include <arpa/inet.h>
int main (void)
{
// 获取 host name
char hostName[_SC_HOST_NAME_MAX];
gethostname(hostName, _SC_HOST_NAME_MAX);
// 通过 host name 获取 host entry
struct hostent* hostEntry = gethostbyname(hostName);
// 打印 IP 地址
for(int i = 0; hostEntry->h_addr_list[i]; i++)
{
printf("%s\n", inet_ntoa(*(struct in_addr*)(hostEntry->h_addr_list[i])));
}
return 0;
}
在
netdb.h
中还有一个gethostent
函数,能够遍历 host entry,但是它只能查询/etc/hosts
里的值。因此,它通常只能返回127.0.0.1
#include <stdio.h> #include <unistd.h> #include <netdb.h> #include <arpa/inet.h> int main(void) { struct hostent* hostEntry; while((hostEntry = gethostent()) != NULL) { for(int i = 0; hostEntry->h_addr_list[i]; i++) { printf("%s\n", inet_ntoa(*(struct in_addr*)(hostEntry->h_addr_list[i]))); } } return 0; }
通过 ALSA 显示音频设备的声道
// apt install libasound2-dev
// LDFLAGS := -lasound
#include <stdio.h>
#include <stdlib.h>
#include <alsa/asoundlib.h>
int main(int argc, char* argv[])
{
if(argc != 2)
{
fprintf(stderr, "Usage: %s <device>\n", argv[0]);
fprintf(stderr, " %s hw:0,0\n", argv[0]);
return EXIT_FAILURE;
}
snd_pcm_t* pcm = NULL;
if(snd_pcm_open(&pcm, argv[1], SND_PCM_STREAM_PLAYBACK, 0) < 0)
{
fprintf(stderr, "snd_pcm_open failed\n");
return EXIT_FAILURE;
}
snd_pcm_chmap_query_t** chmaps = snd_pcm_query_chmaps(pcm);
if(chmaps == NULL)
{
fprintf(stderr, "snd_pcm_query_chmaps failed\n");
return EXIT_FAILURE;
}
for(int i = 0; chmaps[i] != NULL; i++)
{
snd_pcm_chmap_query_t* mapping = chmaps[i];
printf("Mapping-%d\n", i);
for(int ch = 0; ch < mapping->map.channels; ch++)
{
printf("\tchannel-%d %s\n", ch, snd_pcm_chmap_long_name(mapping->map.pos[ch]));
}
}
snd_pcm_free_chmaps(chmaps);
return EXIT_SUCCESS;
}
通过 PulseAudio 播放和录制声音
// libs: -lpulse
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <stdbool.h>
#include <pulse/pulseaudio.h>
#ifdef DEBUG
#define LOG(...) printf(__VA_ARGS__)
#else
#define LOG(...)
#endif
typedef enum Method
{
METHOD_PLAY,
METHOD_RECORD
}Method;
typedef struct UserData
{
pa_context* context;
pa_stream* stream;
pa_mainloop_api* api;
FILE* fp;
Method method;
}UserData;
static void mainloop_quit(UserData* userdata, int ret);
static void context_state_callback(pa_context* context, void* userdata) ;
static void stream_write_callback(pa_stream* stream, size_t length, void* userdata);
static void stream_read_callback(pa_stream* stream, size_t length, void* userdata);
static void stream_drain_complete(pa_stream* stream, int success, void* userdata);
static void context_drain_complete(pa_context* c, void* userdata);
int main(int argc, char* argv[])
{
// 参数检查
if(argc != 3)
{
printf("pademo [play|record] <sound-file>\n");
return EXIT_FAILURE;
}
// 创建一个线程,并在该线程中创建 mainloop
pa_mainloop* mainloop = pa_mainloop_new();
if(mainloop == NULL)
{
fprintf(stderr, "pa_threaded_mainloop_new failed.\n");
return EXIT_FAILURE;
}
// 获取API
pa_mainloop_api* api = pa_mainloop_get_api(mainloop);
if(api == NULL)
{
pa_mainloop_free(mainloop);
fprintf(stderr, "pa_threaded_mainloop_get_api failed.\n");
return EXIT_FAILURE;
}
// 创建上下文
pa_context* context = pa_context_new(api, "demo");
if(context == NULL)
{
pa_mainloop_free(mainloop);
fprintf(stderr, "pa_context_new failed.\n");
return EXIT_FAILURE;
}
UserData data;
if(strcmp(argv[1],"play") == 0)
{
data.fp = fopen(argv[2], "rb");
data.method = METHOD_PLAY;
}
else if(strcmp(argv[1], "record") == 0)
{
data.fp = fopen(argv[2], "wb");
data.method = METHOD_RECORD;
}
else
{
fprintf(stderr, "Unknown method %s\n", argv[1]);
// 释放
pa_context_unref(context);
pa_mainloop_free(mainloop);
return EXIT_FAILURE;
}
// 设置状态变化的回调函数,这是主入口
data.context = context;
data.api = api;
pa_context_set_state_callback(context, context_state_callback, (void*)(&data));
// 开始建立连接
if(pa_context_connect(context, NULL, PA_CONTEXT_NOFAIL, NULL) < 0)
{
pa_context_unref(context);
pa_mainloop_free(mainloop);
fprintf(stderr, "pa_context_connect failed.\n");
return EXIT_FAILURE;
}
// 运行mainloop
int ret = pa_mainloop_run(mainloop, NULL);
// 退出
pa_context_unref(context);
pa_mainloop_free(mainloop);
return ret;
}
// 退出主循环
static void mainloop_quit(UserData* userdata, int ret)
{
LOG("mainloop_quit\n");
userdata->api->quit(userdata->api, ret);
}
// 状态变化的回调函数
static void context_state_callback(pa_context* context, void* userdata)
{
UserData* data = (UserData*)(userdata);
pa_context_state_t state = pa_context_get_state(context);
switch (state)
{
case PA_CONTEXT_READY: // 上下文就绪
{
LOG("PA_CONTEXT_READY\n");
// 创建spec
pa_sample_spec sampleSpec;
sampleSpec.rate = 44100;
sampleSpec.format = PA_SAMPLE_S16LE;
sampleSpec.channels = 2;
// 创建channel map
pa_channel_map channelMap;
pa_channel_map_init_stereo(&channelMap);
// 创建stream
pa_stream* stream = pa_stream_new(context, "demo-stream", &sampleSpec, &channelMap);
data->stream = stream;
if(data->method == METHOD_PLAY) // 播放
{
pa_stream_connect_playback(stream, NULL, NULL, PA_STREAM_NOFLAGS, NULL, NULL);
pa_stream_set_write_callback(stream, stream_write_callback, userdata);
}
else if(data->method == METHOD_RECORD) // 录音
{
pa_stream_connect_record(stream, NULL, NULL, PA_STREAM_NOFLAGS);
pa_stream_set_read_callback(stream, stream_read_callback, userdata);
}
break;
}
case PA_CONTEXT_TERMINATED: // 结束
LOG("PA_CONTEXT_TERMINATED\n");
mainloop_quit(data, EXIT_SUCCESS);
break;
default:
LOG("context state %d\n", state);
}
}
// 播放的回调
static void stream_write_callback(pa_stream* stream, size_t length, void* userdata)
{
UserData* data = (UserData*)(userdata);
void* buffer;
while(true)
{
// 给buffer分配空间,不需要手动释放
pa_stream_begin_write(stream, &buffer, &length);
// 读取文件,写入stream
length = fread(buffer, 1, length, data->fp);
pa_stream_write(stream, buffer, length, NULL, 0, PA_SEEK_RELATIVE); // 会自动释放buffer
LOG("play %zu bytes\n", length);
// 文件读取完毕
if(feof(data->fp))
{
pa_stream_cancel_write(stream);
pa_stream_set_write_callback(stream, NULL, NULL); //清除回调
pa_operation* o = pa_stream_drain(stream, stream_drain_complete, data); // 设置播放完毕时的回调
if(o == NULL)
{
mainloop_quit(data, EXIT_FAILURE);
}
pa_operation_unref(o);
break;
}
}
}
// 录音的回调
static void stream_read_callback(pa_stream* stream, size_t length, void* userdata)
{
UserData* data = (UserData*)(userdata);
const void *buffer;
while(pa_stream_readable_size(stream) > 0)
{
pa_stream_peek(stream, &buffer, &length);
if(buffer == NULL || length <= 0)
{
continue;
}
fwrite(buffer, length, 1, data->fp);
fflush(data->fp);
LOG("record %zu bytes\n", length);
pa_stream_drop(stream);
}
}
// 播放完毕的回调
static void stream_drain_complete(pa_stream* stream, int success, void* userdata)
{
(void)(success);
LOG("stream_drain_complete\n");
UserData* data = (UserData*)(userdata);
// 释放stream
pa_stream_disconnect(stream);
pa_stream_unref(stream);
data->stream = NULL;
// 设置上下文结束的回调
pa_operation* o = pa_context_drain(data->context, context_drain_complete, NULL);
if (o == NULL)
{
pa_context_disconnect(data->context);
}
else
{
pa_operation_unref(o);
}
}
// 上下文结束的回调
static void context_drain_complete(pa_context* context, void* userdata)
{
(void)(userdata);
LOG("context_drain_complete\n");
pa_context_disconnect(context);
}
Linux Hook 技术
Hook 是一种覆盖重写进程中符号的技术,在 Linux 中,通过环境变量 LD_PRELOAD
预加载包含同名符号的动态库即可实现。
覆盖 malloc 和 free 检查内存泄漏
// 文件名: memcheck.c
// 编译命令: gcc -o memcheck.so memcheck.c --shared -fPIC
#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <execinfo.h>
// 打印调用栈的最大深度
#define MAX_STACK_DEPTH 16
typedef struct RecordNode RecordNode;
struct RecordNode
{
RecordNode* next;
void* ptr;
size_t size;
void* stack_trace[MAX_STACK_DEPTH];
size_t stack_depth;
};
static RecordNode* head = NULL; // 此处头指针不存数据,head->next 才是第一个结点
static RecordNode* tail = NULL;
static void* (*real_malloc)(size_t) = NULL; // 原 malloc 函数的地址
static void (*real_free)(void*) = NULL; // 原 free 函数的地址
static bool ignore = false; // 忽略内部调用的 malloc
// 打印调用栈
static void printStack(const RecordNode* node)
{
char** symbols = backtrace_symbols(node->stack_trace, node->stack_depth);
for (size_t i = 0; i < node->stack_depth; ++i) {
fprintf(stderr, " [%zu] %s \r\n", i, symbols[i]);
}
real_free(symbols);
}
// 打印内存泄漏记录
static void printRecord(void)
{
ignore = true;
for (RecordNode* node = head->next; node != NULL; node = node->next)
{
fprintf(stderr, "Leak %zu bytes at %p\n", node->size, node->ptr);
printStack(node);
}
ignore = false;
}
// 初始化
static void init()
{
// 通过 RTLD_NEXT 查找当前进程空间的下一个同名符号来获取原函数地址
real_malloc = (void*(*)(size_t))dlsym(RTLD_NEXT, "malloc");
real_free = (void(*)(void*))dlsym(RTLD_NEXT, "free");
head = (RecordNode*)real_malloc(sizeof(RecordNode));
head->next = NULL;
tail = head;
atexit(printRecord);
}
// 添加记录
static RecordNode* addRecord(void* ptr, size_t size)
{
RecordNode* node = (RecordNode*)real_malloc(sizeof(RecordNode));
node->next = NULL;
node->ptr = ptr;
node->size = size;
node->stack_depth = 0;
tail->next = node;
tail = node;
return node;
}
// 删除记录
static void delRecord(void* ptr)
{
RecordNode* prev = head;
for (RecordNode* node = head->next; node != NULL; node = node->next)
{
if (node->ptr == ptr)
{
prev->next = node->next;
if (node == tail)
tail = prev;
real_free(node);
break;
}
prev = node;
}
}
// hook malloc
void* malloc(size_t size)
{
if (real_malloc == NULL)
init();
void* ptr = real_malloc(size);
if (!ignore) // 防止内部调用 malloc 导致死循环
{
ignore = true;
RecordNode* node = addRecord(ptr, size);
node->stack_depth = backtrace(node->stack_trace, MAX_STACK_DEPTH);
ignore = false;
}
return ptr;
}
// hook free
void free(void* ptr)
{
real_free(ptr);
delRecord(ptr);
}
预加载 memcheck.so
来检查内存泄漏:
构建程序(即下述的 test)时在链接选项中添加
-rdynamic
选项导出符号表才能显示函数名,否则只能显示地址。
$ LD_PRELOAD=./memcheck.so ./test
Leak 233 bytes at 0x559e563c8350
[0] ./memcheck.so(malloc+0x7b) [0x7f6546b064a7]
[1] ./test(func1+0x12) [0x559e5579215b]
[2] ./test(func3+0x12) [0x559e55792185]
[3] ./test(main+0x12) [0x559e557921a4]
[4] /lib/x86_64-linux-gnu/libc.so.6(+0x29d90) [0x7f6546829d90]
[5] /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0x80) [0x7f6546829e40]
[6] ./test(_start+0x25) [0x559e55792085]
Leak 666 bytes at 0x559e563c9560
[0] ./memcheck.so(malloc+0x7b) [0x7f6546b064a7]
[1] ./test(func2+0x12) [0x559e55792170]
[2] ./test(func3+0x1c) [0x559e5579218f]
[3] ./test(main+0x12) [0x559e557921a4]
[4] /lib/x86_64-linux-gnu/libc.so.6(+0x29d90) [0x7f6546829d90]
[5] /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0x80) [0x7f6546829e40]
[6] ./test(_start+0x25) [0x559e55792085]
怎样 detach 子进程
通过 fork
创建进程后,父进程可以使用 waitpid
来获取子进程的结束状态。
如果子进程先于父进程退出,而父进程没有调用 waitpid 的话,子进程将一直存续下去以保持结束状态可以被获取。
此时,子进程被称为僵尸进程。
如果子进程没有被调用 waitpid
而父进程退出了,此时子进程被称为孤儿进程,它会被 init进程(PID为1) 领养并调用waitpid
回收。
有时我们并不关心子进程的结束状态,希望像 pthread_detach
一样示释放子进程。有以下两种方法可以实现:
- 父进程调用
fork
创建子进程A并对其调用waitpid
,子进程A再次fork
创建子进程B并退出。
这样一来,子进程A 由父进程回收,子进程B 成为孤儿进程被init进程回收。
- 父进程调用
signal(SIGCHLD, SIG_IGN)
SIGCHLD
是子进程状态发生变化时产生的信号,默认就是忽略的,但是必须显示调用才会释放子进程。
守护进程
上述方法一中创建孤儿进程的方法常被用于创建守护进程。常用步骤如下:
- 调用
fork
创建子进程,然后父进程退出。 - 子调用
setsid
创建新的会话和进程组,从而避免被原先的会话、进程组以及会话终端影响。 - 调用
chdir
设置工作目录。 - 调用
umask
设置文件掩码(通常设为umask(0)
)。 - 关闭不需要的资源(从父进程继承的文件描述符等)。
除第一步外的后续步骤并不是必须的,但大部分情况下是必要的。
调用 PAM 进行认证
// gcc main.c -lpam
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <termio.h>
#include <security/pam_appl.h>
/***************************************
* @brief 开关回显
* @param[in] fd 文件描述符
* @param[in] off 1-关闭回显,0-开启回显
* *************************************/
static void echoOff(int fd, int off)
{
struct termio tty;
(void) ioctl(fd, TCGETA, &tty);
if (off)
{
tty.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL);
(void) ioctl(fd, TCSETAF, &tty);
}
else
{
tty.c_lflag |= (ECHO | ECHOE | ECHOK | ECHONL);
(void) ioctl(fd, TCSETAW, &tty);
}
}
/***************************************
* @brief 关闭标准输入的回显
* *************************************/
static void echoOffStdin()
{
echoOff(fileno(stdin), 1);
}
/***************************************
* @brief 开启标准输入的回显
* *************************************/
static void echoOnStdin()
{
echoOff(fileno(stdin), 0);
}
/***************************************
* @brief 读取一行输入
* @return 输入的字符串
* *************************************/
static char* readline()
{
struct termio tty;
char input[PAM_MAX_RESP_SIZE];
/* 读取字符直到回车 */
flockfile(stdin);
int i = 0;
for (; i < PAM_MAX_RESP_SIZE; i++)
{
int ch = getchar_unlocked();
if (ch == '\n' || ch == '\r' ||ch == EOF)
break;
input[i] = ch;
}
funlockfile(stdin);
input[i] = '\0';
return (strdup(input));
}
/**************************************************
* @brief PAM对话回调函数
* @param[in] num_msg PAM发送过来的消息数量
* @param[in] msg PAM发送过来的消息数据
* @param[out] resp 发回给PAM的应答
* @param[in] appdata_ptr 用户参数
* @return 状态
* ************************************************/
static int conversation(int num_msg, const struct pam_message** msg, struct pam_response **resp, void *appdata_ptr)
{
// 检查参数
if (num_msg <= 0 || num_msg >= PAM_MAX_MSG_SIZE)
{
fprintf(stderr, "invalid num_msg(%d)\n", num_msg);
return PAM_CONV_ERR;
}
// 给回复消息分配内存,这里分配的内存,由外层的 PAM 框架释放
// TODO: 发生错误的时候需要手动释放
if ((resp[0] = malloc(num_msg * sizeof(struct pam_response))) == NULL)
{
fprintf(stderr, "bad alloc\n");
return PAM_BUF_ERR;
}
// 处理PAM发来的消息并应答
for(int i = 0; i < num_msg; i++)
{
const struct pam_message* m = *msg + i;
struct pam_response* r = *resp + i;
r->resp_retcode = 0; // 这个是保留属性,固定为0
switch (m->msg_style)
{
case PAM_PROMPT_ECHO_OFF: // 请求输入,不回显,例如请求输入用户名
printf("%s", m->msg);
echoOffStdin(); // 关闭回显
r->resp = readline(); // 读取密码
echoOnStdin(); // 开启回显
printf("\n"); // 补个换行
break;
case PAM_PROMPT_ECHO_ON: // 请求输入,回显,例如请求输入密码
printf("%s", m->msg);
r->resp = readline();
break;
case PAM_TEXT_INFO: // 打印普通消息
printf("%s\n", m->msg);
break;
case PAM_ERROR_MSG: // 打印错误消息
fprintf(stderr, "%s\n", m->msg);
break;
default:
printf("DEFAULT\n");
break;
}
}
return PAM_SUCCESS;
}
int main()
{
atexit(echoOnStdin); // 退出时开启回显
struct pam_conv pam_conv = {conversation, NULL};
pam_handle_t *pamh;
if (PAM_SUCCESS != pam_start("login", NULL, &pam_conv, &pamh))
{
fprintf(stderr, "pam_start failed\n");
return EXIT_FAILURE;
}
if (PAM_SUCCESS != pam_authenticate(pamh, 0))
{
fprintf(stderr, "pam_authenticate failed\n");
pam_end(pamh, 0);
return EXIT_FAILURE;
}
pam_end(pamh, 0);
return EXIT_SUCCESS;
}
通过 V4L2 读取摄像头
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <stdint.h>
#include <stdbool.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/videodev2.h> // libv4l-dev
#define VIDEO_DEV "/dev/video0"
int main()
{
// 打开设备
int videoFd = open(VIDEO_DEV, O_RDWR);
if(videoFd < 0)
{
fprintf(stderr, "open %s failed: %s\n", VIDEO_DEV, strerror(errno));
return EXIT_FAILURE;
}
// 读取设备属性
struct v4l2_capability videoCap;
if(ioctl(videoFd, VIDIOC_QUERYCAP, &videoCap) < 0)
{
close(videoFd);
fprintf(stderr, "ioctl VIDIOC_QUERYCAP failed: %s\n", strerror(errno));
return EXIT_FAILURE;
}
printf("%s\n", videoCap.card);
// 判断是否是摄像头
if(videoCap.capabilities & V4L2_CAP_VIDEO_CAPTURE != V4L2_CAP_VIDEO_CAPTURE)
{
fprintf(stderr, "%s doesn't support video recording\n", VIDEO_DEV);
close(videoFd);
return EXIT_FAILURE;
}
// 读取支持的格式
bool supportYUYV = false;
struct v4l2_fmtdesc fmtdesc;
fmtdesc.index=0;
fmtdesc.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
printf("Support format:\n");
while(ioctl(videoFd, VIDIOC_ENUM_FMT, &fmtdesc) != -1)
{
printf("\t%d.%s\n",fmtdesc.index+1,fmtdesc.description);
fmtdesc.index++;
if(fmtdesc.pixelformat == V4L2_PIX_FMT_YUYV)
{
supportYUYV = true;
}
}
if(supportYUYV == false)
{
fprintf(stderr, "YUYV 4:2:2 not supported\n");
close(videoFd);
return EXIT_FAILURE;
}
// 设置帧格式
struct v4l2_format videoFormat;
videoFormat.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
videoFormat.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
if(ioctl(videoFd, VIDIOC_S_FMT, &videoFormat) < 0)
{
fprintf(stderr, "ioctl VIDIOC_S_FMT failed: %s\n", strerror(errno));
close(videoFd);
return EXIT_FAILURE;
}
// 申请缓冲
struct v4l2_requestbuffers request;
request.count = 3; //三帧缓冲
request.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
request.memory = V4L2_MEMORY_MMAP;
if(ioctl(videoFd, VIDIOC_REQBUFS, &request) < 0)
{
fprintf(stderr, "ioctl VIDIOC_REQBUFS failed: %s\n", strerror(errno));
close(videoFd);
return EXIT_FAILURE;
}
// 获取缓冲
struct v4l2_buffer buffer;
memset(&buffer, 0, sizeof(buffer));
buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buffer.memory = V4L2_MEMORY_MMAP;
buffer.index = 0;
if(ioctl(videoFd, VIDIOC_QUERYBUF, &buffer) < 0)
{
fprintf(stderr, "ioctl VIDIOC_QUERYBUF failed: %s\n", strerror(errno));
close(videoFd);
return EXIT_FAILURE;
}
void* bufPtr = mmap(NULL, buffer.length, PROT_READ|PROT_WRITE, MAP_SHARED, videoFd, buffer.m.offset);
if(bufPtr == NULL)
{
fprintf(stderr, "mmap failed: %s\n", strerror(errno));
close(videoFd);
return EXIT_FAILURE;
}
// 创建一个帧缓冲
if(ioctl(videoFd, VIDIOC_QBUF, &buffer) < 0)
{
fprintf(stderr, "ioctl VIDIOC_QBUF failed: %s\n", strerror(errno));
close(videoFd);
return EXIT_FAILURE;
}
// 开始采集
enum v4l2_buf_type bufType = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if(ioctl(videoFd, VIDIOC_STREAMON, &bufType) < 0)
{
fprintf(stderr, "ioctl VIDIOC_STREAMON failed: %s\n", strerror(errno));
close(videoFd);
return EXIT_FAILURE;
}
// 等待采集完成
fd_set fds;
FD_ZERO(&fds);
FD_SET(videoFd, &fds);
struct timeval tv;
tv.tv_sec = 2;
tv.tv_usec = 0;
select(1, &fds, NULL, NULL, &tv);
// 读取一帧图像并删除帧缓冲
if(ioctl(videoFd, VIDIOC_DQBUF, &buffer) < 0)
{
fprintf(stderr, "ioctl VIDIOC_DQBUF failed: %s\n", strerror(errno));
close(videoFd);
return EXIT_FAILURE;
}
// 停止采集
ioctl(videoFd, VIDIOC_STREAMOFF, &bufType);
// 创建一个PPM文件
FILE* ppmFptr = fopen("output.ppm", "wb");
if(ppmFptr == NULL)
{
fprintf(stderr, "%s\n", strerror(errno));
close(videoFd);
return EXIT_FAILURE;
}
// 写入PPM header
fprintf(ppmFptr, "P3\n%d %d\n255\n", videoFormat.fmt.pix.width, videoFormat.fmt.pix.height);
// 读取像素,写入PPM文件
for(size_t i = 0; i+3 < buffer.length; i+=4)
{
uint8_t Y1 = ((uint8_t*)bufPtr)[i];
uint8_t U = ((uint8_t*)bufPtr)[i+1];
uint8_t Y2 = ((uint8_t*)bufPtr)[i+2];
uint8_t V = ((uint8_t*)bufPtr)[i+3];
uint8_t B = 1.164 * (Y1 - 16) + 2.018 * (U - 128);
uint8_t G = 1.164 * (Y1 - 16) - 0.391 * (U - 128) - 0.813 * (V - 128);
uint8_t R = 1.164 * (Y1 - 16) + 1.596 * (V - 128);
fprintf(ppmFptr, "%u %u %u ", R, G, B);
B = 1.164 * (Y2 - 16) + 2.018 * (U - 128);
G = 1.164 * (Y2 - 16) - 0.391 * (U - 128) - 0.813 * (V - 128);
R = 1.164 * (Y2 - 16) + 1.596 * (V - 128);
fprintf(ppmFptr, "%u %u %u ", R, G, B);
}
close(videoFd);
fclose(ppmFptr);
return EXIT_SUCCESS;
}