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