StreamingKit - ShenYj/ShenYj.github.io GitHub Wiki
StreamingKit
目前已经停止更新了,我对音频这方面接触的时间并不长,在进行简单的搜索了解后,个人觉得它是目前最稳定最能满足我需求的一个开源库,能够在我这方面知识还薄弱的情况下,快速满足实现我的需求。
经过了一段时间后,我们有了这样的一个需求
- 录音不限制时长、实时获取 PCM 原始文件上报
- 播放数据流
在现有条件下, StreamingKit
已然满足不了需求了,所以才开始有了音频开发的进一步学习的必要,在学习前,先简单了解下 StreamingKit
STKHTTPDataSource -> STKCoreFoundationDataSource -> STKDataSource
STKLocalFileDataSource -> STKCoreFoundationDataSource -> STKDataSource
STKAutoRecoveringHTTPDataSource -> STKDataSourceWrapper -> STKDataSource
网络音频实际创建的资源类型是被 STKAutoRecoveringHTTPDataSource
包装了一层的 STKHTTPDataSource
-
先实例化 dataSource
retval = [[STKAutoRecoveringHTTPDataSource alloc] initWithHTTPDataSource:[[STKHTTPDataSource alloc] initWithURL:url]];
-
请求响应码 200, 再取一次
content-Type
if (self.httpStatusCode == 200) { if (seekStart == 0) { id value = [httpHeaders objectForKey:@"Content-Length"] ?: [httpHeaders objectForKey:@"content-length"]; fileLength = (SInt64)[value longLongValue]; } NSString* contentType = [httpHeaders objectForKey:@"Content-Type"] ?: [httpHeaders objectForKey:@"content-type"] ; AudioFileTypeID typeIdFromMimeType = [STKHTTPDataSource audioFileTypeHintFromMimeType:contentType]; if (typeIdFromMimeType != 0) { audioFileTypeHint = typeIdFromMimeType; } }
-
设置delegate
除了使用
STKAudioPlayer
时设置的一个STKAudioPlayerDelegate
外,内部还存在一个STKDataSourceDelegate
,并且有两层设置, 接下来以播放网络音频为例-
第一层
STKDataSource
作为基类包含一个遵循了STKDataSourceDelegate
协议的delegate
, 基类当中并无任何实质上的功能
STKDataSourceWrapper
做了一层包装, 直接通过一个参数STKDataSource
完成实例化, 持有这个STKDataSource
的同时,把自己设置成了属性STKDataSource
的delegate
这一层代理只要调用
STKAudioPlayer
的play。。。
去播放某条音频时,创建任务的时候会立即被设置完毕网络音频实际是先通过URI创建了一个
STKHTTPDataSource
对象,在作为构造函数的参数创建了一个STKAutoRecoveringHTTPDataSource
对象 -
第二层
STKAudioPlayer
有个属性upcomingQueue
, 当开启一个播放任务的时候会创建一个STKAutoRecoveringHTTPDataSource
对象,并来到-(void) setDataSource:(STKDataSource*)dataSourceIn withQueueItemId:(NSObject*)queueItemId
方法中在这里会通过刚刚创建出来的
STKAutoRecoveringHTTPDataSource
对象在创建一个STKQueueEntry
对象,存入到upcomingQueue
队列内, 并将状态标记为STKAudioPlayerInternalStatePendingNext
在唤起线程后会调用
-(BOOL) processRunloop
通过匹配状态调用[self setCurrentlyReadingEntry:entry andStartPlaying:YES];
, 继续向下调用到-(void) setCurrentlyReadingEntry:(STKQueueEntry*)entry andStartPlaying:(BOOL)startPlaying clearQueue:(BOOL)clearQueue
在这里
currentlyReadingEntry.dataSource.delegate = self;
即将STKAutoRecoveringHTTPDataSource
对象的delegate
设置成了STKAudioPlayer
这一层的代理会延迟一点,在开始播放时,创建好任务需要标记状态唤醒线程任务后,从队列中取出刚刚创建好的任务再设置
总结:
播放一条音频任务时,几个对象间的关系
- 代理关系:
STKHTTPDataSource
--delegate->STKAutoRecoveringHTTPDataSource
--delegate->STKAudioPlayer
- 持有关系:
-
STKAudioPlayer 持有的 upcomingQueue 队列内包含着持有 STKAutoRecoveringHTTPDataSource 的 STKQueueEntry
等于STKAudioPlayer 持有的 STKAutoRecoveringHTTPDataSource
-
STKAutoRecoveringHTTPDataSource
持有STKHTTPDataSource
,直接通过属性的形式持有
-
-
-
获取音频数据
最开始我没有看到下载歌曲文件实时处理的地方,后来才发现,
StreamingKit
使用的是CFNetwork
框架在实例化 player 的时候, 最后开辟了一个线程
[self createPlaybackThread];
调用播放器的
-(void) play:(NSString*)urlString
方法时会唤醒线程,并最后调用到STKHTTPDataSource
的-(void) openForSeek:(BOOL)forSeek
方法里面这里创建了一个
CFHTTPMessageRef
请求,然后通过调用函数CFReadStreamCreateForHTTPRequest
并传递刚刚创建的请求来创建一个CFReadStream
对象。 最后,用CFReadStreamOpen
打开读取流在
STKCoreFoundationDataSource
中定义了一个回调函数, 在读取到流数据的时候就会被触发static void ReadStreamCallbackProc(CFReadStreamRef stream, CFStreamEventType eventType, void* inClientInfo)
满足一定条件时进入
-(void) dataAvailable
方法,因为实例化的时候实际创建类型是STKAutoRecoveringHTTPDataSource
, 所以当请求成功后满足一定条件会进入到STKAutoRecoveringHTTPDataSource
的-(void) dataSourceDataAvailable:(STKDataSource*)dataSource
方法内
本地音频时只需要创建一个 STKLocalFileDataSource
对象
-
本地音频取后缀
+(AudioFileTypeID) audioFileTypeHintFromFileExtension:(NSString*)fileExtension { static dispatch_once_t onceToken; static NSDictionary* fileTypesByFileExtensions; dispatch_once(&onceToken, ^ { fileTypesByFileExtensions = @{ @"mp3": @(kAudioFileMP3Type), @"wav": @(kAudioFileWAVEType), @"aifc": @(kAudioFileAIFCType), @"aiff": @(kAudioFileAIFFType), @"m4a": @(kAudioFileM4AType), @"mp4": @(kAudioFileMPEG4Type), @"caf": @(kAudioFileCAFType), @"aac": @(kAudioFileAAC_ADTSType), @"ac3": @(kAudioFileAC3Type), @"3gp": @(kAudioFile3GPType) }; }); NSNumber* number = [fileTypesByFileExtensions objectForKey:fileExtension]; if (!number) { return 0; } return (AudioFileTypeID)number.intValue; }