USB音视频设备热拔插程序处理
本文主要探讨USB音频和视频设备的热拔插的采集程序实现,采集程序要实现当用户拔掉usb设备程序不挂掉,用户重新插上usb设备后能及时恢复采集流程。
接口及参数说明
-
开启
int netlink_check_start(thread_func cam_proc, thread_func mic_proc);- 参数
cam_proc对应视频读取线程实现函数,具体请看下面usb视频采集线程示例部分。该函数中必须实现如下流程:
-
摄像头设备存在与否判断,若不存在则一直处这个判断循环。
-
当第一条满足后就是正常摄像头设备的打开-读取-关闭流程。
-
当不需要监测USB摄像头时将该参数传
NULL就行。
- 参数
mic_proc对应USB麦克风读取线程实现函数,具体请看下面usb麦克风线程示例部分。和视频的处理一样必须实现如下流程:
-
USB麦克风设备存在与否判断,若不存在则一直处在监测循环。
-
当第一条满足后就是正常USB麦克风设备的打开-读取-关闭流程。
-
当不需要监测USB麦克风时将该参数传
NULL就行。
- 参数
-
关闭
int netlink_check_stop();
本函数会释放上面函数开启的资源,并等待内部线程结束才返回。
原理和思路
-
确保每次USB设备拔出程序能及时关闭释放该设备资源
为了监测USB设备的拔插,在应用中读取Netlink套接字来获取设备是插入add还是拔出remove,由于USB设备有很多种,应用中只关心包含video和pcm类型的数据,通过这两个数据的过滤就能确定摄像头和USB音频设备的插入和拔出监测。目前,只使用了拔出监测,插入监测没有添加任何逻辑。当监测到关心的USB设备拔出时应用程序将设备及时关闭并退出线程循环函数,因为每次拔出USB设备后对应资源被释放,这样就保证了每次插入的摄像头是/dev/vdieo0,同理麦克风就是hw:0,0。 -
确保每次USB设备插上程序能自动打开该设备
程序使用了一个定时器,每800ms触发一次监测摄像头读取线程或者usb麦克风读取线程是否活着,若是监测到线程退出了就重新创建相应线程。
监测线程使用的是
pthread_kill(tid,0)函数,通过比较返回值判断USB读取线程是否已经退出了。 -
由于程序内部使用了
epoll对USB设备进行异步读取,所以确保了拔出USB设备不会造成整个应用程序崩溃。
拔插USB设备的串口打印信息
/****
**** remove
usb 4-1: USB disconnect, device number 3
remove@/devices/soc/100b0000.ohci/usb4/4-1/4-1:1.0/sound/card0/pcmC0D0c
remove@/devices/soc/100b0000.ohci/usb4/4-1/4-1:1.0/sound/card0/controlC0
remove@/devices/soc/100b0000.ohci/usb4/4-1/4-1:1.0/sound/card0
remove@/devices/soc/100b0000.ohci/usb4/4-1/4-1:1.0
remove@/devices/soc/100b0000.ohci/usb4/4-1/4-1:1.2
remove@/devices/soc/100b0000.ohci/usb4/4-1/4-1:1.3/0003:1B3F:2100.0003/input/input1/event0
remove@/devices/soc/100b0000.ohci/usb4/4-1/4-1:1.3/0003:1B3F:2100.0003/input/input1
remove@/devices/soc/100b0000.ohci/usb4/4-1/4-1:1.3/0003:1B3F:2100.0003
remove@/devices/soc/100b0000.ohci/usb4/4-1/4-1:1.3
remove@/devices/soc/100b0000.ohci/usb4/4-1
remove@/devices/soc/100c0000.ehci/usb3/3-1/3-1:1.0/video4linux/video0
remove@/devices/soc/100c0000.ehci/usb3/3-1/3-1:1.0
remove@/devices/soc/100c0000.ehci/usb3/3-1/3-1:1.1
remove@/devices/soc/100c0000.ehci/usb3/3-1/3-1:1.2/0003:2E7E:0809.0004
remove@/devices/soc/100c0000.ehci/usb3/3-1/3-1:1.2
remove@/devices/soc/100c0000.ehci/usb3/3-1
**************
**** add
usb 3-1: new high-speed USB device number 6 using ehci-platform
add@/devices/soc/100c0000.ehci/usb3/3uvcvideo: Found UVC 1.00 device HD Camera (2e7e:0809)
-1
add@/devices/soc/100c0000.ehci/usb3/3-1/3-1:1.0
add@/devices/soc/100c0000.ehci/usb3/3-1/3-1:1.0/video4linux/video1
add@/dhid-generic 0003:2E7E:0809.0005: device has no listeners, quitting
evices/soc/100c0000.ehci/usb3/3-1/3-1:1.1
add@/devices/soc/100c0000.ehci/usb3/3-1/3-1:1.2
add@/devices/soc/100c0000.ehci/usb3/3-1/3-1:1.2/0003:2E7E:0809.0005
*/
代码片段
1. Netlink套切字读取解析函数
int netlink_parse(char *p)
{
char *bs,*es;
char *tbuf;
char *ptr;
tbuf = strdup(p);
if ((ptr = strchr(tbuf, '@'))) {
bs = tbuf;
*ptr= '\0';
//printf("bs:%s\n",bs);
}
if((ptr = strrchr(p, '/'))){
es = ptr + 1;
//printf("es:%s\n",es);
}
if (strncmp("add", bs, 3) == 0) {
if (strncmp("video", es, 5) == 0) {
printf("USBCAM arrived!\n");
}else if(strncmp("pcm", es, 3) == 0){
printf("USB MIC arrived!\n");
}else{
;
}
}else if(strncmp("remove", bs, 6) == 0){
if (strncmp("video", es, 5) == 0) {
printf("USBCAM left!\n");
//此处添加关闭设备释放资源逻辑
//usbcheck.cam_exit = 1;
}else if(strncmp("pcm", es, 3) == 0){
printf("USB MIC left!\n");
//此处添加关闭设备释放资源逻辑
//usbcheck.mic_exit =1;
}else{
;
}
}
if (tbuf) {
free(tbuf);
}
return 0;
}
2. 定时监测关心的线程是否已经退出,若退出就重新开启
void time_check_cb(void)
{
int kill_rc;
int ret;
if ((usbcheck.flag & USBCHECK_FLAG_CAM) && usbcheck.cam_thread) {
kill_rc = pthread_kill(usbcheck.tid_cam,0);
if(kill_rc == ESRCH){
printf("the Cam thread did not exists or already quit\n");
usbcheck.cam_exit = 0;
// 监测到采集线程退出了,重新创建采集线程,传递参数为线程结束标志
ret = pthread_create(&usbcheck.tid_cam, NULL, usbcheck.cam_thread, &usbcheck.cam_exit);
}else if(kill_rc == EINVAL){
printf("signal is invalid\n");
}else{
//printf("the specified thread is alive\n");
}
}
if(usbcheck.flag & USBCHECK_FLAG_MIC && usbcheck.mic_thread){
kill_rc = pthread_kill(usbcheck.tid_mic,0);
if(kill_rc == ESRCH){
printf("the Mic thread did not exists or already quit\n");
usbcheck.mic_exit = 0;
// 监测到采集线程退出了,重新创建采集线程,传递参数为线程结束标志
ret = pthread_create(&usbcheck.tid_mic, NULL, usbcheck.mic_thread, &usbcheck.mic_exit);
}else if(kill_rc == EINVAL){
printf("signal is invalid\n");
}else{
//printf("the specified thread is alive\n");
}
}
}
3. usb麦克风线程示例
/*
* 创建线程会将结束标志作为参数传递进线程函数
*/
void * mic_thread(void * arg)
{
// 结束标志参数
int *pexit = (int *) arg;
alsa_ai_t *ai = NULL;
// 1. 尝试打开usb音频设备直至成功,否则一直在这个循环里面,除非程序退出
while (!*pexit && (ai = alsa_ai_open(ALSA_DEV_NAME, 48000, 2)) == NULL ) {
fprintf(stderr, "Cannot identify '%s': %d, %s\n",
ALSA_DEV_NAME, errno, strerror(errno));
usleep(1000*500);
}
printf("Open Microphone device... \n");
// 2. 读取音频数据并处理的循环
while(!*pexit){
printf("Read Mic data every 800 ms!\n");
usleep(800*1000);
}
// 3. 收到程序线程退出,关闭设备释放资源
printf("Close Microphone device... \n");
alsa_ai_close(ai);
pthread_exit(NULL);
return (void *)NULL;
}
4. usb视频采集线程示例
/*
* 创建线程会将结束标志作为参数传递进线程函数
*/
void *cam_thread(void * arg)
{
struct uvc_ctx *uvc = NULL;
struct stat st;
// 结束标志参数
int *pexit = (int *) arg;
// 1. 尝试打开usb摄像头设备直至成功,否则一直在这个循环里面,除非程序退出
while (!*pexit && ((-1 == stat(CAM_DEV, &st)) || (!S_ISCHR(st.st_mode)))) {
fprintf(stderr, "Cannot identify '%s': %d, %s\n",
CAM_DEV, errno, strerror(errno));
usleep(1000*500);
}
printf("Open cam device... \n");
uvc = uvc_open(CAM_DEV);
// 2. 读取摄像头数据并处理的循环
while(!*pexit){
printf("Read cam data every 500 ms!\n");
usleep(500*1000);
}
// 3. 收到程序线程退出,关闭设备释放资源
printf("Close Video device... \n");
if (uvc) {
uvc_close(uvc);
}
pthread_exit(NULL);
return (void *)NULL;
}
5. netlink_check_start
// 一些常量及自定义的变量
#define MAX_EVENTS (10)
#define UEVENT_BUF_SIZE (2048)
#define USBCHECK_FLAG_CAM (1 << 0)
#define USBCHECK_FLAG_MIC (1 << 1)
typedef void* (*thread_func) (void*);
typedef struct _usbcheck {
struct epoll_event evt_nl;
struct epoll_event evt_timer;
int epollfd;
int csock;
int timer_fd;
int main_exit;
int mic_exit;
int cam_exit;
int flag;
struct itimerspec tick;
pthread_t tid_mic;
pthread_t tid_cam;
pthread_t tid_main;
thread_func mic_thread;
thread_func cam_thread;
} usbcheck_t;
usbcheck_t usbcheck;
void * main_thread(void *arg)
{
int nfds,i;
usbcheck_t *puc = (usbcheck_t *)arg;
struct epoll_event events[MAX_EVENTS];
if(puc == NULL) return (void *)NULL;
if(usbcheck.flag & USBCHECK_FLAG_CAM){
usbcheck.cam_exit = 0;
pthread_create(&usbcheck.tid_cam, NULL, usbcheck.cam_thread, (void *)&usbcheck.cam_exit);
}
if (usbcheck.flag & USBCHECK_FLAG_MIC) {
usbcheck.mic_exit = 0;
pthread_create(&usbcheck.tid_mic, NULL, usbcheck.mic_thread, (void *)&usbcheck.mic_exit);
}
while(!usbcheck.main_exit){
nfds=epoll_wait(usbcheck.epollfd, events, MAX_EVENTS, -1);
if (nfds == -1) {
perror("epoll_wait");
break;
}
for (i = 0; i < nfds; i++) {
if (events[i].data.fd == usbcheck.csock) {
int len;
char buffer[UEVENT_BUF_SIZE];
len = recv(usbcheck.csock, buffer, UEVENT_BUF_SIZE, 0);
if (len > 0) {
netlink_parse(buffer);
}
}else if (events[i].data.fd == usbcheck.timer_fd) {
uint64_t exp;
int len;
//printf("timer ticked to check pthread id, exp=%ld.\n",exp);
len = read(usbcheck.timer_fd, &exp, sizeof exp);
time_check_cb();
}else{
}
}
}
usbcheck.cam_exit = usbcheck.mic_exit = 1;
if (usbcheck.flag & USBCHECK_FLAG_CAM) {
pthread_join(usbcheck.tid_cam, NULL);
}
if (usbcheck.flag & USBCHECK_FLAG_MIC) {
pthread_join(usbcheck.tid_mic, NULL);
}
return (void *)NULL;
}
int netlink_check_start(thread_func cam_proc, thread_func mic_proc)
{
int ret;
int bufsize = 1024;
struct sockaddr_nl client;
usbcheck.csock = socket(AF_NETLINK, SOCK_RAW, NETLINK_KOBJECT_UEVENT);
memset(&client, 0, sizeof(client));
client.nl_family = AF_NETLINK;
client.nl_pid = getpid();
client.nl_groups = 1;
setsockopt(usbcheck.csock, SOL_SOCKET, SO_RCVBUF, &bufsize, sizeof(bufsize));
bind(usbcheck.csock, (struct sockaddr *)&client, sizeof(client));
usbcheck.epollfd = epoll_create1(0);
if (usbcheck.epollfd == -1) {
perror("epoll_create1");
goto _exit;
}
usbcheck.evt_nl.events = EPOLLIN;
usbcheck.evt_nl.data.fd = usbcheck.csock;
if (epoll_ctl(usbcheck.epollfd, EPOLL_CTL_ADD, usbcheck.csock, &usbcheck.evt_nl) == -1) {
perror("epoll_ctl");
goto _exit;
}
usbcheck.timer_fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK);
usbcheck.evt_timer.events = EPOLLIN;
usbcheck.evt_timer.data.fd = usbcheck.timer_fd;
if (epoll_ctl(usbcheck.epollfd, EPOLL_CTL_ADD, usbcheck.timer_fd, &usbcheck.evt_timer) == -1) {
perror("epoll_ctl timer");
goto _exit;
}
if(mic_proc != NULL){
usbcheck.mic_thread = mic_proc;
usbcheck.flag |= USBCHECK_FLAG_MIC;
}
if (cam_proc != NULL) {
usbcheck.cam_thread = cam_proc;
usbcheck.flag |= USBCHECK_FLAG_CAM;
}
usbcheck.cam_thread = cam_proc;
// start threads check timer
// 800 ms 定时器开启
usbcheck.tick.it_interval.tv_sec = 0;
usbcheck.tick.it_interval.tv_nsec = 800*1000*1000;
usbcheck.tick.it_value.tv_sec = 1;
usbcheck.tick.it_value.tv_nsec = 0;
ret = timerfd_settime(usbcheck.timer_fd, 0, &usbcheck.tick, NULL);
ret = pthread_create(&usbcheck.tid_main, NULL, main_thread, &usbcheck);
return 0;
_exit:
close(usbcheck.csock);
close(usbcheck.timer_fd);
close(usbcheck.epollfd);
return -1;
}
6. netlink_check_stop
int netlink_check_stop()
{
usbcheck.main_exit = 1;
pthread_join(usbcheck.tid_main,NULL);
close(usbcheck.csock);
close(usbcheck.timer_fd);
close(usbcheck.epollfd);
memset(&usbcheck, 0, sizeof usbcheck);
return 0;
}
7. 函数调用示例
/*main.c*/
int exit_flag = 0;
void signal_handler(int signo)
{
printf("signal %d(%s) received\n", signo, strsignal(signo));
exit_flag = 1;
}
int main(int argc, char ** argv)
{
//初始化信号处理函数
signal(SIGINT, signal_handler);
signal(SIGPIPE, signal_handler);
signal(SIGTERM, signal_handler);
//netlink_check_init(cam_thread, mic_thread);//开启视频和音频
netlink_check_start(cam_thread, NULL);// 只开启视频
while(!exit_flag){
usleep(100*1000);
}
netlink_check_stop();
return 0;
}

浙公网安备 33010602011771号