It’s been a few days since I’ve posted, and I haven’t managed to get a lot of work done in the last couple of day either, between SkyCon on Saturday, doing something weird to my neck Friday night (and the pain’s not gone away since), and managing to catch a cold yesterday.
On Thursday or Friday I spent a bit of time figuring out how to copy the framebuffer into local memory, encode into some compressed format, and get that data off over the network. Turns out it’s pretty easy to do with Ogre!
// This pixel format is totally arbitrary Ogre::PixelFormat pf = Ogre::PF_R8G8B8; // Pixelbox to receive the framebuffer contents. 480x320x1 // (i.e., 2D box the size of the iPod's screen) Ogre::PixelBox pb( 480, 320, 1, pf, pbBuffer); // Copy the framebuffer into the pixelbox mTexture->copyContentsToMemory( pb, Ogre::RenderTarget::FB_AUTO ); // Create a new image and give it the pixelbox as its content Ogre::Image im; im.loadDynamicImage( pbBuffer, 480, 320, pf ); // Encode the image as a PNG and grab a pointer to the resulting datastream Ogre::DataStreamPtr ds = im.encode( "png" ); // create a buffer on the stack and copy the stream in there unsigned char* b = new unsigned char[ds->size()]; ds->read( b, ds->size() ); // tell the IO subsystem to send it off over the network YkdIOSubsystem::getSingleton().sendImage( b, (int)ds->size() );
Obviously there’s a whole lot of copying going on here that doesn’t need to be done, but it works as a basic prototype (albeit with about 4.5 fps.. the copyContentsToMemory() on the RenderTexture take half the time and the sending of the image blocks right now).
On the receiving side (the iPod), the image was coming through ok (as verified by the second, third and fourth bytes of the received data being ‘PNG’). However, the UIImage wasn’t updating at all. When I’d press the home button, I’d briefly see the image, but not while actually running.
After some poking around various forums, I saw a mention of trouble updating UIKit components from other threads. I had a single NSThread doing recvfrom() on a BSD socket, then making changes to the UI based on the messages it received. This morning I got rid of the NSThread and set up a CFSocketRef to run in the main NSRunLoop instead, and it works! When data is available to be read, a callback is fired and in that callback I can do whatever I want to the UI.
Here’s how the CFSocketRef is set up:
// boring old BSD socket int serverSocket = socket( AF_INET, SOCK_DGRAM, IPPROTO_UDP ); // This is vital so the callback can get a reference to "self" CFSocketContext socketCtxt = { 0, self, NULL, NULL, NULL }; // set up a CFSocketRef using the existing socket created above with socket() CFSocketRef sockRef = CFSocketCreateWithNative ( kCFAllocatorDefault, serverSocket, kCFSocketReadCallBack, &recvData, &socketCtxt ); // Add the CFSocketRef to the main run loop CFRunLoopSourceRef runLoopSource = CFSocketCreateRunLoopSource( kCFAllocatorDefault, sockRef, 0 ); CFRunLoopAddSource( CFRunLoopGetCurrent(), runLoopSource, kCFRunLoopDefaultMode ); // Clean up... CFRelease( runLoopSource ); CFRelease( sockRef );
Here’s what the callback (&recvData above) looks like:
static void recvData(CFSocketRef socket, CFSocketCallBackType type, CFDataRef address, const void *data, void *info) { if ( type != kCFSocketReadCallBack ) return; // send a message to "info": "self" from the context originally used in CFSocketCreateWithNative() // YkRemote's recvDataFromSocket: will perform recvfrom(), and hopefully it won't need to block. [(YkRemote*)info recvDataFromSocket:socket]; }
It’s my first time hopping back and forth between an Objective-C method and a C function like that, but it worked!
With this set-up, all UIKit calls are in the main thread and my problems have all disappeared.

