mirror of
https://github.com/moonlight-stream/moonlight-qt.git
synced 2026-06-15 21:22:40 +00:00
Complete refactor of project.
- now runs universally on iPhone, iPad, and simulator - all paths should now be relative
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
//
|
||||
// AppDelegate.h
|
||||
// Limelight
|
||||
// Limelight-iOS
|
||||
//
|
||||
// Created by Diego Waxemberg on 8/30/14.
|
||||
// Copyright (c) 2014 Limelight Stream. All rights reserved.
|
||||
// Created by Diego Waxemberg on 1/17/14.
|
||||
// Copyright (c) 2014 Diego Waxemberg. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
@@ -18,5 +18,6 @@
|
||||
|
||||
- (void)saveContext;
|
||||
- (NSURL *)applicationDocumentsDirectory;
|
||||
+ (NSOperationQueue*) getMainOpQueue;
|
||||
|
||||
@end
|
||||
|
||||
+14
-10
@@ -1,9 +1,9 @@
|
||||
//
|
||||
// AppDelegate.m
|
||||
// Limelight
|
||||
// Limelight-iOS
|
||||
//
|
||||
// Created by Diego Waxemberg on 8/30/14.
|
||||
// Copyright (c) 2014 Limelight Stream. All rights reserved.
|
||||
// Created by Diego Waxemberg on 1/17/14.
|
||||
// Copyright (c) 2014 Diego Waxemberg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "AppDelegate.h"
|
||||
@@ -14,15 +14,19 @@
|
||||
@synthesize managedObjectModel = _managedObjectModel;
|
||||
@synthesize persistentStoreCoordinator = _persistentStoreCoordinator;
|
||||
|
||||
static NSOperationQueue* mainQueue;
|
||||
|
||||
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
|
||||
{
|
||||
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
|
||||
// Override point for customization after application launch.
|
||||
self.window.backgroundColor = [UIColor whiteColor];
|
||||
[self.window makeKeyAndVisible];
|
||||
mainQueue = [[NSOperationQueue alloc]init];
|
||||
return YES;
|
||||
}
|
||||
|
||||
+ (NSOperationQueue*) getMainOpQueue
|
||||
{
|
||||
return mainQueue;
|
||||
}
|
||||
|
||||
- (void)applicationWillResignActive:(UIApplication *)application
|
||||
{
|
||||
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
|
||||
@@ -58,7 +62,7 @@
|
||||
if (managedObjectContext != nil) {
|
||||
if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error]) {
|
||||
// Replace this implementation with code to handle the error appropriately.
|
||||
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
|
||||
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
|
||||
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
|
||||
abort();
|
||||
}
|
||||
@@ -90,7 +94,7 @@
|
||||
if (_managedObjectModel != nil) {
|
||||
return _managedObjectModel;
|
||||
}
|
||||
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"Limelight" withExtension:@"momd"];
|
||||
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"Limelight_iOS" withExtension:@"momd"];
|
||||
_managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
|
||||
return _managedObjectModel;
|
||||
}
|
||||
@@ -103,7 +107,7 @@
|
||||
return _persistentStoreCoordinator;
|
||||
}
|
||||
|
||||
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"Limelight.sqlite"];
|
||||
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"Limelight_iOS.sqlite"];
|
||||
|
||||
NSError *error = nil;
|
||||
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// Connection.h
|
||||
// Limelight-iOS
|
||||
//
|
||||
// Created by Diego Waxemberg on 1/19/14.
|
||||
// Copyright (c) 2014 Diego Waxemberg. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface Connection : NSOperation <NSStreamDelegate>
|
||||
|
||||
-(id) initWithHost:(int)ipaddr width:(int)width height:(int)height;
|
||||
-(void) main;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,264 @@
|
||||
//
|
||||
// Connection.m
|
||||
// Limelight-iOS
|
||||
//
|
||||
// Created by Diego Waxemberg on 1/19/14.
|
||||
// Copyright (c) 2014 Diego Waxemberg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "Connection.h"
|
||||
#import <AudioUnit/AudioUnit.h>
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
|
||||
#include "Limelight.h"
|
||||
#include "opus.h"
|
||||
#include "VideoDecoder.h"
|
||||
#include "VideoRenderer.h"
|
||||
|
||||
@implementation Connection {
|
||||
IP_ADDRESS host;
|
||||
STREAM_CONFIGURATION streamConfig;
|
||||
CONNECTION_LISTENER_CALLBACKS clCallbacks;
|
||||
DECODER_RENDERER_CALLBACKS drCallbacks;
|
||||
AUDIO_RENDERER_CALLBACKS arCallbacks;
|
||||
}
|
||||
|
||||
static OpusDecoder *opusDecoder;
|
||||
|
||||
|
||||
#define PCM_BUFFER_SIZE 1024
|
||||
#define OUTPUT_BUS 0
|
||||
|
||||
static short* decodedPcmBuffer;
|
||||
static int filledPcmBuffer;
|
||||
NSLock* audioRendererBlock;
|
||||
AudioComponentInstance audioUnit;
|
||||
bool started = false;
|
||||
|
||||
|
||||
void DrSetup(int width, int height, int fps, void* context, int drFlags)
|
||||
{
|
||||
printf("Setup video\n");
|
||||
nv_avc_init(width, height, DISABLE_LOOP_FILTER | FAST_DECODE | FAST_BILINEAR_FILTERING, 2);
|
||||
}
|
||||
|
||||
void DrSubmitDecodeUnit(PDECODE_UNIT decodeUnit)
|
||||
{
|
||||
unsigned char* data = (unsigned char*) malloc(decodeUnit->fullLength + nv_avc_get_input_padding_size());
|
||||
if (data != NULL) {
|
||||
int offset = 0;
|
||||
int err;
|
||||
|
||||
PLENTRY entry = decodeUnit->bufferList;
|
||||
while (entry != NULL) {
|
||||
memcpy(&data[offset], entry->data, entry->length);
|
||||
offset += entry->length;
|
||||
entry = entry->next;
|
||||
}
|
||||
|
||||
err = nv_avc_decode(data, decodeUnit->fullLength);
|
||||
if (err != 0) {
|
||||
printf("Decode failed: %d\n", err);
|
||||
}
|
||||
|
||||
free(data);
|
||||
}
|
||||
}
|
||||
|
||||
void DrStart(void)
|
||||
{
|
||||
printf("Start video\n");
|
||||
[VideoRenderer startRendering];
|
||||
}
|
||||
|
||||
void DrStop(void)
|
||||
{
|
||||
printf("Stop video\n");
|
||||
[VideoRenderer stopRendering];
|
||||
}
|
||||
|
||||
void DrRelease(void)
|
||||
{
|
||||
printf("Release video\n");
|
||||
nv_avc_destroy();
|
||||
}
|
||||
|
||||
void ArInit(void)
|
||||
{
|
||||
int err;
|
||||
|
||||
printf("Init audio\n");
|
||||
|
||||
opusDecoder = opus_decoder_create(48000, 2, &err);
|
||||
|
||||
decodedPcmBuffer = malloc(PCM_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
void ArRelease(void)
|
||||
{
|
||||
printf("Release audio\n");
|
||||
|
||||
if (decodedPcmBuffer != NULL) {
|
||||
free(decodedPcmBuffer);
|
||||
decodedPcmBuffer = NULL;
|
||||
}
|
||||
|
||||
if (opusDecoder != NULL) {
|
||||
opus_decoder_destroy(opusDecoder);
|
||||
opusDecoder = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void ArStart(void)
|
||||
{
|
||||
printf("Start audio\n");
|
||||
}
|
||||
|
||||
void ArStop(void)
|
||||
{
|
||||
printf("Stop audio\n");
|
||||
}
|
||||
|
||||
void ArDecodeAndPlaySample(char* sampleData, int sampleLength)
|
||||
{
|
||||
|
||||
if (!started) {
|
||||
AudioOutputUnitStart(audioUnit);
|
||||
started = true;
|
||||
}
|
||||
filledPcmBuffer = opus_decode(opusDecoder, (unsigned char*)sampleData, sampleLength, decodedPcmBuffer, PCM_BUFFER_SIZE / 2, 0);
|
||||
if (filledPcmBuffer > 0) {
|
||||
// Return of opus_decode is samples per channel
|
||||
filledPcmBuffer *= 4;
|
||||
|
||||
NSLog(@"pcmBuffer: %d", filledPcmBuffer);
|
||||
//[audioRendererBlock lock];
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
-(id) initWithHost:(int)ipaddr width:(int)width height:(int)height
|
||||
{
|
||||
self = [super init];
|
||||
host = ipaddr;
|
||||
|
||||
streamConfig.width = width;
|
||||
streamConfig.height = height;
|
||||
streamConfig.fps = 30;
|
||||
|
||||
drCallbacks.setup = DrSetup;
|
||||
drCallbacks.start = DrStart;
|
||||
drCallbacks.stop = DrStop;
|
||||
drCallbacks.release = DrRelease;
|
||||
drCallbacks.submitDecodeUnit = DrSubmitDecodeUnit;
|
||||
|
||||
arCallbacks.init = ArInit;
|
||||
arCallbacks.start = ArStart;
|
||||
arCallbacks.stop = ArStop;
|
||||
arCallbacks.release = ArRelease;
|
||||
arCallbacks.decodeAndPlaySample = ArDecodeAndPlaySample;
|
||||
|
||||
|
||||
//////// Don't think any of this is used /////////
|
||||
NSError *audioSessionError = nil;
|
||||
AVAudioSession* audioSession = [AVAudioSession sharedInstance];
|
||||
[audioSession setPreferredSampleRate:48000.0 error:&audioSessionError];
|
||||
|
||||
[audioSession setCategory: AVAudioSessionCategoryPlayAndRecord error: &audioSessionError];
|
||||
[audioSession setPreferredOutputNumberOfChannels:2 error:&audioSessionError];
|
||||
[audioSession setActive: YES error: &audioSessionError];
|
||||
//////////////////////////////////////////////////
|
||||
|
||||
OSStatus status;
|
||||
|
||||
AudioComponentDescription audioDesc;
|
||||
audioDesc.componentType = kAudioUnitType_Output;
|
||||
audioDesc.componentSubType = kAudioUnitSubType_RemoteIO;
|
||||
audioDesc.componentFlags = 0;
|
||||
audioDesc.componentFlagsMask = 0;
|
||||
audioDesc.componentManufacturer = kAudioUnitManufacturer_Apple;
|
||||
|
||||
status = AudioComponentInstanceNew(AudioComponentFindNext(NULL, &audioDesc), &audioUnit);
|
||||
|
||||
if (status) {
|
||||
NSLog(@"Unable to instantiate new AudioComponent: %d", (int32_t)status);
|
||||
}
|
||||
|
||||
|
||||
AudioStreamBasicDescription audioFormat = {0};
|
||||
audioFormat.mSampleRate = 48000;
|
||||
audioFormat.mBitsPerChannel = 16;
|
||||
audioFormat.mFormatID = kAudioFormatLinearPCM;
|
||||
audioFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger;
|
||||
audioFormat.mFramesPerPacket = 1;
|
||||
audioFormat.mChannelsPerFrame = 2;
|
||||
audioFormat.mBytesPerFrame = 960;
|
||||
audioFormat.mBytesPerPacket = 960;
|
||||
audioFormat.mReserved = 0;
|
||||
|
||||
status = AudioUnitSetProperty(audioUnit,
|
||||
kAudioUnitProperty_StreamFormat,
|
||||
kAudioUnitScope_Output,
|
||||
OUTPUT_BUS,
|
||||
&audioFormat,
|
||||
sizeof(audioFormat));
|
||||
if (status) {
|
||||
NSLog(@"Unable to set audio unit to output: %d", (int32_t)status);
|
||||
}
|
||||
|
||||
AURenderCallbackStruct callbackStruct = {0};
|
||||
callbackStruct.inputProc = playbackCallback;
|
||||
callbackStruct.inputProcRefCon = NULL;
|
||||
|
||||
status = AudioUnitSetProperty(audioUnit,
|
||||
kAudioUnitProperty_SetRenderCallback,
|
||||
kAudioUnitScope_Global,
|
||||
OUTPUT_BUS,
|
||||
&callbackStruct,
|
||||
sizeof(callbackStruct));
|
||||
if (status) {
|
||||
NSLog(@"Unable to set audio unit callback: %d", (int32_t)status);
|
||||
}
|
||||
|
||||
status = AudioUnitInitialize(audioUnit);
|
||||
if (status) {
|
||||
NSLog(@"Unable to initialize audioUnit: %d", (int32_t)status);
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
static OSStatus playbackCallback(void *inRefCon,
|
||||
AudioUnitRenderActionFlags *ioActionFlags,
|
||||
const AudioTimeStamp *inTimeStamp,
|
||||
UInt32 inBusNumber,
|
||||
UInt32 inNumberFrames,
|
||||
AudioBufferList *ioData) {
|
||||
// Notes: ioData contains buffers (may be more than one!)
|
||||
// Fill them up as much as you can. Remember to set the size value in each buffer to match how
|
||||
// much data is in the buffer.
|
||||
|
||||
NSLog(@"Playback callback");
|
||||
|
||||
for (int i = 0; i < ioData->mNumberBuffers; i++) {
|
||||
ioData->mBuffers[i].mNumberChannels = 2;
|
||||
int min = MIN(ioData->mBuffers[i].mDataByteSize, filledPcmBuffer);
|
||||
NSLog(@"Min: %d", min);
|
||||
memcpy(ioData->mBuffers[i].mData, decodedPcmBuffer, min);
|
||||
ioData->mBuffers[i].mDataByteSize = min;
|
||||
filledPcmBuffer -= min;
|
||||
}
|
||||
|
||||
//[audioRendererBlock unlock];
|
||||
return noErr;
|
||||
}
|
||||
|
||||
|
||||
-(void) main
|
||||
{
|
||||
LiStartConnection(host, &streamConfig, &clCallbacks, &drCallbacks, &arCallbacks, NULL, 0);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// ConnectionHandler.h
|
||||
// Limelight-iOS
|
||||
//
|
||||
// Created by Diego Waxemberg on 1/27/14.
|
||||
// Copyright (c) 2014 Diego Waxemberg. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface ConnectionHandler : NSOperation
|
||||
@property NSString* hostName;
|
||||
@property int mode;
|
||||
|
||||
+ (void) pairWithHost:(NSString*)host;
|
||||
+ (void) streamWithHost:(NSString*)host viewController:(UIViewController*)viewCont;
|
||||
+ (NSString*) resolveHost:(NSString*)host;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,336 @@
|
||||
//
|
||||
// ConnectionHandler.m
|
||||
// Limelight-iOS
|
||||
//
|
||||
// Created by Diego Waxemberg on 1/27/14.
|
||||
// Copyright (c) 2014 Diego Waxemberg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "ConnectionHandler.h"
|
||||
#import "AppDelegate.h"
|
||||
#import <AdSupport/ASIdentifierManager.h>
|
||||
#import "MainFrameViewController.h"
|
||||
|
||||
#include <libxml2/libxml/xmlreader.h>
|
||||
#include <netdb.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
@implementation ConnectionHandler
|
||||
static const int PAIR_MODE = 0x1;
|
||||
static const int STREAM_MODE = 0x2;
|
||||
|
||||
static MainFrameViewController* viewCont;
|
||||
|
||||
+ (void)streamWithHost:(NSString *)host viewController:(MainFrameViewController *)viewController
|
||||
{
|
||||
viewCont = viewController;
|
||||
ConnectionHandler* streamHandler = [[ConnectionHandler alloc] initForStream:host];
|
||||
[[AppDelegate getMainOpQueue] addOperation:streamHandler];
|
||||
}
|
||||
|
||||
+ (void)pairWithHost:(NSString *)host
|
||||
{
|
||||
ConnectionHandler* pairHandler = [[ConnectionHandler alloc] initForPair:host];
|
||||
[[AppDelegate getMainOpQueue]addOperation:pairHandler];
|
||||
}
|
||||
|
||||
+ (NSString*) resolveHost:(NSString*)host
|
||||
{
|
||||
struct hostent *hostent;
|
||||
|
||||
if (inet_addr([host UTF8String]) != INADDR_NONE)
|
||||
{
|
||||
// Already an IP address
|
||||
return host;
|
||||
}
|
||||
else
|
||||
{
|
||||
hostent = gethostbyname([host UTF8String]);
|
||||
if (hostent != NULL)
|
||||
{
|
||||
char* ipstr = inet_ntoa(*(struct in_addr*)hostent->h_addr_list[0]);
|
||||
NSLog(@"Resolved %@ -> %s", host, ipstr);
|
||||
return [NSString stringWithUTF8String:ipstr];
|
||||
}
|
||||
else
|
||||
{
|
||||
NSLog(@"Failed to resolve host: %d", h_errno);
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+ (NSString*) getId
|
||||
{
|
||||
// we need to generate some unique id
|
||||
NSUUID* uniqueId = [ASIdentifierManager sharedManager].advertisingIdentifier;
|
||||
NSString* idString = [NSString stringWithString:[uniqueId UUIDString]];
|
||||
|
||||
//use just the last 12 digits because GameStream truncates id's
|
||||
return [idString substringFromIndex:24];
|
||||
}
|
||||
|
||||
+ (NSString*) getName
|
||||
{
|
||||
NSString* name = [UIDevice currentDevice].name;
|
||||
NSLog(@"Device Name: %@", name);
|
||||
|
||||
//sanitize name
|
||||
name = [name stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
|
||||
return name;
|
||||
}
|
||||
|
||||
- (id) initForStream:(NSString*)host
|
||||
{
|
||||
self = [super init];
|
||||
self.hostName = host;
|
||||
self.mode = STREAM_MODE;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id) initForPair:(NSString*)host
|
||||
{
|
||||
self = [super init];
|
||||
self.hostName = host;
|
||||
self.mode = PAIR_MODE;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void) main
|
||||
{
|
||||
switch (self.mode)
|
||||
{
|
||||
case STREAM_MODE:
|
||||
{
|
||||
bool pairState;
|
||||
int err = [self pairState:&pairState];
|
||||
if (err) {
|
||||
NSLog(@"ERROR: Pair state error: %d", err);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!pairState)
|
||||
{
|
||||
NSLog(@"WARN: Not paired with host!");
|
||||
UIAlertView* alert = [[UIAlertView alloc] initWithTitle:@"Not Paired"
|
||||
message:@"This device is not paired with the host."
|
||||
delegate:nil
|
||||
cancelButtonTitle:@"Okay"
|
||||
otherButtonTitles:nil, nil];
|
||||
[self showAlert:alert];
|
||||
return;
|
||||
}
|
||||
|
||||
//TODO: parse this from http request
|
||||
unsigned int appId = 67339056;
|
||||
|
||||
NSData* applist = [self httpRequest:[NSString stringWithFormat:@"applist?uniqueid=%@", [ConnectionHandler getId]]];
|
||||
|
||||
NSString* streamString = [NSString stringWithFormat:@"http://%@:47989/launch?uniqueid=%@&appid=%u", [ConnectionHandler resolveHost:self.hostName], [ConnectionHandler getId], appId];
|
||||
NSLog(@"host: %@", self.hostName);
|
||||
|
||||
NSURL* url = [[NSURL alloc] initWithString:streamString];
|
||||
|
||||
NSMutableURLRequest* request = [[NSMutableURLRequest alloc] initWithURL:url];
|
||||
[request setHTTPMethod:@"GET"];
|
||||
|
||||
NSURLResponse* response = nil;
|
||||
NSData *response1 = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:NULL];
|
||||
|
||||
|
||||
[viewCont performSelector:@selector(segueIntoStream) onThread:[NSThread mainThread] withObject:nil waitUntilDone:NO];
|
||||
break;
|
||||
}
|
||||
case PAIR_MODE:
|
||||
{
|
||||
NSLog(@"Trying to pair to %@...", self.hostName);
|
||||
|
||||
bool pairState;
|
||||
int err = [self pairState:&pairState];
|
||||
if (err) {
|
||||
NSLog(@"ERROR: Pair state error: %d", err);
|
||||
break;
|
||||
}
|
||||
|
||||
NSLog(@"Pair state: %i", pairState);
|
||||
|
||||
if(pairState)
|
||||
{
|
||||
UIAlertView* alert = [[UIAlertView alloc]initWithTitle:@"Pairing"
|
||||
message:[NSString stringWithFormat:@"Already paired to:\n %@", self.hostName]
|
||||
delegate:nil
|
||||
cancelButtonTitle:@"Okay"
|
||||
otherButtonTitles:nil, nil];
|
||||
[self showAlert:alert];
|
||||
NSLog(@"Showing alertview");
|
||||
break;
|
||||
}
|
||||
|
||||
// this will hang until it pairs successfully or unsuccessfully
|
||||
NSData* pairResponse = [self httpRequest:[NSString stringWithFormat:@"pair?uniqueid=%@&devicename=%@", [ConnectionHandler getId], [ConnectionHandler getName]]];
|
||||
|
||||
void* buffer = [self getXMLBufferFromData:pairResponse];
|
||||
if (buffer == NULL) {
|
||||
NSLog(@"ERROR: Unable to allocate pair buffer.");
|
||||
UIAlertView* alert = [[UIAlertView alloc] initWithTitle:@"Pairing"
|
||||
message:[NSString stringWithFormat:@"Failed to pair with host:\n %@", self.hostName]
|
||||
delegate:nil
|
||||
cancelButtonTitle:@"Okay"
|
||||
otherButtonTitles:nil, nil];
|
||||
[self showAlert:alert];
|
||||
break;
|
||||
}
|
||||
xmlDocPtr doc = xmlParseMemory(buffer, [pairResponse length]-1);
|
||||
|
||||
if (doc == NULL) {
|
||||
NSLog(@"ERROR: An error occured trying to parse pair response.");
|
||||
break;
|
||||
}
|
||||
|
||||
xmlNodePtr node = xmlDocGetRootElement(doc);
|
||||
node = node->xmlChildrenNode;
|
||||
while (node != NULL) {
|
||||
NSLog(@"Node: %s", node->name);
|
||||
if (!xmlStrcmp(node->name, (const xmlChar*)"sessionid"))
|
||||
{
|
||||
xmlChar* sessionid = xmlNodeListGetString(doc, node->xmlChildrenNode, 1);
|
||||
|
||||
if (xmlStrcmp(sessionid, (xmlChar*)"0")) {
|
||||
UIAlertView* alert = [[UIAlertView alloc] initWithTitle:@"Pairing"
|
||||
message:[NSString stringWithFormat:@"Successfully paired to host:\n %@", self.hostName]
|
||||
delegate:nil
|
||||
cancelButtonTitle:@"Okay"
|
||||
otherButtonTitles:nil, nil];
|
||||
[self showAlert:alert];
|
||||
|
||||
} else {
|
||||
UIAlertView* alert = [[UIAlertView alloc] initWithTitle:@"Pairing"
|
||||
message:[NSString stringWithFormat:@"Pairing was declined by host:\n %@", self.hostName]
|
||||
delegate:nil
|
||||
cancelButtonTitle:@"Okay"
|
||||
otherButtonTitles:nil, nil];
|
||||
[self showAlert:alert];
|
||||
}
|
||||
|
||||
xmlFree(sessionid);
|
||||
goto cleanup;
|
||||
}
|
||||
node = node->next;
|
||||
}
|
||||
|
||||
NSLog(@"ERROR: Could not find \"sessionid\" element in XML");
|
||||
|
||||
cleanup:
|
||||
xmlFreeNode(node);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
NSLog(@"Invalid connection mode specified!!!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (int) pairState: (bool*)paired
|
||||
{
|
||||
int err;
|
||||
NSData* pairData = [self httpRequest:[NSString stringWithFormat:@"pairstate?uniqueid=%@", [ConnectionHandler getId]]];
|
||||
|
||||
void* buffer = [self getXMLBufferFromData:pairData];
|
||||
|
||||
if (buffer == NULL) {
|
||||
NSLog(@"ERROR: Unable to allocate pair state buffer.");
|
||||
UIAlertView* alert = [[UIAlertView alloc] initWithTitle:@"Pairing"
|
||||
message:[NSString stringWithFormat:@"Failed to get pair state with host:\n %@", self.hostName]
|
||||
delegate:nil
|
||||
cancelButtonTitle:@"Okay"
|
||||
otherButtonTitles:nil, nil];
|
||||
[self showAlert:alert];
|
||||
return -1;
|
||||
}
|
||||
|
||||
xmlDocPtr doc = xmlParseMemory(buffer, [pairData length]-1);
|
||||
|
||||
if (doc == NULL) {
|
||||
NSLog(@"ERROR: An error occured trying to parse pair state.");
|
||||
return -1;
|
||||
}
|
||||
|
||||
xmlNodePtr node = xmlDocGetRootElement(doc);
|
||||
while (node != NULL) {
|
||||
NSLog(@"Node: %s", node->name);
|
||||
if (!xmlStrcmp(node->name, (const xmlChar*)"paired"))
|
||||
{
|
||||
xmlChar* pairState = xmlNodeListGetString(doc, node->xmlChildrenNode, 1);
|
||||
*paired = !xmlStrcmp(pairState, (const xmlChar*)"1");
|
||||
err = 0;
|
||||
|
||||
xmlFree(pairState);
|
||||
goto cleanup;
|
||||
}
|
||||
node = node->xmlChildrenNode;
|
||||
}
|
||||
|
||||
NSLog(@"ERROR: Could not find \"paired\" element in XML");
|
||||
err = -2;
|
||||
|
||||
cleanup:
|
||||
xmlFreeNode(node);
|
||||
return err;
|
||||
}
|
||||
|
||||
- (NSData*) httpRequest:(NSString*)args
|
||||
{
|
||||
NSString* requestString = [NSString stringWithFormat:@"http://%@:47989/%@", [ConnectionHandler resolveHost:self.hostName], args];
|
||||
|
||||
NSLog(@"Making HTTP request: %@", requestString);
|
||||
|
||||
NSURL* url = [[NSURL alloc] initWithString:requestString];
|
||||
|
||||
NSMutableURLRequest* request = [[NSMutableURLRequest alloc] initWithURL:url];
|
||||
[request setHTTPMethod:@"GET"];
|
||||
[request setTimeoutInterval:7];
|
||||
|
||||
NSURLResponse* response = nil;
|
||||
NSError* error = nil;
|
||||
NSData* responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
|
||||
|
||||
if (error != nil) {
|
||||
NSLog(@"An error occured making HTTP request: %@\n Caused by: %@", [error localizedDescription], [error localizedFailureReason]);
|
||||
}
|
||||
return responseData;
|
||||
}
|
||||
|
||||
|
||||
- (void*) getXMLBufferFromData:(NSData*)data
|
||||
{
|
||||
if (data == nil) {
|
||||
NSLog(@"ERROR: No data to get XML from!");
|
||||
return NULL;
|
||||
}
|
||||
char* buffer = malloc([data length] + 1);
|
||||
if (buffer == NULL) {
|
||||
NSLog(@"ERROR: Unable to allocate XML buffer.");
|
||||
return NULL;
|
||||
}
|
||||
[data getBytes:buffer length:[data length]];
|
||||
buffer[[data length]] = 0;
|
||||
|
||||
NSLog(@"Buffer: %s", buffer);
|
||||
|
||||
//#AllTheJank
|
||||
void* newBuffer = (void*)[[[NSString stringWithUTF8String:buffer]stringByReplacingOccurrencesOfString:@"UTF-16" withString:@"UTF-8" options:NSCaseInsensitiveSearch range:NSMakeRange(0, [data length])] UTF8String];
|
||||
|
||||
NSLog(@"New Buffer: %s", newBuffer);
|
||||
free(buffer);
|
||||
return newBuffer;
|
||||
}
|
||||
|
||||
- (void) showAlert:(UIAlertView*) alert
|
||||
{
|
||||
[alert performSelector:@selector(show) onThread:[NSThread mainThread] withObject:nil waitUntilDone:NO];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -24,20 +24,25 @@
|
||||
<string>1.0</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
<string>MainFrame-iPhone</string>
|
||||
<key>UIMainStoryboardFile~ipad</key>
|
||||
<string>MainFrame-iPad</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>armv7</string>
|
||||
</array>
|
||||
<key>UIStatusBarHidden</key>
|
||||
<true/>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
//
|
||||
// MainFrameViewController.h
|
||||
// Limelight-iOS
|
||||
//
|
||||
// Created by Diego Waxemberg on 1/17/14.
|
||||
// Copyright (c) 2014 Diego Waxemberg. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface MainFrameViewController : UIViewController <UIPickerViewDataSource,UIPickerViewDelegate>
|
||||
- (IBAction)StreamButton:(UIButton *)sender;
|
||||
- (IBAction)PairButton:(UIButton *)sender;
|
||||
@property (strong, nonatomic) IBOutlet UITextField *HostField;
|
||||
@property (strong, nonatomic) IBOutlet UIPickerView *StreamConfigs;
|
||||
@property (strong, nonatomic) NSArray* streamConfigVals;
|
||||
|
||||
+ (const char*)getHostAddr;
|
||||
- (void) segueIntoStream;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,78 @@
|
||||
//
|
||||
// MainFrameViewController.m
|
||||
// Limelight-iOS
|
||||
//
|
||||
// Created by Diego Waxemberg on 1/17/14.
|
||||
// Copyright (c) 2014 Diego Waxemberg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MainFrameViewController.h"
|
||||
#import "VideoDepacketizer.h"
|
||||
#import "ConnectionHandler.h"
|
||||
|
||||
@interface MainFrameViewController ()
|
||||
|
||||
@end
|
||||
|
||||
@implementation MainFrameViewController
|
||||
static NSString* hostAddr;
|
||||
|
||||
+ (const char*)getHostAddr
|
||||
{
|
||||
return [hostAddr UTF8String];
|
||||
}
|
||||
|
||||
- (void)PairButton:(UIButton *)sender
|
||||
{
|
||||
NSLog(@"Pair Button Pressed!");
|
||||
hostAddr = self.HostField.text;
|
||||
[ConnectionHandler pairWithHost:hostAddr];
|
||||
}
|
||||
|
||||
- (void)StreamButton:(UIButton *)sender
|
||||
{
|
||||
NSLog(@"Stream Button Pressed!");
|
||||
hostAddr = self.HostField.text;
|
||||
[ConnectionHandler streamWithHost:hostAddr viewController:self];
|
||||
}
|
||||
|
||||
- (void) segueIntoStream {
|
||||
[self performSegueWithIdentifier:@"createStreamFrame" sender:self];
|
||||
}
|
||||
|
||||
- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component
|
||||
{
|
||||
return [self.streamConfigVals objectAtIndex:row];
|
||||
}
|
||||
|
||||
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component
|
||||
{
|
||||
//TODO: figure out how to save this info!!
|
||||
}
|
||||
|
||||
// returns the number of 'columns' to display.
|
||||
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
// returns the # of rows in each component..
|
||||
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component
|
||||
{
|
||||
return 4;
|
||||
}
|
||||
|
||||
- (void)viewDidLoad
|
||||
{
|
||||
[super viewDidLoad];
|
||||
// Do any additional setup after loading the view.
|
||||
self.streamConfigVals = [[NSArray alloc] initWithObjects:@"1280x720 (30Hz)",@"1280x720 (60Hz)",@"1920x1080 (30Hz)",@"1920x1080 (60Hz)",nil];
|
||||
self.HostField.text = @"neyer.case.edu";
|
||||
}
|
||||
|
||||
- (void)didReceiveMemoryWarning
|
||||
{
|
||||
[super didReceiveMemoryWarning];
|
||||
// Dispose of any resources that can be recreated.
|
||||
}
|
||||
@end
|
||||
@@ -0,0 +1,14 @@
|
||||
//
|
||||
// StreamFrameViewController.h
|
||||
// Limelight-iOS
|
||||
//
|
||||
// Created by Diego Waxemberg on 1/18/14.
|
||||
// Copyright (c) 2014 Diego Waxemberg. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "StreamView.h"
|
||||
|
||||
@interface StreamFrameViewController : UIViewController
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,61 @@
|
||||
//
|
||||
// StreamFrameViewController.m
|
||||
// Limelight-iOS
|
||||
//
|
||||
// Created by Diego Waxemberg on 1/18/14.
|
||||
// Copyright (c) 2014 Diego Waxemberg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "StreamFrameViewController.h"
|
||||
#import "MainFrameViewController.h"
|
||||
#import "VideoDepacketizer.h"
|
||||
#import "Connection.h"
|
||||
#import "VideoRenderer.h"
|
||||
#import "ConnectionHandler.h"
|
||||
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
@interface StreamFrameViewController ()
|
||||
|
||||
@end
|
||||
|
||||
@implementation StreamFrameViewController
|
||||
|
||||
- (void)viewDidLoad
|
||||
{
|
||||
[super viewDidLoad];
|
||||
|
||||
[UIApplication sharedApplication].idleTimerDisabled = YES;
|
||||
|
||||
StreamView* streamView = [[StreamView alloc] initWithFrame:self.view.frame];
|
||||
streamView.backgroundColor = [UIColor blackColor];
|
||||
|
||||
[self.view addSubview:streamView];
|
||||
[streamView setNeedsDisplay];
|
||||
|
||||
CGAffineTransform transform = CGAffineTransformMakeTranslation((streamView.frame.size.height/2) - (streamView.frame.size.width/2), (streamView.frame.size.width/2) - (streamView.frame.size.height/2));
|
||||
transform = CGAffineTransformRotate(transform, M_PI_2);
|
||||
transform = CGAffineTransformScale(transform, -1, -1);
|
||||
streamView.transform = transform;
|
||||
|
||||
// Repositions and resizes the view.
|
||||
CGRect contentRect = CGRectMake(0,0, self.view.frame.size.width, self.view.frame.size.height);
|
||||
streamView.bounds = contentRect;
|
||||
|
||||
Connection* conn = [[Connection alloc] initWithHost:inet_addr([[ConnectionHandler resolveHost:[NSString stringWithUTF8String:[MainFrameViewController getHostAddr]]] UTF8String]) width:1280 height:720];
|
||||
|
||||
NSOperationQueue* opQueue = [[NSOperationQueue alloc] init];
|
||||
[opQueue addOperation:conn];
|
||||
[opQueue addOperation:[[VideoRenderer alloc]initWithTarget:streamView]];
|
||||
|
||||
}
|
||||
|
||||
- (void)didReceiveMemoryWarning
|
||||
{
|
||||
[super didReceiveMemoryWarning];
|
||||
// Dispose of any resources that can be recreated.
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,13 @@
|
||||
//
|
||||
// StreamView.h
|
||||
// Limelight-iOS
|
||||
//
|
||||
// Created by Diego Waxemberg on 1/18/14.
|
||||
// Copyright (c) 2014 Diego Waxemberg. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface StreamView : UIView
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,73 @@
|
||||
//
|
||||
// StreamView.m
|
||||
// Limelight-iOS
|
||||
//
|
||||
// Created by Diego Waxemberg on 1/18/14.
|
||||
// Copyright (c) 2014 Diego Waxemberg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "StreamView.h"
|
||||
#import "VideoDecoder.h"
|
||||
#import "VideoRenderer.h"
|
||||
|
||||
@implementation StreamView {
|
||||
size_t width;
|
||||
size_t height;
|
||||
size_t bitsPerComponent;
|
||||
size_t bytesPerRow;
|
||||
CGColorSpaceRef colorSpace;
|
||||
CGContextRef bitmapContext;
|
||||
CGImageRef image;
|
||||
unsigned char* pixelData;
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)frame
|
||||
{
|
||||
self = [super initWithFrame:frame];
|
||||
|
||||
// Initialization code
|
||||
|
||||
width = 1280;
|
||||
height = 720;
|
||||
bitsPerComponent = 8;
|
||||
bytesPerRow = (bitsPerComponent / 8) * width * 4;
|
||||
pixelData = malloc(width * height * 4);
|
||||
colorSpace = CGColorSpaceCreateDeviceRGB();
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Only override drawRect: if you perform custom drawing.
|
||||
// An empty implementation adversely affects performance during animation.
|
||||
- (void)drawRect:(CGRect)rect
|
||||
{
|
||||
if (![VideoRenderer isRendering]) {
|
||||
return;
|
||||
}
|
||||
if (!nv_avc_get_rgb_frame((char*)pixelData, width*height*4))
|
||||
{
|
||||
//NSLog(@"no new decoded frame!");
|
||||
//return;
|
||||
}
|
||||
|
||||
bitmapContext = CGBitmapContextCreate(pixelData, width, height, bitsPerComponent, bytesPerRow, colorSpace, kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Little);
|
||||
image = CGBitmapContextCreateImage(bitmapContext);
|
||||
|
||||
struct CGContext* context = UIGraphicsGetCurrentContext();
|
||||
|
||||
CGContextSetBlendMode(context, kCGBlendModeCopy);
|
||||
CGContextSetInterpolationQuality(context, kCGInterpolationNone);
|
||||
CGContextSetShouldAntialias(context, false);
|
||||
CGContextRotateCTM(context, -M_PI_2);
|
||||
CGContextScaleCTM(context, -(float)self.frame.size.width/self.frame.size.height, (float)self.frame.size.height/self.frame.size.width);
|
||||
CGContextDrawImage(context, rect, image);
|
||||
|
||||
CGImageRelease(image);
|
||||
|
||||
[super drawRect:rect];
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,46 @@
|
||||
//
|
||||
// VideoDecoder.h
|
||||
// Limelight-iOS
|
||||
//
|
||||
// Created by Diego Waxemberg on 1/18/14.
|
||||
// Copyright (c) 2014 Diego Waxemberg. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
// Disables the deblocking filter at the cost of image quality
|
||||
#define DISABLE_LOOP_FILTER 0x1
|
||||
// Uses the low latency decode flag (disables multithreading)
|
||||
#define LOW_LATENCY_DECODE 0x2
|
||||
// Threads process each slice, rather than each frame
|
||||
#define SLICE_THREADING 0x4
|
||||
// Uses nonstandard speedup tricks
|
||||
#define FAST_DECODE 0x8
|
||||
// Uses bilinear filtering instead of bicubic
|
||||
#define BILINEAR_FILTERING 0x10
|
||||
// Uses a faster bilinear filtering with lower image quality
|
||||
#define FAST_BILINEAR_FILTERING 0x20
|
||||
// Disables color conversion (output is NV21)
|
||||
#define NO_COLOR_CONVERSION 0x40
|
||||
// Native color format: RGB0
|
||||
#define NATIVE_COLOR_RGB0 0x80
|
||||
// Native color format: 0RGB
|
||||
#define NATIVE_COLOR_0RGB 0x100
|
||||
// Native color format: ARGB
|
||||
#define NATIVE_COLOR_ARGB 0x200
|
||||
// Native color format: RGBA
|
||||
#define NATIVE_COLOR_RGBA 0x400
|
||||
|
||||
@interface VideoDecoder : NSObject
|
||||
int nv_avc_init(int width, int height, int perf_lvl, int thread_count);
|
||||
void nv_avc_destroy(void);
|
||||
|
||||
int nv_avc_get_raw_frame(char* buffer, int size);
|
||||
|
||||
int nv_avc_get_rgb_frame(char* buffer, int size);
|
||||
int nv_avc_get_rgb_frame_int(int* buffer, int size);
|
||||
int nv_avc_redraw(void);
|
||||
|
||||
int nv_avc_get_input_padding_size(void);
|
||||
int nv_avc_decode(unsigned char* indata, int inlen);
|
||||
@end
|
||||
@@ -0,0 +1,335 @@
|
||||
//
|
||||
// VideoDecoder.m
|
||||
// Limelight-iOS
|
||||
//
|
||||
// Created by Diego Waxemberg on 1/18/14.
|
||||
// Copyright (c) 2014 Diego Waxemberg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "VideoDecoder.h"
|
||||
#import "avcodec.h"
|
||||
#import "swscale.h"
|
||||
#include <pthread.h>
|
||||
|
||||
@implementation VideoDecoder
|
||||
- (id) init
|
||||
{
|
||||
self = [super init];
|
||||
return self;
|
||||
}
|
||||
|
||||
// General decoder and renderer state
|
||||
AVPacket pkt;
|
||||
AVCodec* decoder;
|
||||
AVCodecContext* decoder_ctx;
|
||||
AVFrame* yuv_frame;
|
||||
AVFrame* dec_frame;
|
||||
pthread_mutex_t mutex;
|
||||
|
||||
// Color conversion and rendering
|
||||
AVFrame* rgb_frame;
|
||||
char* rgb_frame_buf;
|
||||
struct SwsContext* scaler_ctx;
|
||||
int render_pix_fmt;
|
||||
|
||||
#define BYTES_PER_PIXEL 4
|
||||
|
||||
// This function must be called before
|
||||
// any other decoding functions
|
||||
int nv_avc_init(int width, int height, int perf_lvl, int thread_count) {
|
||||
int err;
|
||||
int filtering;
|
||||
|
||||
pthread_mutex_init(&mutex, NULL);
|
||||
|
||||
// Initialize the avcodec library and register codecs
|
||||
av_log_set_level(AV_LOG_QUIET);
|
||||
avcodec_register_all();
|
||||
|
||||
av_init_packet(&pkt);
|
||||
|
||||
decoder = avcodec_find_decoder(AV_CODEC_ID_H264);
|
||||
if (decoder == NULL) {
|
||||
// __android_log_write(ANDROID_LOG_ERROR, "NVAVCDEC",
|
||||
// "Couldn't find H264 decoder");
|
||||
return -1;
|
||||
}
|
||||
|
||||
decoder_ctx = avcodec_alloc_context3(decoder);
|
||||
if (decoder_ctx == NULL) {
|
||||
//__android_log_write(ANDROID_LOG_ERROR, "NVAVCDEC",
|
||||
// "Couldn't allocate context");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Show frames even before a reference frame
|
||||
decoder_ctx->flags2 |= CODEC_FLAG2_SHOW_ALL;
|
||||
|
||||
if (perf_lvl & DISABLE_LOOP_FILTER) {
|
||||
// Skip the loop filter for performance reasons
|
||||
decoder_ctx->skip_loop_filter = AVDISCARD_ALL;
|
||||
}
|
||||
|
||||
if (perf_lvl & LOW_LATENCY_DECODE) {
|
||||
// Use low delay single threaded encoding
|
||||
decoder_ctx->flags |= CODEC_FLAG_LOW_DELAY;
|
||||
}
|
||||
|
||||
if (perf_lvl & SLICE_THREADING) {
|
||||
decoder_ctx->thread_type = FF_THREAD_SLICE;
|
||||
}
|
||||
else {
|
||||
decoder_ctx->thread_type = FF_THREAD_FRAME;
|
||||
}
|
||||
|
||||
decoder_ctx->thread_count = thread_count;
|
||||
|
||||
decoder_ctx->width = width;
|
||||
decoder_ctx->height = height;
|
||||
decoder_ctx->pix_fmt = PIX_FMT_YUV420P;
|
||||
|
||||
render_pix_fmt = AV_PIX_FMT_BGR0;
|
||||
|
||||
err = avcodec_open2(decoder_ctx, decoder, NULL);
|
||||
if (err < 0) {
|
||||
//__android_log_write(ANDROID_LOG_ERROR, "NVAVCDEC",
|
||||
// "Couldn't open codec");
|
||||
return err;
|
||||
}
|
||||
|
||||
dec_frame = av_frame_alloc();
|
||||
if (dec_frame == NULL) {
|
||||
//__android_log_write(ANDROID_LOG_ERROR, "NVAVCDEC",
|
||||
// "Couldn't allocate frame");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!(perf_lvl & NO_COLOR_CONVERSION)) {
|
||||
rgb_frame = av_frame_alloc();
|
||||
if (rgb_frame == NULL) {
|
||||
//__android_log_write(ANDROID_LOG_ERROR, "NVAVCDEC",
|
||||
// "Couldn't allocate frame");
|
||||
return -1;
|
||||
}
|
||||
|
||||
rgb_frame_buf = (char*)av_malloc(width * height * BYTES_PER_PIXEL);
|
||||
if (rgb_frame_buf == NULL) {
|
||||
//__android_log_write(ANDROID_LOG_ERROR, "NVAVCDEC",
|
||||
// "Couldn't allocate picture");
|
||||
return -1;
|
||||
}
|
||||
|
||||
err = avpicture_fill((AVPicture*)rgb_frame,
|
||||
rgb_frame_buf,
|
||||
render_pix_fmt,
|
||||
decoder_ctx->width,
|
||||
decoder_ctx->height);
|
||||
if (err < 0) {
|
||||
//__android_log_write(ANDROID_LOG_ERROR, "NVAVCDEC",
|
||||
// "Couldn't fill picture");
|
||||
return err;
|
||||
}
|
||||
|
||||
if (perf_lvl & FAST_BILINEAR_FILTERING) {
|
||||
filtering = SWS_FAST_BILINEAR;
|
||||
}
|
||||
else if (perf_lvl & BILINEAR_FILTERING) {
|
||||
filtering = SWS_BILINEAR;
|
||||
}
|
||||
else {
|
||||
filtering = SWS_BICUBIC;
|
||||
}
|
||||
|
||||
scaler_ctx = sws_getContext(decoder_ctx->width,
|
||||
decoder_ctx->height,
|
||||
decoder_ctx->pix_fmt,
|
||||
decoder_ctx->width,
|
||||
decoder_ctx->height,
|
||||
render_pix_fmt,
|
||||
filtering,
|
||||
NULL, NULL, NULL);
|
||||
if (scaler_ctx == NULL) {
|
||||
//__android_log_write(ANDROID_LOG_ERROR, "NVAVCDEC",
|
||||
// "Couldn't get scaler context");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// This function must be called after
|
||||
// decoding is finished
|
||||
void nv_avc_destroy(void) {
|
||||
if (decoder_ctx) {
|
||||
avcodec_close(decoder_ctx);
|
||||
av_free(decoder_ctx);
|
||||
decoder_ctx = NULL;
|
||||
}
|
||||
if (scaler_ctx) {
|
||||
sws_freeContext(scaler_ctx);
|
||||
scaler_ctx = NULL;
|
||||
}
|
||||
if (dec_frame) {
|
||||
av_frame_free(&dec_frame);
|
||||
dec_frame = NULL;
|
||||
}
|
||||
if (yuv_frame) {
|
||||
av_frame_free(&yuv_frame);
|
||||
yuv_frame = NULL;
|
||||
}
|
||||
if (rgb_frame) {
|
||||
av_frame_free(&rgb_frame);
|
||||
rgb_frame = NULL;
|
||||
}
|
||||
if (rgb_frame_buf) {
|
||||
av_free(rgb_frame_buf);
|
||||
rgb_frame_buf = NULL;
|
||||
}
|
||||
|
||||
pthread_mutex_destroy(&mutex);
|
||||
}
|
||||
|
||||
static AVFrame* dequeue_new_frame(void) {
|
||||
AVFrame *our_yuv_frame = NULL;
|
||||
|
||||
pthread_mutex_lock(&mutex);
|
||||
|
||||
// Check if there's a new frame
|
||||
if (yuv_frame) {
|
||||
// We now own the decoder's frame and are
|
||||
// responsible for freeing it when we're done
|
||||
our_yuv_frame = yuv_frame;
|
||||
yuv_frame = NULL;
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&mutex);
|
||||
|
||||
return our_yuv_frame;
|
||||
}
|
||||
|
||||
static int update_rgb_frame(void) {
|
||||
AVFrame *our_yuv_frame;
|
||||
int err;
|
||||
|
||||
our_yuv_frame = dequeue_new_frame();
|
||||
if (our_yuv_frame == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Convert the YUV image to RGB
|
||||
err = sws_scale(scaler_ctx,
|
||||
our_yuv_frame->data,
|
||||
our_yuv_frame->linesize,
|
||||
0,
|
||||
decoder_ctx->height,
|
||||
rgb_frame->data,
|
||||
rgb_frame->linesize);
|
||||
|
||||
av_frame_free(&our_yuv_frame);
|
||||
|
||||
if (err != decoder_ctx->height) {
|
||||
//__android_log_write(ANDROID_LOG_ERROR, "NVAVCDEC",
|
||||
// "Scaling failed");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int render_rgb_to_buffer(char* buffer, int size) {
|
||||
int err;
|
||||
|
||||
// Draw the frame to the buffer
|
||||
err = avpicture_layout((AVPicture*)rgb_frame,
|
||||
render_pix_fmt,
|
||||
decoder_ctx->width,
|
||||
decoder_ctx->height,
|
||||
buffer,
|
||||
size);
|
||||
if (err < 0) {
|
||||
// __android_log_write(ANDROID_LOG_ERROR, "NVAVCDEC",
|
||||
// "Picture fill failed");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int nv_avc_get_raw_frame(char* buffer, int size) {
|
||||
AVFrame *our_yuv_frame;
|
||||
int err;
|
||||
|
||||
our_yuv_frame = dequeue_new_frame();
|
||||
if (our_yuv_frame == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
err = avpicture_layout((AVPicture*)our_yuv_frame,
|
||||
decoder_ctx->pix_fmt,
|
||||
decoder_ctx->width,
|
||||
decoder_ctx->height,
|
||||
buffer,
|
||||
size);
|
||||
|
||||
av_frame_free(&our_yuv_frame);
|
||||
|
||||
return (err >= 0);
|
||||
}
|
||||
|
||||
int nv_avc_get_rgb_frame(char* buffer, int size) {
|
||||
return (update_rgb_frame() && render_rgb_to_buffer(buffer, size));
|
||||
}
|
||||
|
||||
int nv_avc_get_input_padding_size(void) {
|
||||
return FF_INPUT_BUFFER_PADDING_SIZE;
|
||||
}
|
||||
|
||||
// packets must be decoded in order
|
||||
// indata must be inlen + FF_INPUT_BUFFER_PADDING_SIZE in length
|
||||
int nv_avc_decode(unsigned char* indata, int inlen) {
|
||||
int err = 0;
|
||||
int got_pic = 0;
|
||||
|
||||
pkt.data = indata;
|
||||
pkt.size = inlen;
|
||||
|
||||
while (pkt.size > 0) {
|
||||
got_pic = 0;
|
||||
err = avcodec_decode_video2(
|
||||
decoder_ctx,
|
||||
dec_frame,
|
||||
&got_pic,
|
||||
&pkt);
|
||||
if (err < 0) {
|
||||
//__android_log_write(ANDROID_LOG_ERROR, "NVAVCDEC",
|
||||
// "Decode failed");
|
||||
got_pic = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
pkt.size -= err;
|
||||
pkt.data += err;
|
||||
}
|
||||
|
||||
// Only copy the picture at the end of decoding the packet
|
||||
if (got_pic) {
|
||||
pthread_mutex_lock(&mutex);
|
||||
|
||||
// Only clone this frame if the last frame was taken.
|
||||
// This saves on extra copies for frames that don't get
|
||||
// rendered.
|
||||
if (yuv_frame == NULL) {
|
||||
// Clone a new frame
|
||||
yuv_frame = av_frame_clone(dec_frame);
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&mutex);
|
||||
}
|
||||
|
||||
return err < 0 ? err : 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,21 @@
|
||||
//
|
||||
// VideoDepacketizer.h
|
||||
// Limelight-iOS
|
||||
//
|
||||
// Created by Diego Waxemberg on 1/18/14.
|
||||
// Copyright (c) 2014 Diego Waxemberg. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "VideoDecoder.h"
|
||||
|
||||
@interface VideoDepacketizer : NSOperation <NSStreamDelegate>
|
||||
@property uint8_t* byteBuffer;
|
||||
@property unsigned int offset;
|
||||
@property VideoDecoder* decoder;
|
||||
@property NSString* file;
|
||||
@property UIView* target;
|
||||
|
||||
- (id) initWithFile:(NSString*) file renderTarget:(UIView*)renderTarget;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,78 @@
|
||||
//
|
||||
// VideoDepacketizer.m
|
||||
// Limelight-iOS
|
||||
//
|
||||
// Created by Diego Waxemberg on 1/18/14.
|
||||
// Copyright (c) 2014 Diego Waxemberg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "VideoDepacketizer.h"
|
||||
|
||||
@implementation VideoDepacketizer
|
||||
static int BUFFER_LENGTH = 131072;
|
||||
|
||||
- (id)initWithFile:(NSString *)file renderTarget:(UIView*)renderTarget
|
||||
{
|
||||
self = [super init];
|
||||
self.file = file;
|
||||
self.target = renderTarget;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)main
|
||||
{
|
||||
NSInputStream* inStream = [[NSInputStream alloc] initWithFileAtPath:self.file];
|
||||
self.byteBuffer = malloc(BUFFER_LENGTH);
|
||||
self.decoder = [[VideoDecoder alloc]init];
|
||||
|
||||
|
||||
[inStream open];
|
||||
while ([inStream streamStatus] != NSStreamStatusOpen) {
|
||||
NSLog(@"stream status: %d", [inStream streamStatus]);
|
||||
sleep(1);
|
||||
}
|
||||
while (true)
|
||||
{
|
||||
unsigned int len = 0;
|
||||
|
||||
len = [inStream read:self.byteBuffer maxLength:BUFFER_LENGTH];
|
||||
if (len)
|
||||
{
|
||||
BOOL firstStart = false;
|
||||
for (int i = 0; i < len - 4; i++) {
|
||||
self.offset++;
|
||||
if (self.byteBuffer[i] == 0 && self.byteBuffer[i+1] == 0
|
||||
&& self.byteBuffer[i+2] == 0 && self.byteBuffer[i+3] == 1)
|
||||
{
|
||||
if (firstStart)
|
||||
{
|
||||
// decode the first i-1 bytes and render a frame
|
||||
//[self.decoder decode:self.byteBuffer length:i];
|
||||
[self.target performSelectorOnMainThread:@selector(setNeedsDisplay) withObject:NULL waitUntilDone:FALSE];
|
||||
|
||||
// move offset back to beginning of start sequence
|
||||
[inStream setProperty:[[NSNumber alloc] initWithInt:self.offset-4] forKey:NSStreamFileCurrentOffsetKey];
|
||||
self.offset -= 1;
|
||||
|
||||
break;
|
||||
} else
|
||||
{
|
||||
firstStart = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
NSLog(@"No Buffer! restarting file!");
|
||||
// move offset back to beginning of start sequence
|
||||
self.offset = 0;
|
||||
[inStream close];
|
||||
inStream = [[NSInputStream alloc] initWithFileAtPath:self.file];
|
||||
[inStream open];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,18 @@
|
||||
//
|
||||
// VideoRenderer.h
|
||||
// Limelight-iOS
|
||||
//
|
||||
// Created by Diego Waxemberg on 1/19/14.
|
||||
// Copyright (c) 2014 Diego Waxemberg. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface VideoRenderer : NSOperation
|
||||
@property UIView* renderTarget;
|
||||
- (id) initWithTarget:(UIView*)target;
|
||||
+ (void) startRendering;
|
||||
+ (void) stopRendering;
|
||||
+ (BOOL) isRendering;
|
||||
@end
|
||||
@@ -0,0 +1,51 @@
|
||||
//
|
||||
// VideoRenderer.m
|
||||
// Limelight-iOS
|
||||
//
|
||||
// Created by Diego Waxemberg on 1/19/14.
|
||||
// Copyright (c) 2014 Diego Waxemberg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "VideoRenderer.h"
|
||||
|
||||
@implementation VideoRenderer
|
||||
static bool render = false;
|
||||
|
||||
- (id)initWithTarget:(UIView *)target
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
self.renderTarget = target;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)main
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (render)
|
||||
{
|
||||
[self.renderTarget performSelectorOnMainThread:@selector(setNeedsDisplay) withObject:NULL waitUntilDone:TRUE];
|
||||
usleep(10000);
|
||||
} else {
|
||||
sleep(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+ (void) startRendering
|
||||
{
|
||||
render = true;
|
||||
}
|
||||
|
||||
+ (void) stopRendering
|
||||
{
|
||||
render = false;
|
||||
}
|
||||
|
||||
+ (BOOL) isRendering
|
||||
{
|
||||
return render;
|
||||
}
|
||||
@end
|
||||
Reference in New Issue
Block a user