StreamingKit - ShenYj/ShenYj.github.io GitHub Wiki

StreamingKit

StreamingKit 目前已经停止更新了,我对音频这方面接触的时间并不长,在进行简单的搜索了解后,个人觉得它是目前最稳定最能满足我需求的一个开源库,能够在我这方面知识还薄弱的情况下,快速满足实现我的需求。

经过了一段时间后,我们有了这样的一个需求

  1. 录音不限制时长、实时获取 PCM 原始文件上报
  2. 播放数据流

在现有条件下, StreamingKit 已然满足不了需求了,所以才开始有了音频开发的进一步学习的必要,在学习前,先简单了解下 StreamingKit

关于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 的同时,把自己设置成了属性 STKDataSourcedelegate

      这一层代理只要调用STKAudioPlayerplay。。。去播放某条音频时,创建任务的时候会立即被设置完毕

      网络音频实际是先通过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;
    }
    
⚠️ **GitHub.com Fallback** ⚠️