Search code examples
iospacket

Can't copy with NSData:dataWithBytesNoCopy


I'm trying to simply copy the contents of a packet in the form of an NSData object without copying the header information using NSData:dataWithBytesNoCopy

-(void)saveData:(NSData *)data
{    
    const char* theBytes = [data bytes];    
    self.bodyData = [NSData dataWithBytesNoCopy:(char *)(theBytes + PACKET_HEADER_SIZE)
                                         length:[data length] - PACKET_HEADER_SIZE 
                                   freeWhenDone:NO];    
}

this is what data looks like:

534e4150 00000000 0071534e 41500000 00000071 d5000008 5200e612 a180014a c4e947b9 35060225 bee8e729 00c21511 68a6dc52 c7177902 8343ea38 70bbc102 8e0fa9c4 0bbc8141 a1f2870e 1790d2a7 339487d4 e0c09de0 81487d4e 385e430f 9439c814 87ca1c38 5e1855aa 77667d4e 6643a9c7 0bbc1057 f0000710 4e1dcd12 801381be 06088e78 060c87e0 6088e780 60c8fc0c 111de018 78fc0c11 1de01832 3f030ccd f260c8fc 0c111de0 1879fc75 b3f93fcf e38ccde0 1879f819 6cfe4c3c fc0cb67e 01cf3f07 7ff880c8 a0301810 0a060201 00400000 c0f2b122 7e27910a f91843fc 408adf21 19891fc6 21722089 7fe26848 164550a9 ffc683f1 70d267ff cf253987 e71ffffe 468cc40c 545b7ffd 05da00ab ffee9fd2 f39fffff 710e1644 c8220000 84106102 000607ce 6811242a f84c9818 21be1de1 4ff10145 abe25a18 843fdfc3 7201885b a0fc7fe0 dd50c5c2 790c021c 57ff0b

(the first 10 bytes constitute the header, I simply want to copy the body of the packet).

after running the operation.. self.bodyData looks like this:

534e4150 00000000 0071d500 00085200 e612a180 014ac4e9 47b93506 0225bee8 e72900c2 151168a6 dc52c717 79028343 ea3870bb c1028e0f a9c40bbc 8141a1f2 870e1790 d2a73394 87d4e0c0 9de08148 7d4e385e 430f9439 c81487ca 1c385e18 55aa7766 7d4e6643 a9c70bbc 1057f000 07104e1d cd128013 81be0608 8e78060c 87e06088 e78060c8 fc0c111d e01878fc 0c111de0 18323f03 0ccdf260 c8fc0c11 1de01879 fc75b3f9 3fcfe38c cde01879 f8196cfe 4c3cfc0c b67e01cf 3f077ff8 80c8a030 18100a06 02010040 0000c0f2 b1227e27 910af918 43fc408a df211989 1fc62172 20897fe2 68481645 50a9ffc6 83f170d2 67ffcf25 3987e71f fffe468c c40c545b 7ffd05da 00abffee 9fd2f39f ffff710e 1644c822 00008410 61020006 07ce6811 242af84c 981821be 1de14ff1 0145abe2 5a18843f dfc37201 885ba0fc 7fe0dd50 c5c2790c 021c57ff 0bfa0098 11032e83 6a8213ff e32a1f28 a4873447 2282203f ffe4049a 311cd22c 6239a458 abffffe6 26489899 1a9aa4b3 536fffff fd8c4d98 d5264523 c6a93189 ffffffff fffec792 64526353 ec6a9322 91e3c7d0 04

notice how the first 10 bytes remain the same.. whereas the remaning bytes change for some reason.. I have no idea why.. suggestions?

Update: just in case you were wondering where the data is coming from, I'm actually getting an .mp3 file and slicing it up using audio streaming services then sending it over bluetooth/wifi using GKSession.. the following is the code:

static const int maxBufferSize = 0x10000;   // limit maximum size to 64K
static const int minBufferSize = 0x4000;    // limit minimum size to 16K

NSUInteger bufferByteSize = maxBufferSize;
UInt32 numPacketsToRead = 0;
NSUInteger headerByteSize = 10;             // header takes 10 bytes


if (maxBufferSize < audioFile.maxPacketSize) bufferByteSize = audioFile.maxPacketSize; 
if (bufferByteSize < minBufferSize) bufferByteSize = minBufferSize;

numPacketsToRead = bufferByteSize/audioFile.maxPacketSize;

BOOL isVBR = YES;

AudioStreamPacketDescription    packetDescriptions[numPacketsToRead];    
SInt64 inStartingPacket = 0;


do {          

    void *myBuffer = [[[NSMutableData alloc] initWithCapacity:bufferByteSize] mutableBytes];

    NSMutableData *packetData = [[NSMutableData alloc] initWithCapacity:bufferByteSize + headerByteSize];
    UInt32 outNumBytes = 0;
    OSStatus result = 0;

    result = AudioFileReadPackets (
                                   audioFile.fileID,
                                   NO,
                                   &outNumBytes,
                                   isVBR ? packetDescriptions : 0,
                                   inStartingPacket,
                                   &numPacketsToRead,
                                   myBuffer
                                   );

    // add header info         
    [packetData rw_appendInt32:'SNAP'];   // 0x534E4150
    [packetData rw_appendInt32:0];
    [packetData rw_appendInt16:PacketTypeMusic];

    NSData *NSpacketData = packetData;

    [packetData appendBytes:myBuffer length:outNumBytes];

    NSError *error;

     NSLog(@"about to send out bytes");
    if (![_session sendDataToAllPeers:NSpacketData withDataMode:GKSendDataReliable error:&error]) 
    {
        NSLog(@"Error sending data to clients: %@", error);
    }        
} while (numPacketsToRead < audioFile.packetsCount); 

Solution

  • Using dataBytesWithNoCopy means that the NSData object will just keep a pointer to the data you give it. In your case that data you are giving it is managed by another data object (the one passed in as a parameter).

    If you give raw data to NSData, the NSData object is really only valid while that data is valid.

    Now, that same method has another argument, freeWhenDone which tells the NSData object whether or not it owns the data. If you allocate the buffer, and you want NSData to free it when done, then pass YES. Again, in your case, since your data is owned by another NSData object, you tell it NO.

    However, later, when you access your iVar bodyData its data will still be pointing back into the data from that NSData object you gave it. If that object has changed, or been deallocated, then your pointers will be pointing into some piece of unknown memory.

    If you want to create a copy of the data, then you have to use the standard initializers.

    If you want to use noCopy then you have to guarantee that the original data lives at least as long as the NSData using it.

    EDIT

    If you want to do it the "standard" way...

    char const *bytes = data.bytes;
    self.bodyData = [NSData dataWithBytes:bytes + PACKET_HEADER_SIZE
                                   length:data.length - PACKET_HEADER_SIZE];    
    

    Now, this creates a NSData with a copy of the bytes. Note that it will allocate space for the bytes you are giving it, and then it copies those bytes.

    The "no copy" version just "uses" the data pointer you give it - and if you tell it to do so, it will free the buffer when it is done. This is used for performance reasons, but if you use it you must make sure that the pointer you give it lives at least as long as the data object itself.

    In C, if you want a buffer, you have to allocate and copy it yourself...

    size_t length = data.length - PACKET_HEADER_SIZE;
    void * buffer = malloc(length);
    memcpy(buffer, ((char const *)data.bytes) + PACKET_HEADER_SIZE, length);