iOS流音频从一个iOS设备到另一个

时间:2011-12-02 14:04:58

标签: iphone objective-c ios ipad 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]];
}

然后我有这个Game Center接收数据的方法:

- (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

我迷失了。信息超载。

我已经实现了Cocoa With Love的音频流代码,但我无法弄清楚如何通过GameCenter获取我收到的NSData并将其推送到他的代码中。 http://cocoawithlove.com/2008/09/streaming-and-playing-live-mp3-stream.html

有人可以帮我解决这个问题吗?因此,我需要帮助的部分只是简单地将歌曲数据分解成数据包(或者它可以工作),然后迭代这些数据包并通过游戏套件发送它,然后解析它在接收设备上传入的数据作为播放它它进来了。

2 个答案:

答案 0 :(得分:31)

您需要查看的API是"Audio Queue Services"

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

播放音频时,您可以设置队列或服务。该队列将要求一些音频数据。然后,当它播放了所有这些后,它会要求更多。这种情况一直持续到您停止队列,或者没有更多数据要播放。

iOS中的两个主要低级API是Audio Unit和Audio Queue。从较低的层面来说,我的意思是一个API,比说"更回报这个mp3"或者其他什么。

我的经验是音频单元的延迟较低,但音频队列更适合流式传输音频。所以,我认为后者是一个更好的选择。

您需要做的一件事的关键部分是缓冲。这意味着充分加载数据,以便播放中没有间隙。您可能希望通过最初加载大量数据来处理此问题。那你就是在前面。您在内存中有足够大的缓冲区,同时在后台线程上接收更多数据。

我建议密切关注的示例项目是SpeakHere。特别要查看类SpeakHereController.mmAQPlayer.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。

提到的步骤:

  1. 从mediaPicker获取音乐的NSData
  2. 创建AVAsset(你真的这么做吗?)
  3. 发送给GKPlayers?
  4. 从gamecenter收到NSData
  5. 如果你是2岁,请回到AVAsset。
  6. 使用AVPlayer:playerWithPlayItem获取AVPlayer并播放
  7. 如果你在自己的方法上进行一些编码和传输,比如使用AVAssetReader来获取原始数据和发送,那么只需反向进行。