iOS在线音频流播放

作者: 落影loyinglin | 来源:发表于2017-04-03 11:09 被阅读5186次

    前言

    这是一篇关于在线音频播放的文章,参考自苹果OS X的demo。
    在移植到iOS后,可以通过iphone播放Mac上面的音频,实现在线播放音频的功能。
    本文可以学习到socket编程AudioFileStream转换音频流AudioQueue播放音频信号量的使用

    正文

    demo有两个工程,分别是serversclient
    servers是OS X的应用,作为服务端,负责发送音频流数据;
    client是iOS的应用,作为客户端,负责接收音频流数据;
    音频数据通过AudioFileStream转换后,调用AudioQueue进行播放,中间会用到信号量进行等待和同步。

    1、socket编程

    bind方法用于绑定接口,然后用listen监听tcp连接请求,accept用于接受tcp连接;
    fopen打开音频文件,fread读取音频数据,send对建立的连接发送音频流;

    对已经失效的socket,send两次数据就会触发SIGPIPE信号,默认的处理是关闭进程。

    // 打开文件
    FILE* file = fopen([[[NSBundle mainBundle] pathForResource:@"chenli" ofType:@"mp3"] UTF8String], "r");
    // 创建socket
    int listener_socket = socket(AF_INET, SOCK_STREAM, 0);
    // 绑定socket
    bind(listener_socket, (struct sockaddr*)&server_sockaddr, sizeof(server_sockaddr));
    // 监听tcp连接
    listen(listener_socket, 4);
    // 接收tcp连接,注意!这里并不是三次握手。
    int connection_socket = accept(listener_socket, (struct sockaddr*)&client_sockaddr, &client_sockaddr_size);
    // 读取文件
    size_t bytesRead = fread(buf, 1, 32768, file);
    // 发送音频流
    ssize_t bytesSent = send(connection_socket, buf, bytesRead, 0);
    // 关闭socket
    close(connection_socket);
    
    2、AudioQueue播放音频

    AudioQueue的播放时,需要先给audioBuffer填充数据,并把audioBuffer放入AudioQueue,然后通知AudioQueue开始播放;
    AudioQueue从已经填充的audioBuffer里面开始播放数据,实时把播放完毕的audioBuffer回调给业务层,业务继续填充播放完毕的audioBuffer,重复流程直到音频播放完毕。

    前文使用AudioToolbox播放AAC有对AudioQueue更详细的介绍以及更简化的demo。

    配置AudioQueue

    // 添加AudioQueue的回调函数和添加参数,MyAudioQueueOutputCallback是播完结束的回调
    AudioQueueNewOutput(&asbd, MyAudioQueueOutputCallback, myData, NULL, NULL, 0, &audioQueue);
    // AudioBuffer分配buffer
    AudioQueueAllocateBuffer(audioQueue, kAQBufSize, &audioQueueBuffer[i]);
    // 添加AudioQueue的属性监听
    AudioQueueAddPropertyListener(audioQueue, kAudioQueueProperty_IsRunning, MyAudioQueueIsRunningCallback, myData);
    

    开始播放

    // 开始AudioQueue播放
    AudioQueueStart(myData->audioQueue, NULL);
    // 向AudioQueue传入buffer
    AudioQueueEnqueueBuffer(audioQueue, fillBuf, (UInt32)myData->packetsFilled, packetDescs);
    

    播放结束

    // 传入最后的音频数据后需要调用,否则buffer里面的数据可能会影响下次播放
    AudioQueueFlush(audioQueue);
    // 如果需要停止播放,可以调用这个函数,第二个参数表示同步/异步
    AudioQueueStop(audioQueue, false);
    // 播放完毕,销毁队列
    AudioQueueDispose(audioQueue, false);
    
    3、互斥锁

    普通锁
    pthread_mutex_lock(mutex) 加锁,可能会阻塞;
    pthread_mutex_unlock(mutex) 解锁;

    条件锁(pthread_cond_wait)
    调用pthread_cond_wait时,条件不成立则阻塞,直到条件成立;
    调用pthread_cond_wait前,要先调用pthread_mutex_lock(mutex)加锁,pthread_cond_wait会在调用结束解锁mutex;
    pthread_cond_wait条件满足后(pthread_cond_signal被调用),会对mutex加锁,当我们执行完程序时需要对mutex解锁;

    调用pthread_cond_wait时,为了防止并发放入阻塞队列,所以需要提前对mutex加锁;

    申请条件锁

    pthread_mutex_lock(&mutex);
    pthread_cond_wait(&cond, &mutex);
    pthread_mutex_unlock(&mutex);
    

    释放条件锁

    pthread_mutex_lock(&mutex);
    pthread_cond_signal(&cond);
    pthread_mutex_unlock(&mutex);
    
    4、AudioFileStream转换音频流

    AudioFileStream可以用来读取音频流信息和分离音频帧,与之类似的API簇还有AudioFile和ExtAudioFile。
    AudioFileStream可以用在线音频流,也可以使用本地文件。

    // 打开一个音频流转换器,需要设置AudioFileStream_PropertyListenerProc 和 AudioFileStream_PacketsProc 回调函数;
    AudioFileStreamOpen(myData, MyPropertyListenerProc, MyPacketsProc, kAudioFileAAC_ADTSType, &audioFileStream);
    
    // AudioFileStreamParseBytes 解析数据,会调用之前设置好的AudioFileStream_PropertyListenerProc 和 AudioFileStream_PacketsProc 回调函数;
    AudioFileStreamParseBytes(myData->audioFileStream, (UInt32)bytesRecvd, buf, 0);
    
    // 获取特定的属性
    AudioFileStreamGetProperty(inAudioFileStream, kAudioFileStreamProperty_DataFormat, &asbdSize, &asbd);
    
    // 关闭音频流
    AudioFileStreamClose(audioFileStream);
    

    附录

    demo中用到用到的一些方法:
    AudioFileStreamParseBytes 解析数据,会调用之前设置好的AudioFileStream_PropertyListenerProcAudioFileStream_PacketsProc 回调函数;
    AudioFileStreamOpen 打开一个音频流转换器,需要设置AudioFileStream_PropertyListenerProcAudioFileStream_PacketsProc 回调函数;
    MyPropertyListenerProc 音频属性回调函数;
    MyPacketsProc 数据回调函数;
    MyEnqueueBuffer 把buffer里面的数据传入AudioQueue;
    WaitForFreeBuffer 当前所有buffer已经占用满,等待AudioQueue播放完释放buffer;
    MyAudioQueueOutputCallback AudioQueue释放buffer的回调函数;
    MyAudioQueueIsRunningCallback AudioQueue是否在播放的回调函数;
    MyConnectSocket 建立socket链接
    demo 的代码地址在这里传送门

    demo的打开方式:
    server是服务端,运行在OS X
    有binary和app两种方式

    • binary需要编译完之后,找到二进制所在的目录,在其目录下放对应的音频文件;
    • app打开,保持运行;

    client是客户端,运行在iOS

    • 1、在getHostName处需要修改为OS X的ip地址;
    • 2、iOS和OS X需要处于同一局域网;
    • 3、clietn未播放完结束,会导致server关闭;

    总结

    这个demo很有意思:用到很多知识点,而且很简单,非常适合学习。
    最近越来越忙,如果有问题可以评论或者简信联系,尽量清楚点描述问题还有问题的上下文。

    前文系列,或许会有兴趣。
    使用VideoToolbox硬编码H.264
    使用VideoToolbox硬解码H.264
    使用AudioToolbox编码AAC
    使用AudioToolbox播放AAC
    HLS点播实现(H.264和AAC码流)
    HLS推流的实现(iOS和OS X系统)

    相关文章

      网友评论

      • 8ab85fad1fa1:修改了Host 电脑 和 手机都在一个WIFI下 但一直 can't connect
        退休老干部:你应该是没网络权限 socket打开失败了。。。启动的时候先随便请求一个http链接
      • 新地球说着一口陌生腔调:AudioQueue 这个类播放有什么优势呢?
      • Mo丿小冷:你好,我直接播放接收到的ACC裸数据应该怎么处理呢?
      • Liusr:lin,有没有遇到收到的数据不及时,audioqueue会停止播放的问题,现象就是回调仍在执行,但就是没声了
        angle_杰:遇到了同样的问题,楼上解决了吗
        Liusr:@cctao89 写个定时器重写初始化。
        静音数据,我尝试过,线程的回调依然凌乱。而且,我也不会构造真正的静音数据
        d611ec22c786:这个问题解决了么?我现在也碰到这个问题了
      • 冰冻的西瓜:看过了你所有的有关opengl的文章,因为基础不牢,所以很多看不懂,请问一下,iOS想实现一个柱面投影的效果,应该从哪里入手研究比较好,谢谢~~~
      • zhao1zhihui:好厉害
      • 5a7c2642d9ee:有效,:+1:
      • littlewish:收藏回去试试demo,谢谢分析
      • da27c260cc85:想问一下,苹果的开发者网站上的iOS的demo路径,您知道么?
        落影loyinglin:在develop.apple.com上面

      本文标题:iOS在线音频流播放

      本文链接:https://www.haomeiwen.com/subject/yrlgottx.html