iPhone TV Out Tutorial

**UPDATE** Sadly this does not work anymore, not since iOS 4.0 removed the API entries. However, iOS 4.3 AirPlay becomes available to third party applications, which may be able to be used to provide similar functionality. Another option would be to stream the screens to a server over TCP and have it render them.

The iPhone has a TV Out capability.  This can be seen by hooking up the iPhone to a TV using the TV Out cable from Apple.

http://support.apple.com/kb/HT1454

However TV Out is only active from within certain applications, for example, YouTube and Photos.

The code presented here allows your application to output the contents of it’s display to the TV whilst it is running, through use of some undocumented APIs.

Warning: Submitting an application for review with this code compiled into it will get your application rejected.  However, it is still useful for demonstration purposes in debug builds of your application.

Ok, so onto the code.  First we add a new Objective-C class based on NSObject and name it MPTVOutWindow. Having done that, replace the contents of the two generated files MPTVOutWindow.h and MPTVOutWindow.m with the following versions:

MPTVOutWindow.h

// MPTVOutWindow.h
//
// Copyright 2009 Stinkbot LLC. All rights reserved.
// Copyright 2009 RedSky IT. All rights reserved.
//
#ifdef __INCLUDE_TVOUT_SUPPORT

@class UIImage;

@interface MPTVOutWindow : UIWindow
- (id)initWithVideoView:(id)fp8;
+ (void) startTvOut:(int)fps;
+ (void) stopTvOut;
@end

@interface MPTVOutWindow (extended)
+ (void) createAndMakeKeyWindow;
+ (void) outputScreenToTv;
@end

@interface MPVideoView : UIView
- (id)initWithFrame:(struct CGRect)fp8;
@end

#endif

MPTVOutWindow.m

// MPTVOutWindow.m
//
// Copyright 2009 Stinkbot LLC. All rights reserved.
// Copyright 2009 RedSky IT. All rights reserved.
//
#ifdef __INCLUDE_TVOUT_SUPPORT

#ifndef DEBUG
#  error trying to include TV Out code in a non-debug build
#endif

#import "MPTVOutWindow.h"
#import <QuartzCore/QuartzCore.h>

#define degreesToRadian(x) (M_PI * (x) / 180.0)

static MPTVOutWindow *tvout = nil;
static UIImageView *imageView = nil;
static BOOL done = NO;
static int FPS = 10;

CGAffineTransform tnormal;

@implementation MPTVOutWindow (extended)

+ (void) createAndMakeKeyWindow {
  MPVideoView *vidView = [[MPVideoView alloc] initWithFrame:CGRectZero];
  tvout = [[MPTVOutWindow alloc] initWithVideoView:vidView];  

  imageView = [[UIImageView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
  tnormal = imageView.transform;
  imageView.center = vidView.center;

  [vidView addSubview:imageView];
  [imageView release];
  [vidView release];

  [tvout makeKeyAndVisible];
}

+ (void) outputScreenToTv {
  CGImageRef UIGetScreenImage();
  CGImageRef screen = UIGetScreenImage();		// Auto-release (supposedly)
  if (screen) {
    UIImage *image = [[UIImage alloc] initWithCGImage:screen];
    CFRelease(screen);		// BUT! client crashes after a few fraees if we DONT release this
    if ([UIApplication sharedApplication].statusBarOrientation == UIInterfaceOrientationPortrait) {
      imageView.transform = tnormal;
    } else if ([UIApplication sharedApplication].statusBarOrientation == UIInterfaceOrientationLandscapeLeft) {
      imageView.transform = CGAffineTransformRotate(tnormal, degreesToRadian(90));;
    } else if ([UIApplication sharedApplication].statusBarOrientation == UIInterfaceOrientationLandscapeRight) {
      imageView.transform = CGAffineTransformRotate(tnormal, degreesToRadian(-90));
    } else if ([UIApplication sharedApplication].statusBarOrientation == UIInterfaceOrientationPortraitUpsideDown) {
      imageView.transform = CGAffineTransformRotate(tnormal, degreesToRadian(180));
    }
    imageView.image = image;
    [image release];
  }
}

+ (void) updateLoop
{
  done = NO;
  while (!done) {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    [self performSelectorOnMainThread:@selector(outputScreenToTv) withObject:nil waitUntilDone:NO];
    [NSThread sleepForTimeInterval: (1.0/FPS) ];
    [pool drain];
  }
}

+ (void) startTvOut:(int)fps {
  FPS = fps;
  [NSThread detachNewThreadSelector:@selector(updateLoop) toTarget:self withObject:nil];
}

+ (void) stopTvOut {
  done = YES;
}

@end

@implementation MPVideoView (extended)
- (void) addSubview: (UIView *) aView
{
  [super addSubview:aView];
}
@end 

#endif /* __INCLUDE_TVOUT_SUPPORT */

Also add the following frameworks to your Frameworks folder in Xcode (Right Click, Add… Existing Frameworks… they are in the Frameworks sub-folder):

QuartzCode.framework
MediaPlayer.framework

To activate the TV Out code, edit your AppName_Prefix.pch file found in the Other Sources folder and add the following code:

AppName_Prefix.pch

// Only include TV Output in debug builds.
#ifdef DEBUG
#  define __INCLUDE_TVOUT_SUPPORT
#endif

Also, double click the Project Root node to open up the Project Info window and scroll to the bottom of the Build tab. Use the little icon at the bottom left to Add a user defined setting named OTHER_CFLAGS with a value of -DDEBUG, then close the Project Info window.

Open AppNameAppDelegate.m and add the following just below other #imports.

AppNameAppDelegate.m

#import "MPTVOutWindow.h"

and in the same file, in the applicationDidFinishLaunching: method add the following code near the top:

applicationDidFinishLaunching:

#ifdef __INCLUDE_TVOUT_SUPPORT
  [MPTVOutWindow createAndMakeKeyWindow];
  [MPTVOutWindow startTvOut:15];  // Set frame rate to 15 FPS
  [window makeKeyAndVisible]; // Not required if window not yet created
#endif

Finally, to stop TV output in your code, use

applicationWillTerminate:

// See if TV Out should be switched on (only valid in debug mode)
#ifdef __INCLUDE_TVOUT_SUPPORT
  [MPTVOutWindow stopTvOut];
#endif

And that’s all there is to it. Build your application and install it on your device then hook up your device to a TV or Projector and run your application. The application screen will be visible on the TV and you can still interact with your application as normal. Rotate your device and the TV will show the rotated output.

The picture quality is not brilliant, and the TV output lags very slightly behind the device (probably because the frame display code is executed in the main thread and has to wait until other tasks on the main thread have finished). It is also not possible to achieve high frame rates. All that said, for demonstration purposes these issues are probably not going to be a major concern.

Credits

This code is adapted from code by Rob Terrell available here:

http://gist.github.com/119128

About austinfrance

Technical Developer @ RedSky IT / Explorer Software
This entry was posted in Development, iPhone and tagged , . Bookmark the permalink.

Leave a comment