开源代码:http://code.google.com/p/upnpx/
upnpx是一个支持dlna协议的局域网多媒体共享与播放控制的第三方框架。
http://upnp.org/sdcps-and-certification/resources/sdks/
Good UPnP documentation is available at at the website http://upnp.org. The architecture is described in the following document UPNP Device Architecture Document
A UPnP session exist of several steps:
upnpx is an implementation of the UPnP Specifications and has API functions for the different steps. This tutorial give simple examples how to use the upnpx API in order to build your own UPnP Control Point.
This tutorial handles the 6 individual steps and starts at Step 0. Addressing.
{UPnP specifications: "Through addressing, devices and control points get a network address".}
Basically this means that your device has to get an IP address. Since the OS handles this task we are lucky and don't have to do much. Just be sure you actually are connected to the network and have an IP address before using upnpx.
Next Step 1. Discovery
{UPnP specifications: "Through discovery, control points find interesting device(s)."}
During Discovery we will search for any UPnP device on the local network. upnpx implements the SSDP protocol (Simple Service Discovery Protocol) and maintains an in-memory database of all devices on the network, it will automatically update this database when new devices are added or removed. Your code can read the database and get notifications when an update is done.
Check the project ./upnpx/projects/xcode3/upnpxdemo/upnpxdemo.xcodeproj to see an example of a functional ssdp implementation:
Basically, there are 3 steps to complete to implement SSDP:
1. Implement the Interface UPnPDBObserver:
RootViewController.h:
#import "UPnPDB.h" @interface RootViewController : UITableViewController <UPnPDBObserver>{ NSArray *mDevices; //BasicUPnPDevice* } //protocol UPnPDBObserver -(void)UPnPDBWillUpdate:(UPnPDB*)sender; -(void)UPnPDBUpdated:(UPnPDB*)sender;
2. Get a pointer to the discovery database via the UPnPManager, register yourself as an observer and tell the SSDP implementation to search for devices by calling searchSSDP:
RootViewController.m:
- (void)viewDidLoad { [super viewDidLoad]; // Uncomment the following line to display an Edit button in the navigation bar for this view controller. // self.navigationItem.rightBarButtonItem = self.editButtonItem; UPnPDB* db = [[UPnPManager GetInstance] DB]; mDevices = [db rootDevices]; //BasicUPnPDevice [mDevices retain]; [db addObserver:(UPnPDBObserver*)self]; //Optional; set User Agent [[[UPnPManager GetInstance] SSDP] setUserAgentProduct:@"upnpxdemo/1.0" andOS:@"OSX"]; //Search for UPnP Devices [[[UPnPManager GetInstance] SSDP] searchSSDP]; self.title = @"upnpx demo"; }
3. Implement the UPnPDBObserver callback functions:
//protocol UPnPDBObserver -(void)UPnPDBWillUpdate:(UPnPDB*)sender{ NSLog(@"UPnPDBWillUpdate %d", [mDevices count]); } -(void)UPnPDBUpdated:(UPnPDB*)sender{ NSLog(@"UPnPDBUpdated %d", [mDevices count]); }
The above code is all what is needed to search for UPnP devices on the Local Network. The NSArray mDevices contains a BasicUPnPDeviceInstance per found device. The upnpx stack will automatically keep track of any changes (device add, remove, update) and the UPnPDBUpdated functions will be called whenever such changes are discovered.
There is little extra coding to be done in order to fill in an UITableView to display the found UPnP devices on the iPhone UI.
Check the upnpxdemo project for the full implementation.
Next Step 2. Description
{UPnP specifications: "For the control point to learn more about the device and its capabilities, or to interact with the device, the control point MUST retrieve a description of the device and its capabilities from the URL provided by the device in the discovery message."}
upnpx does the description step in the background so that integrators do not have to deal with it. The BasicUPnPDevice pointers we get from the UPnPDB during Step 1. Discovery already contains the device description and capabilities. upnpx did the necessary HTTP requests and parsed the Device and Service Description XML's for all 'known' Devices found during the SSDP Discovery.
Check the Factory DeviceFactory.m for a list of current supported devices. This list can quite easily be extended with the help of the automated code generator scpdcodegenerator.xcodeproj which takes scdp XML descriptions, from the UPnP standard, as input. See Add new device.
Since the UPnPDB only returns the device base class BasicUPnPDevice you need to implement a little logic to get access to the higher level device classes, the trick is to check the type and to cast the base class. See the following sample:
//In this example we are interested in MediaServer1Devices - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { BasicUPnPDevice *device = [mDevices objectAtIndex:indexPath.row]; if([[device urn] isEqualToString:@"urn:schemas-upnp-org:device:MediaServer:1"]){ MediaServer1Device *server = (MediaServer1Device*)device; //We are ready to call control-methods on the server device } }
MediaServer1Device
This step is in fact quite easy, all you have to know it that upnpx give you instances of Devices, including description and capabilities, in the form of BasicUPnPDevice base classes and that you need to cast them to the actual device class in order to be ready for the next step.
Next Step 3. Control
{UPnP specifications: "Through control, control points invoke actions on devices and poll for values."}
This is an interesting step since we actually will read and set device properties. Example for MediaServer devices we will be able to query the media on the device in different ways, browse, search, get icons etc. And for example for MediaRenderers we will have methods to play content, pause, change volume etc.
The exact capabilities for a device are defined by the UPnP Standard or by the vendor, upnpx provide an Objective-C interface for such specified service functions.
Example: MediaServer1
This section demonstrates how to write a MediaServer control in order to browse its media data.
Screenshots from upnpxdemo.xcodeproj
The first thing to do is to take look at the MediaServer-v1 device specifications, these are defined in the following template:http://www.upnp.org/specs/av/UPnP-av-MediaServer-v1-Device.pdf
UPnP devices expose Actions and Events through services, so you need to find and choose the desired Service Type that offers the functionality you want to achieve.
For our example MediaServer:1 the supported Service Types are listed under "2.2 Device Model":
ContentDirectory:1.0 | UPnP-av-ContentDirectory-v1-Service.pdf |
ConnectionManager:1.0 | UPnP-av-ConnectionManager-v1-Service.pdf |
AVTransport:1.0 | UPnP-av-AVTransport-v1-Service.pdf |
After examining the specifications you will see that the ContentDirectory:1.0 service offers an Action, under 2.7.4. Browse, to browse the data on a device.
The Browse action defines a set of input and output parameters, read the specifications to understand their meaning. upnpx implements theBrowse action (as well as all other Actions) with the exact same parameters, the usage of the parameters is defined by the specifications.
Now we decided we need access to the Browse Action, we need to find out how to achieve this in upnpx. Well, like the other things, it is not too difficult, call it easy.
1. First you need the MediaServer1Device pointer you got from Step 2. Description.
... BasicUPnPDevice *device = [mDevices objectAtIndex:indexPath.row]; if([[device urn] isEqualToString:@"urn:schemas-upnp-org:device:MediaServer:1"]){ MediaServer1Device *server = (MediaServer1Device*)device; //We are ready to call control-methods on the server device } ...
2. The MediaServer1Device defines 3 properties to access the Services.
-(SoapActionsAVTransport1*)avTransport; -(SoapActionsConnectionManager1*)connectionManager; -(SoapActionsContentDirectory1*)contentDirectory;
Use the one you need, in our case it is [server contentDirectory]
3. Call the desired Actions. All actions defined in ContentDirectory:1.0 are available as Methods in the SoapActionsContentDirectory1 class.
In our case we want to use the, somewhat complexer, BrowseWithObjectID Action. Note that upnpx follows the Objective-C conventions and add the name of the first argument as a postfix to the method (Action) name : Browse WithObjectID
-(int)BrowseWithObjectID:(NSString*)objectid BrowseFlag:(NSString*)browseflag Filter:(NSString*)filter StartingIndex:(NSString*)startingindex RequestedCount:(NSString*)requestedcount SortCriteria:(NSString*)sortcriteria OutResult:(NSMutableString*)result OutNumberReturned:(NSMutableString*)numberreturned OutTotalMatches:(NSMutableString*)totalmatches OutUpdateID:(NSMutableString*)updateid;
This is an example from upnpxdemo.xcodeproj that demonstrates how to handle the input and output parameters (the output parameters need to be of the type NSMutableString, when the function returns they contain the requested data):
//Allocate NSMutableString's to read the results NSMutableString *outResult = [[NSMutableString alloc] init]; NSMutableString *outNumberReturned = [[NSMutableString alloc] init]; NSMutableString *outTotalMatches = [[NSMutableString alloc] init]; NSMutableString *outUpdateID = [[NSMutableString alloc] init]; [[m_device contentDirectory] BrowseWithObjectID:m_rootId BrowseFlag:@"BrowseDirectChildren" Filter:@"*" StartingIndex:@"0" RequestedCount:@"0" SortCriteria:@"+dc:title" OutResult:outResult OutNumberReturned:outNumberReturned OutTotalMatches:outTotalMatches OutUpdateID:outUpdateID]; ... <snip>... [outResult release]; [outNumberReturned release]; [outTotalMatches release]; [outUpdateID release];
All Actions for all services work in a similar way. Mostly it is enough to call them with the right parameters (check the specs for that) and the device will act on your commands.
For this demo, the Browse Action returns a somewhat complicated result in outResult in the form of DIDL Xml. upnpx has some helper code to parse that DIDL result into an array with usable objects:
FolderView.h:
@interface FolderView : UITableViewController { ... NSMutableArray *m_playList; }
FolderView.m:
- (void)viewDidLoad { [super viewDidLoad]; //Allocate NSMutableString's to read the results NSMutableString *outResult = [[NSMutableString alloc] init]; NSMutableString *outNumberReturned = [[NSMutableString alloc] init]; NSMutableString *outTotalMatches = [[NSMutableString alloc] init]; NSMutableString *outUpdateID = [[NSMutableString alloc] init]; [[m_device contentDirectory] BrowseWithObjectID:m_rootId BrowseFlag:@"BrowseDirectChildren" Filter:@"*" StartingIndex:@"0" RequestedCount:@"0" SortCriteria:@"+dc:title" OutResult:outResult OutNumberReturned:outNumberReturned OutTotalMatches:outTotalMatches OutUpdateID:outUpdateID]; //The collections are returned as DIDL Xml in the string 'outResult' //upnpx provide a helper class to parse the DIDL Xml in an Array of usable MediaServer1BasicObject objects [m_playList removeAllObjects]; NSData *didl = [outResult dataUsingEncoding:NSUTF8StringEncoding]; MediaServerBasicObjectParser *parser = [[MediaServerBasicObjectParser alloc] initWithMediaObjectArray:m_playList itemsOnly:NO]; [parser parseFromData:didl]; [parser release]; //Playlist contains a collection of usable MediaServer1BasicObject's now. //Check the type and cast them to MediaServer1ContainerObject or MediaServer1ItemObject to have full access to the information //Example: //if([[m_playList objectAtIndex:0] isContainer]){ // MediaServer1ContainerObject *container = (MediaServer1ContainerObject*)[m_playList objectAtIndex:0]; // ...do something useful with the container //}else{ // MediaServer1ItemObject *item = (MediaServer1ItemObject*)[m_playList objectAtIndex:0]; // ...do something useful with the item //} [outResult release]; [outNumberReturned release]; [outTotalMatches release]; [outUpdateID release]; self.title = m_title; }
Check the upnpxdemo project, and especially the FolderView.m, for the full implementation.
Next Step 4. Eventing
{UPnP specifications: "Through eventing, control points listen to state changes in device(s)."}
1. Implement the Interface : BasicUPnPServiceObserver, declared in BasicUPnPService.h
@protocol BasicUPnPServiceObserver -(void)UPnPEvent:(BasicUPnPService*)sender events:(NSDictionary*)events; @end
2. Register your Class as an Observer (be sure to do this only once)
if([[self avTransportService] isObserver:(BasicUPnPServiceObserver*)self] == NO){ [[self avTransportService] addObserver:(BasicUPnPServiceObserver*)self]; //Lazy observer attach }
3. Receive the events
UPnP-av-AVTransport-v1-Service.pdf
//BasicUPnPServiceObserver -(void)UPnPEvent:(BasicUPnPService*)sender events:(NSDictionary*)events{ if(sender == [self avTransportService]){ NSString *newState = [events objectForKey:@"TransportState"]; if([newState isEqualToString:@"STOPPED"]){ //Do your stuff, play next song etc... } } }
Next Step 5. Presentation
{UPnP specifications: "If a device has a URL for presentation, then the control point can retrieve a page from this URL, load the page into a browser and, depending on the capabilities of the page, allow a user to control the device and/or view device status. The degree to which each of these can be accomplished depends on the specific capabilities of the presentation page and device. "}