如何从一台iOS设备到另一台iOS设备无线传输AVAsset音频?

时间:2013-02-04 13:38:57

标签: ios core-audio audio-streaming audioqueue avassetreader

我正在制作类似于从iPod库传输音频,通过网络或蓝牙发送数据,以及使用音频队列播放的内容。

感谢您question and code。帮助我很多。

我有两个问题。

  1. 我应该从一台设备发送到另一台设备? CMSampleBufferRef? AudioBuffer? MDATA? AudioQueueBuffer?包?我不知道。

  2. 当应用程序完成播放时,它崩溃了,我收到错误(-12733)。我只是想知道如何处理错误而不是让它崩溃。 (检查OSState?发生错误时,停止吗?)

    错误:无法读取样本数据(-12733)

1 个答案:

答案 0 :(得分:6)

我将首先回答您的第二个问题 - 不要等待应用程序崩溃,您可以通过检查您正在阅读的CMSampleBufferRef中可用的样本数量来停止从轨道中提取音频;例如(此代码也将包含在我的答案的下半部分):

CMSampleBufferRef sample;
sample = [readerOutput copyNextSampleBuffer];

CMItemCount numSamples = CMSampleBufferGetNumSamples(sample);

if (!sample || (numSamples == 0)) {
  // handle end of audio track here
  return;
}

关于你的第一个问题,它取决于你正在抓取的音频类型 - 它可能是PCM(非压缩)或VBR(压缩)格式。我甚至不打算解决PCM部分问题,因为通过网络将未压缩的音频数据从一部手机发送到另一部手机根本不聪明 - 这会不必要地昂贵并且会阻塞你的网络带宽。所以我们留下了VBR数据。为此,您必须发送从样本中提取的AudioBufferAudioStreamPacketDescription的内容。但话说回来,最好用代码来解释我在说什么:

-(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];
}

我在上面的代码中使用的一些方法是您不必担心的方法,例如向每个数据包添加标头(我创建了自己的协议,您可以创建自己的协议)。有关详细信息,请参阅this教程。

- (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];
}

备注:

  1. 我在这里没有涉及很多网络细节(例如,在接收数据部分。我使用了很多自定义对象而没有扩展它们的定义)。我没有,因为解释所有这些都超出了SO的一个答案的范围。但是,您可以关注Ray Wenderlich的excellent tutorial。他花时间解释网络原则,我上面使用的架构几乎是他的逐字逐句。然而,有一个陷阱(见下一点)

  2. 根据您的项目,GKSession可能不合适(特别是如果您的项目是实时的,或者如果您需要同时连接的设备超过2-3个),它有很多limitations。您将不得不深入挖掘并直接使用Bonjour。 iPhone cool projects有一个很好的快速章节,它提供了使用Bonjour服务的一个很好的例子。它并不像听起来那么可怕(并且苹果文档在这个问题上有点霸道)。

  3. 我注意到你使用GCD进行多线程处理。同样,如果您正在处理实时,那么您不希望使用高级框架来为您做繁重的工作(GCD就是其中之一)。有关此主题的更多信息,请阅读此优秀article。另请阅读justin回答评论中我和this之间的长时间讨论。

  4. 您可能需要查看iOS 6中引入的MTAudioProcessingTap。在处理AVAssets时,它可以为您节省一些麻烦。我没有测试过这个东西。在我完成所有工作后,它就出来了。

  5. 最后但并非最不重要的,您可能需要查看learning core audio本书。这是关于这一主题的广泛认可的参考文献。我记得当你提出这个问题时,你会像你一样陷入困境。核心音频很重,需要时间才能接收。所以只会给你指针。你将不得不花时间自己吸收材料然后你会弄清楚事情是如何运作的。祝你好运!