iOS将audio从一台iOS设备stream式传输到另一台

我从设备的iTunes库中获取一首歌曲,并将其推送到AVAsset中:

- (void)mediaPicker: (MPMediaPickerController *)mediaPicker didPickMediaItems:(MPMediaItemCollection *)mediaItemCollection { NSArray *arr = mediaItemCollection.items; MPMediaItem *song = [arr objectAtIndex:0]; NSData *songData = [NSData dataWithContentsOfURL:[song valueForProperty:MPMediaItemPropertyAssetURL]]; } 

然后我有这个游戏中心接收数据的方法:

 - (void)match:(GKMatch *)match didReceiveData:(NSData *)data fromPlayer:(NSString *)playerID 

我很难找出如何通过GameCenter发送AVAsset,然后让它在接收设备上播放。

我已阅读: http : //developer.apple.com/library/ios/#documentation/MusicAudio/Reference/AudioStreamReference/Reference/reference.html#//apple_ref/doc/uid/TP40006162

http://developer.apple.com/library/ios/#documentation/AudioVideo/Conceptual/MultimediaPG/UsingAudio/UsingAudio.html#//apple_ref/doc/uid/TP40009767-CH2-SW5

http://developer.apple.com/library/mac/#documentation/AVFoundation/Reference/AVAudioPlayerClassReference/Reference/Reference.html

http://developer.apple.com/library/mac/#documentation/MusicAudio/Conceptual/AudioQueueProgrammingGuide/Introduction/Introduction.html

我只是迷路了。 信息超载。

我已经实现了可爱与爱的audiostream代码,但我不知道如何把我通过GameCenter接收到的NSData,并推到他的代码。 http://cocoawithlove.com/2008/09/streaming-and-playing-live-mp3-stream.html

有人可以帮我解决这个问题吗? 因此,我需要帮助的部分是将歌曲数据分解成数据包(或者它的工作原理),然后迭代这些数据包并通过游戏包发送,然后parsing数据,因为它接收到的设备是PLAY it AS它进来。

您需要查看的API是“audio队列服务” 。

对,这里是对你需要做的基本的概述。

播放audio时,您将设置队列或服务。 该队列将要求一些audio数据。 那么,当它发挥了所有的后面,它会要求更多。 这一直持续下去,直到您停止队列,或者没有更多的数据可以播放。

iOS中两个主要的低层API是audio单元和audio队列。 更低的层次,我的意思是一个比说“只播放这个mp3”或者其他任何东西都更加基本的API。

我的经验是,audio单元的延迟较低,但audio队列更适合stream式传输audio。 所以,我觉得后者是更好的select。

你需要做的一个关键部分是缓冲。 这意味着要充分加载数据,以便在播放时不会出现间隙。 您可能想要通过最初加载更大量的数据来处理这个问题。 然后你在前面玩。 你将有一个足够大的内存缓冲区,同时在后台线程上接收更多的数据。

我会推荐学习的样本项目是SpeakHere 。 特别看看SpeakHereController.mmAQPlayer.mm类。

控制器处理诸如启动和停止AQPlayerAQPlayer代表一个AudioQueue 。 仔细看看AQPlayer::AQBufferCallback 。 这是队列需要更多数据时调用的callback方法。

您需要确保队列数据的设置以及您收到的数据格式完全匹配。 检查通道数量(单声道或立体声?),帧数,整数或浮点数和采样率。 如果有任何不匹配的东西,当您在相应的缓冲区中工作时,您将得到EXC_BAD_ACCESS错误,否则您将得到白噪声,或者 – 在采样率错误的情况下 – 听起来放慢或加速的audio向上。

请注意SpeakHere运行两个audio队列; 一个用于录音,一个用于回放。 所有的audio材料使用数据缓冲区。 所以你总是传递指向缓冲区的指针。 所以,例如在播放过程中,您将有一个有20秒audio的内存缓冲区。 也许每秒钟你的callback将被队列调用,本质上是说“请给我另一个值得的数据”。 你可以把它看作是一个回放头,通过你的数据来请求更多的信息。

我们来仔细看看。 与SpeakHere不同,你将在内存缓冲区中工作,而不是将audio写出到临时文件。

