DOUAudioStreamer 中kqueue的应用

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语句是不是性能更好一点,。

链接

kqueue官方manual

原文地址:https://www.cnblogs.com/edisongz/p/6942253.html