DOUAudioStreamer是一个基于Core Audio的流式音频播放器,其中的DOUAudioEventLoop通过kqueue来控制音频的各种状态。
kqueue简介(详情请看官方manual)
kqueue的功能类似epoll,多用于后台多个socket连接时的I/O复用,注册感兴趣的event,把描述符链表交给内核,然后就等待。一旦有某个或多个事件发生,内核就把 一个只包含有发生了事件的描述符的链表通知给进程,由此避免了每次函数返回的时候都要去遍历整个链表(相较与select和poll)。尽管对于只打开了几个描述符的进程而言这点改进算不得什么,但对于那些打开了几千个文件描述符的程序来说,这种性能改进就相当显著了。
在DOUAudioStreamer中kqueue系统调用生成一个之关联的唯一的描述符,通过kevent来注册和监听音频播放的各种状态的变化,以及变化后(即感兴趣的event发生后)的各种处理。
typedef NS_ENUM(uint64_t, event_type) { event_play, //播放 event_pause, //暂停 event_stop, //停止 event_seek, //手动选择播放位置 event_streamer_changed, //更换了streamer event_provider_events, event_finalizing, //dealloc中发送event,正在释放 #if TARGET_OS_IPHONE event_interruption_begin, event_interruption_end, event_old_device_unavailable, //最后一步 #endif /* TARGET_OS_IPHONE */ event_first = event_play, #if TARGET_OS_IPHONE event_last = event_old_device_unavailable, #else /* TARGET_OS_IPHONE */ event_last = event_finalizing, #endif /* TARGET_OS_IPHONE */ event_timeout };
init中初始化过程中的_setupAudioSession(也可以[AVAudioSession sharedInstance] 来设置)
AudioSessionInitialize(NULL, NULL, audio_session_interruption_listener, (__bridge void *)self); //注册被其他应用打断,或其他应用音频播放停止后的处理
//设置MediaPlayback属性可在后台播放声音,也可以避免一些不插耳机时无声的问题 UInt32 audioCategory = kAudioSessionCategory_MediaPlayback; AudioSessionSetProperty(kAudioSessionProperty_AudioCategory, sizeof(audioCategory), &audioCategory); AudioSessionAddPropertyListener(kAudioSessionProperty_AudioRouteChange, audio_route_change_listener, (__bridge void *)self); AudioSessionSetActive(TRUE);
kevent相关的操作
/**
* 官方manual中
The EV_SET() macro is provided for ease of initializing a kevent structure.
EVFILT_USER
Establishes a user event identified by ident which is not associated with any kernel mechanism but is triggered by user level code.
*
*
**/
//相当于register各种感兴趣事件
- (void)_enableEvents { for (uint64_t event = event_first; event <= event_last; ++event) { struct kevent kev;
/**从event_play到event_old_device_unavailable,监听 EV_ADD--添加event,EV_ENABLE--事件被触发后允许返回,EV_CLEAR--返回事件后,重新设置event的状态 **/
EV_SET(&kev, event, EVFILT_USER, EV_ADD | EV_ENABLE | EV_CLEAR, 0, 0, NULL); kevent(_kq, &kev, 1, NULL, 0, NULL); } } - (void)_sendEvent:(event_type)event { [self _sendEvent:event userData:NULL]; } - (void)_sendEvent:(event_type)event userData:(void *)userData { struct kevent kev;
//EVFILT_USER通过NOTE_TRIGGER来触发 EV_SET(&kev, event, EVFILT_USER, 0, NOTE_TRIGGER, 0, userData); kevent(_kq, &kev, 1, NULL, 0, NULL); } - (event_type)_waitForEvent { return [self _waitForEventWithTimeout:NSUIntegerMax]; }
//kevent监听着感兴趣的事件,一有可用事件就返回事件类型 - (event_type)_waitForEventWithTimeout:(NSUInteger)timeout { struct timespec _ts; struct timespec *ts = NULL; if (timeout != NSUIntegerMax) { ts = &_ts; ts->tv_sec = timeout / 1000; ts->tv_nsec = (timeout % 1000) * 1000; } while (1) { struct kevent kev; int n = kevent(_kq, NULL, 0, &kev, 1, ts); if (n > 0) { if (kev.filter == EVFILT_USER && kev.ident >= event_first && kev.ident <= event_last) { _lastKQUserData = kev.udata; return kev.ident; } } else { break; } } return event_timeout; }
最后 _waitForEventWithTimeout / _waitForEvent 被 event_loop_main线程方法中的_eventLoop不断调用,通过_waitForEvent返回的event_type来继续在_handleEvent中处理各种状态需要的操作;
另外_handleEvent中用的是if-else,switch语句是不是性能更好一点,。