我从设备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]];
}
然后我有这个Game Center接收数据的方法:
- (void)match:(GKMatch *)match didReceiveData:(NSData *)data fromPlayer:(NSString *)playerID
我在解决如何通过GameCenter发送这个AVAsset然后让它在接收设备上播放时遇到了很多麻烦。
我迷失了。信息超载。
我已经实现了Cocoa With Love的音频流代码,但我无法弄清楚如何通过GameCenter获取我收到的NSData并将其推送到他的代码中。 http://cocoawithlove.com/2008/09/streaming-and-playing-live-mp3-stream.html
有人可以帮我解决这个问题吗?因此,我需要帮助的部分只是简单地将歌曲数据分解成数据包(或者它可以工作),然后迭代这些数据包并通过游戏套件发送它,然后解析它在接收设备上传入的数据作为播放它它进来了。
答案 0 :(得分:31)
您需要查看的API是"Audio Queue Services"。
是的,这里是您需要做的基本概述。
播放音频时,您可以设置队列或服务。该队列将要求一些音频数据。然后,当它播放了所有这些后,它会要求更多。这种情况一直持续到您停止队列,或者没有更多数据要播放。
iOS中的两个主要低级API是Audio Unit和Audio Queue。从较低的层面来说,我的意思是一个API,比说"更回报这个mp3"或者其他什么。
我的经验是音频单元的延迟较低,但音频队列更适合流式传输音频。所以,我认为后者是一个更好的选择。
您需要做的一件事的关键部分是缓冲。这意味着充分加载数据,以便播放中没有间隙。您可能希望通过最初加载大量数据来处理此问题。那你就是在前面。您在内存中有足够大的缓冲区,同时在后台线程上接收更多数据。
我建议密切关注的示例项目是SpeakHere。特别要查看类SpeakHereController.mm
和AQPlayer.mm
。
控制器处理诸如启动和停止AQPlayer
之类的事情。 AQPlayer
代表AudioQueue
。仔细查看AQPlayer::AQBufferCallback
。这是队列需要更多数据时调用的回调方法。
您需要确保队列数据的设置以及您收到的数据格式完全匹配。检查诸如通道数量(单声道或立体声?),帧数,整数或浮点数和采样率等内容。如果有任何事情无法匹配,那么当您在相应的缓冲区中工作时,您会遇到EXC_BAD_ACCESS
错误,或者您会发出白噪声,或者 - 如果出现错误采样率 - 听起来减慢或加速的音频。
请注意,SpeakHere运行两个音频队列;一个用于录制,一个用于播放。所有音频内容都使用数据缓冲区。因此,您始终将循环指针传递给缓冲区。因此,例如在播放期间,您将说一个具有20秒音频的内存缓冲区。也许每一秒你的回调都会被队列调用,基本上是说"请给我另外一些价值的数据"。您可以将其视为回放头,在您的数据中移动,请求更多信息。
让我们更详细地看一下这个。与SpeakHere不同,您将在内存缓冲区中工作,而不是将音频写入临时文件。
请注意,如果您要处理大量数据,那么在iOS设备上,您将别无选择,只能在磁盘上保留大部分数据。特别是如果用户可以重放音频,倒回音频等,你需要把它放在某个地方!
无论如何,假设AQPlayer
将从内存中读取,我们需要按如下方式更改它。
首先,在某个地方保存数据,在AQPlayer.h
:
void SetAudioBuffer(float *inAudioBuffer) { mMyAudioBuffer = inAudioBuffer; }
您已在NSData
对象中拥有该数据,因此您只需将调用返回的指针传递给[myData bytes]
。
什么将数据提供给音频队列?这是AQPlayer
中设置的回拨方法:
void AQPlayer::AQBufferCallback(void * inUserData,
AudioQueueRef inAQ,
AudioQueueBufferRef inCompleteAQBuffer)
我们用于将部分数据添加到音频队列的方法是AudioQueueEnqueueBuffer
:
AudioQueueEnqueueBuffer(inAQ, inCompleteAQBuffer, 0, NULL);
inAQ
是我们的回调收到的队列引用。
inCompleteAQBuffer
是指向音频队列缓冲区的指针。
那么如何通过调用NSData对象上的bytes
方法将数据 - 即通过调用NSData对象上的inCompleteAQBuffer
方法返回的指针 - 放入音频队列缓冲区memcpy
?
使用memcpy(inCompleteAQBuffer->mAudioData, THIS->mMyAudioBuffer + (THIS->mMyPlayBufferPosition / sizeof(float)), numBytesToCopy);
:
inCompleteAQBuffer->mAudioDataByteSize = numBytesToCopy;
您还需要设置缓冲区大小:
numBytesToCopy
numBytesToCopy
始终是相同的,除非您即将耗尽数据。例如,如果您的缓冲区的音频数据为2秒,并且您有9秒钟的播放时间,那么对于前四个回调,您将获得2秒钟的值。对于最终回调,您只剩下1秒的数据。 // 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;
必须反映出来。
SELF->mPlayBufferPosition += numBytesToCopy;
最后,我们推进播放头。在我们的回调中,我们为队列提供了一些数据。下次我们收到回调会发生什么?我们不想再次提供相同的数据。除非你正在做一些时髦的dj循环,否则不会这样做!
所以我们推进头部,这基本上只是指向音频缓冲区的指针。指针像记录中的指针一样在缓冲区中移动:
{{1}}
那就是它!还有一些其他的逻辑,但你可以通过研究SpeakHere中的完整回调方法来实现。
我必须强调几点。首先,不要只复制并粘贴我上面的代码。绝对要确保你明白自己在做什么。毫无疑问,你会遇到问题,你需要了解发生了什么。
其次,确保音频格式相同,甚至更好地理解音频格式。 Audio Queue Services Programming Guide in Recording Audio中介绍了这一点。查看清单2-8指定音频队列的音频数据格式。
了解您拥有最原始的数据单元(整数或浮点数)至关重要。单声道或立体声,帧中有一个或两个声道。这定义了该帧中有多少整数或浮点数。然后你有每个数据包的帧(可能是1)。您的采样率决定了您每秒有多少数据包。
文档全部涵盖了这些内容。只要确保一切都匹配,否则你会有一些非常奇怪的声音!
祝你好运!答案 1 :(得分:0)
你的目的是什么?你怎么称呼[match sendDataToAllPlayers:...]?它决定如何从收到的数据中恢复AVAsset。
提到的步骤:
如果你在自己的方法上进行一些编码和传输,比如使用AVAssetReader来获取原始数据和发送,那么只需反向进行。