USB音视频设备热拔插程序处理

本文主要探讨USB音频和视频设备的热拔插的采集程序实现,采集程序要实现当用户拔掉usb设备程序不挂掉,用户重新插上usb设备后能及时恢复采集流程。

接口及参数说明

  1. 开启
    int netlink_check_start(thread_func cam_proc, thread_func mic_proc);

    • 参数 cam_proc对应视频读取线程实现函数,具体请看下面usb视频采集线程示例部分。该函数中必须实现如下流程:
    1. 摄像头设备存在与否判断,若不存在则一直处这个判断循环。

    2. 当第一条满足后就是正常摄像头设备的打开-读取-关闭流程。

    3. 当不需要监测USB摄像头时将该参数传NULL就行。

    • 参数mic_proc对应USB麦克风读取线程实现函数,具体请看下面usb麦克风线程示例部分。和视频的处理一样必须实现如下流程:
    1. USB麦克风设备存在与否判断,若不存在则一直处在监测循环。

    2. 当第一条满足后就是正常USB麦克风设备的打开-读取-关闭流程。

    3. 当不需要监测USB麦克风时将该参数传NULL就行。

  2. 关闭
    int netlink_check_stop();
    本函数会释放上面函数开启的资源,并等待内部线程结束才返回。

原理和思路

  1. 确保每次USB设备拔出程序能及时关闭释放该设备资源
    为了监测USB设备的拔插,在应用中读取Netlink套接字来获取设备是插入add还是拔出remove,由于USB设备有很多种,应用中只关心包含videopcm类型的数据,通过这两个数据的过滤就能确定摄像头和USB音频设备的插入和拔出监测。目前,只使用了拔出监测,插入监测没有添加任何逻辑。当监测到关心的USB设备拔出时应用程序将设备及时关闭并退出线程循环函数,因为每次拔出USB设备后对应资源被释放,这样就保证了每次插入的摄像头是/dev/vdieo0,同理麦克风就是hw:0,0

  2. 确保每次USB设备插上程序能自动打开该设备

    程序使用了一个定时器,每800ms触发一次监测摄像头读取线程或者usb麦克风读取线程是否活着,若是监测到线程退出了就重新创建相应线程。

    监测线程使用的是pthread_kill(tid,0)函数,通过比较返回值判断USB读取线程是否已经退出了。

  3. 由于程序内部使用了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;
}
// 一些常量及自定义的变量
#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;

}

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;
}

posted @ 2024-07-05 22:17  lyphotoes  阅读(59)  评论(0)    收藏  举报