如何将AVAssetaudio从一个iOS设备无线传输到另一个?
我正在做一些像从iPod库stream式传输audio,通过networking或蓝牙发送数据,并使用audio队列播放。
感谢这个问题和代码 。 帮助我很多。
我有两个问题。
-
我应该从一台设备发送到另一台设备? CMSampleBufferRef? AudioBuffer? MDATA? AudioQueueBuffer? 包? 我不知道。
-
当应用程序完成播放,它坠毁,我得到错误(-12733)。 我只是想知道如何处理错误,而不是让它崩溃。 (检查OSState?发生错误时,停止它?)
错误:无法读取示例数据(-12733)
我将首先回答第二个问题 – 不要等到应用程序崩溃,您可以通过检查正在读取的CMSampleBufferRef中可用的样本数来停止从轨道中拖动audio; 例如(这个代码也将包括在我的答案的后半部分):
CMSampleBufferRef sample; sample = [readerOutput copyNextSampleBuffer]; CMItemCount numSamples = CMSampleBufferGetNumSamples(sample); if (!sample || (numSamples == 0)) { // handle end of audio track here return; }
关于你的第一个问题,它取决于你正在抓取的audiotypes – 它可能是枯萎的PCM(非压缩)或VBR(压缩)格式。 我甚至不打算解决PCM部分问题,因为通过networking将未压缩的audio数据从一部电话发送到另一部电话根本就不聪明 – 这是不必要的昂贵,并会阻塞您的networking带宽。 所以我们留下了VBR数据。 为此,您必须发送从示例中提取的AudioBuffer
和AudioStreamPacketDescription
的内容。 但是,再次,最好解释一下我所说的代码:
-(void)broadcastSample { [broadcastLock lock]; CMSampleBufferRef sample; sample = [readerOutput copyNextSampleBuffer]; CMItemCount numSamples = CMSampleBufferGetNumSamples(sample); if (!sample || (numSamples == 0)) { Packet *packet = [Packet packetWithType:PacketTypeEndOfSong]; packet.sendReliably = NO; [self sendPacketToAllClients:packet]; [sampleBroadcastTimer invalidate]; return; } NSLog(@"SERVER: going through sample loop"); Boolean isBufferDataReady = CMSampleBufferDataIsReady(sample); CMBlockBufferRef CMBuffer = CMSampleBufferGetDataBuffer( sample ); AudioBufferList audioBufferList; CheckError(CMSampleBufferGetAudioBufferListWithRetainedBlockBuffer( sample, NULL, &audioBufferList, sizeof(audioBufferList), NULL, NULL, kCMSampleBufferFlag_AudioBufferList_Assure16ByteAlignment, &CMBuffer ), "could not read sample data"); const AudioStreamPacketDescription * inPacketDescriptions; size_t packetDescriptionsSizeOut; size_t inNumberPackets; CheckError(CMSampleBufferGetAudioStreamPacketDescriptionsPtr(sample, &inPacketDescriptions, &packetDescriptionsSizeOut), "could not read sample packet descriptions"); inNumberPackets = packetDescriptionsSizeOut/sizeof(AudioStreamPacketDescription); AudioBuffer audioBuffer = audioBufferList.mBuffers[0]; for (int i = 0; i < inNumberPackets; ++i) { NSLog(@"going through packets loop"); SInt64 dataOffset = inPacketDescriptions[i].mStartOffset; UInt32 dataSize = inPacketDescriptions[i].mDataByteSize; size_t packetSpaceRemaining = MAX_PACKET_SIZE - packetBytesFilled - packetDescriptionsBytesFilled; size_t packetDescrSpaceRemaining = MAX_PACKET_DESCRIPTIONS_SIZE - packetDescriptionsBytesFilled; if ((packetSpaceRemaining < (dataSize + AUDIO_STREAM_PACK_DESC_SIZE)) || (packetDescrSpaceRemaining < AUDIO_STREAM_PACK_DESC_SIZE)) { if (![self encapsulateAndShipPacket:packet packetDescriptions:packetDescriptions packetID:assetOnAirID]) break; } memcpy((char*)packet + packetBytesFilled, (const char*)(audioBuffer.mData + dataOffset), dataSize); memcpy((char*)packetDescriptions + packetDescriptionsBytesFilled, [self encapsulatePacketDescription:inPacketDescriptions[i] mStartOffset:packetBytesFilled ], AUDIO_STREAM_PACK_DESC_SIZE); packetBytesFilled += dataSize; packetDescriptionsBytesFilled += AUDIO_STREAM_PACK_DESC_SIZE; // if this is the last packet, then ship it if (i == (inNumberPackets - 1)) { NSLog(@"woooah! this is the last packet (%d).. so we will ship it!", i); if (![self encapsulateAndShipPacket:packet packetDescriptions:packetDescriptions packetID:assetOnAirID]) break; } } [broadcastLock unlock]; }
我在上面的代码中使用的一些方法是你不需要担心的方法,比如为每个数据包添加头(我正在创build自己的协议,你可以创build自己的)。 有关更多信息,请参阅本教程。
- (BOOL)encapsulateAndShipPacket:(void *)source packetDescriptions:(void *)packetDescriptions packetID:(NSString *)packetID { // package Packet char * headerPacket = (char *)malloc(MAX_PACKET_SIZE + AUDIO_BUFFER_PACKET_HEADER_SIZE + packetDescriptionsBytesFilled); appendInt32(headerPacket, 'SNAP', 0); appendInt32(headerPacket,packetNumber, 4); appendInt16(headerPacket,PacketTypeAudioBuffer, 8); // we use this so that we can add int32s later UInt16 filler = 0x00; appendInt16(headerPacket,filler, 10); appendInt32(headerPacket, packetBytesFilled, 12); appendInt32(headerPacket, packetDescriptionsBytesFilled, 16); appendUTF8String(headerPacket, [packetID UTF8String], 20); int offset = AUDIO_BUFFER_PACKET_HEADER_SIZE; memcpy((char *)(headerPacket + offset), (char *)source, packetBytesFilled); offset += packetBytesFilled; memcpy((char *)(headerPacket + offset), (char *)packetDescriptions, packetDescriptionsBytesFilled); NSData *completePacket = [NSData dataWithBytes:headerPacket length: AUDIO_BUFFER_PACKET_HEADER_SIZE + packetBytesFilled + packetDescriptionsBytesFilled]; NSLog(@"sending packet number %lu to all peers", packetNumber); NSError *error; if (![_session sendDataToAllPeers:completePacket withDataMode:GKSendDataReliable error:&error]) { NSLog(@"Error sending data to clients: %@", error); } Packet *packet = [Packet packetWithData:completePacket]; // reset packet packetBytesFilled = 0; packetDescriptionsBytesFilled = 0; packetNumber++; free(headerPacket); // free(packet); free(packetDescriptions); return YES; } - (char *)encapsulatePacketDescription:(AudioStreamPacketDescription)inPacketDescription mStartOffset:(SInt64)mStartOffset { // take out 32bytes b/c for mStartOffset we are using a 32 bit integer, not 64 char * packetDescription = (char *)malloc(AUDIO_STREAM_PACK_DESC_SIZE); appendInt32(packetDescription, (UInt32)mStartOffset, 0); appendInt32(packetDescription, inPacketDescription.mVariableFramesInPacket, 4); appendInt32(packetDescription, inPacketDescription.mDataByteSize,8); return packetDescription; }
接收数据:
- (void)receiveData:(NSData *)data fromPeer:(NSString *)peerID inSession:(GKSession *)session context:(void *)context { Packet *packet = [Packet packetWithData:data]; if (packet == nil) { NSLog(@"Invalid packet: %@", data); return; } Player *player = [self playerWithPeerID:peerID]; if (player != nil) { player.receivedResponse = YES; // this is the new bit } else { Player *player = [[Player alloc] init]; player.peerID = peerID; [_players setObject:player forKey:player.peerID]; } if (self.isServer) { [Logger Log:@"SERVER: we just received packet"]; [self serverReceivedPacket:packet fromPlayer:player]; } else [self clientReceivedPacket:packet]; }
笔记:
-
我在这里没有涉及很多networking细节(即在接收数据部分,我使用了很多定制的对象而没有扩展它们的定义)。 我并不是因为解释所有这些超出了SO上的一个答案的范围。 不过,您可以按照Ray Wenderlich的优秀教程进行操作。 他花了他的时间来解释networking的原则,而我上面使用的架构几乎是从他那里逐字逐句的。 但是有一个捕获(见下一点)
-
根据您的项目,GKSession可能不适合(特别是如果您的项目是实时的,或者如果您需要超过2-3个设备同时连接),它有很多限制 。 您将不得不深入挖掘并直接使用Bonjour。 iPhone酷项目有一个很好的快速篇章,给出了使用Bonjour服务的一个很好的例子。 这听起来并不像听起来那么吓人(苹果的文档在这方面有点霸道)。
-
我注意到你使用GCD进行multithreading。 同样,如果你正在处理实时,那么你不希望使用先进的框架,为你做繁重的工作(GCD是其中之一)。 欲了解更多关于这个问题读这个优秀的文章 。 也请阅读我和贾斯汀之间在这个答案的评论中的长时间的讨论。
-
您可能希望查看iOS 6中引入的MTAudioProcessingTap 。它可以在处理AVAsset时为您节省一些麻烦。 我没有testing这个东西。 在我做完所有的工作之后,它就出来了。
-
最后但并非最不重要的,你可能想看看学习核心audio书籍。 这是一个广泛公认的这个问题的参考。 我记得当你问这个问题的时候,就像你一样卡住了。 核心audio是沉重的责任,需要时间沉入。所以只会给你指针。 你将不得不花时间吸收材料,然后你会弄清楚事情是如何运作的。 祝你好运!