iOS在线音频流播放

前言

这是一篇关于在线音频播放的文章,参考自苹果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系统)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容