请注意,如果您正在处理大量数据,则在iOS设备上,您将别无select,只能将大部分数据保留在磁盘上。 特别是如果用户可以重放audio,倒带等,你需要把它放在一个地方!

无论如何,假设AQPlayer将从内存中读取,我们需要改变它如下。

首先,在AQPlayer.h保存数据的AQPlayer.h

 void SetAudioBuffer(float *inAudioBuffer) { mMyAudioBuffer = inAudioBuffer; } 

你已经有了一个NSData对象的数据,所以你可以传入从[myData bytes]调用返回的指针。

什么将这些数据提供给audio队列? 这是在AQPlayer设置的callback方法:

 void AQPlayer::AQBufferCallback(void * inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inCompleteAQBuffer) 

我们用来将部分数据添加到audio队列的方法是AudioQueueEnqueueBuffer

 AudioQueueEnqueueBuffer(inAQ, inCompleteAQBuffer, 0, NULL); 

inAQ是我们的callback收到的队列的引用。 inCompleteAQBuffer是指向audio队列缓冲区的指针。

那么如何让你的数据 – 通过调用你的NSData对象的bytes方法返回的指针 – 进入audio队列缓冲区inCompleteAQBuffer

使用memcpy

 memcpy(inCompleteAQBuffer->mAudioData, THIS->mMyAudioBuffer + (THIS->mMyPlayBufferPosition / sizeof(float)), numBytesToCopy); 

您还需要设置缓冲区大小:

  inCompleteAQBuffer->mAudioDataByteSize = numBytesToCopy; 

numBytesToCopy始终是相同的, 除非您即将用完数据。 例如,如果你的缓冲区是2秒钟的audio数据,你有9秒的时间来回放,那么对于前四个callback你将会传递2秒的时间。 对于最后的callback,你只剩下1秒钟的数据。 numBytesToCopy必须反映这一点。

  // Calculate how many bytes are remaining? It could be less than a normal buffer // size. For example, if the buffer size is 0.5 seconds and recording stopped // halfway through that. In which case, we copy across only the recorded bytes // and we don't enqueue any more buffers. SInt64 numRemainingBytes = THIS->mPlayBufferEndPosition - THIS->mPlayBufferPosition; SInt64 numBytesToCopy = numRemainingBytes < THIS->mBufferByteSize ? numRemainingBytes : THIS->mBufferByteSize; 

最后,我们推进播放头。 在我们的callback中,我们给队列发送了一些数据。 下次我们得到callback会发生什么? 我们不想再次提供相同的数据。 除非你正在做一些时髦的DJ循环的东西!

所以我们推进了头,这基本上只是一个指向我们的audio缓冲区的指针。 指针在logging中像针一样穿过缓冲区:

  SELF->mPlayBufferPosition += numBytesToCopy; 

而已! 还有一些其他的逻辑,但你可以通过在SpeakHere中学习完整的callback方法来获得。

我必须强调几点。 首先,不要复制和粘贴我的代码上面。 绝对要确保你明白你在做什么。 毫无疑问,你会遇到问题,你需要了解发生了什么。

其次,确保audio格式是相同的,甚至更好地理解audio格式。 录制audio中的“ audio队列服务编程指南”中介绍了这一点。 请看清单2-8指定一个audio队列的audio数据格式

理解你拥有最原始的数据单元,无论是整数还是浮点数都是至关重要的。 单声道或立体声你有一个或两个频道在一个框架。 它定义了该帧中有多less个整数或浮点数。 然后你有每个数据包的帧(可能是1)。 您的采样率决定了您每秒有多less个数据包。

这些都覆盖在文档中。 只要确保一切都匹配,否则你会有一些奇怪的声音!

祝你好运!

你的目的是什么? 和你如何调用[匹配sendDataToAllPlayers:…]? 它决定如何从收到的数据中获取AVAsset。

有提到的步骤:

  1. 从mediaPicker获取NSData音乐的NSData
  2. 创buildAVAsset(你真的这么做吗?)
  3. 发送给GKPlayers?
  4. 从gamecenter接收NSData
  5. 如果你是第二个,请取回AVAsset。
  6. 使用AVPlayer:playerWithPlayItem来获取AVPlayer并播放

如果你正在使用自己的方法进行一些编码和传输,比如使用AVAssetReader来获取原始数据并发送,那么只需要做相反的处理。