Merge branch 'master' of github.com:limelight-stream/limelight-ios

# By Cameron Gutman
# Via Cameron Gutman
* 'master' of github.com:limelight-stream/limelight-ios:
  Improve touch input support
  Add some WIP touch input support
  Implement working audio support
  Video fully works now
This commit is contained in:
Diego Waxemberg 2014-10-20 02:59:50 -04:00
commit 42773241c1
8 changed files with 250 additions and 65 deletions

View File

@ -7,6 +7,7 @@
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
984C441819F48D1D0061A500 /* StreamView.m in Sources */ = {isa = PBXBuildFile; fileRef = 984C441719F48D1D0061A500 /* StreamView.m */; };
98A03B4D19F352EB00861ACA /* liblimelight-common.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 98A03B4A19F3514B00861ACA /* liblimelight-common.a */; }; 98A03B4D19F352EB00861ACA /* liblimelight-common.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 98A03B4A19F3514B00861ACA /* liblimelight-common.a */; };
98A03B5019F3598400861ACA /* VideoDecoderRenderer.m in Sources */ = {isa = PBXBuildFile; fileRef = 98A03B4F19F3598400861ACA /* VideoDecoderRenderer.m */; }; 98A03B5019F3598400861ACA /* VideoDecoderRenderer.m in Sources */ = {isa = PBXBuildFile; fileRef = 98A03B4F19F3598400861ACA /* VideoDecoderRenderer.m */; };
98A03B5119F35AAC00861ACA /* libcrypto.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FBCC0E9819EF9703009729EB /* libcrypto.a */; }; 98A03B5119F35AAC00861ACA /* libcrypto.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FBCC0E9819EF9703009729EB /* libcrypto.a */; };
@ -77,6 +78,8 @@
/* End PBXContainerItemProxy section */ /* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
984C441619F48D1D0061A500 /* StreamView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StreamView.h; sourceTree = "<group>"; };
984C441719F48D1D0061A500 /* StreamView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = StreamView.m; sourceTree = "<group>"; };
98A03B4519F3514B00861ACA /* limelight-common.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = "limelight-common.xcodeproj"; path = "limelight-common-c/limelight-common.xcodeproj"; sourceTree = "<group>"; }; 98A03B4519F3514B00861ACA /* limelight-common.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = "limelight-common.xcodeproj"; path = "limelight-common-c/limelight-common.xcodeproj"; sourceTree = "<group>"; };
98A03B4E19F3598400861ACA /* VideoDecoderRenderer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = VideoDecoderRenderer.h; path = Limelight/VideoDecoderRenderer.h; sourceTree = SOURCE_ROOT; }; 98A03B4E19F3598400861ACA /* VideoDecoderRenderer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = VideoDecoderRenderer.h; path = Limelight/VideoDecoderRenderer.h; sourceTree = SOURCE_ROOT; };
98A03B4F19F3598400861ACA /* VideoDecoderRenderer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = VideoDecoderRenderer.m; path = Limelight/VideoDecoderRenderer.m; sourceTree = SOURCE_ROOT; }; 98A03B4F19F3598400861ACA /* VideoDecoderRenderer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = VideoDecoderRenderer.m; path = Limelight/VideoDecoderRenderer.m; sourceTree = SOURCE_ROOT; };
@ -322,6 +325,8 @@
FBCC0E9C19F00659009729EB /* mkcert.c */, FBCC0E9C19F00659009729EB /* mkcert.c */,
FBC8622B19F0BEFB0087327B /* HttpManager.h */, FBC8622B19F0BEFB0087327B /* HttpManager.h */,
FBC8622C19F0BEFB0087327B /* HttpManager.m */, FBC8622C19F0BEFB0087327B /* HttpManager.m */,
984C441619F48D1D0061A500 /* StreamView.h */,
984C441719F48D1D0061A500 /* StreamView.m */,
); );
path = Limelight; path = Limelight;
sourceTree = "<group>"; sourceTree = "<group>";
@ -687,6 +692,7 @@
FB290D3A19B2C6E3004C83CF /* StreamFrameViewController.m in Sources */, FB290D3A19B2C6E3004C83CF /* StreamFrameViewController.m in Sources */,
FB290D0019B2C406004C83CF /* main.m in Sources */, FB290D0019B2C406004C83CF /* main.m in Sources */,
FB290D3919B2C6E3004C83CF /* MainFrameViewController.m in Sources */, FB290D3919B2C6E3004C83CF /* MainFrameViewController.m in Sources */,
984C441819F48D1D0061A500 /* StreamView.m in Sources */,
FB290D3719B2C6E3004C83CF /* Connection.m in Sources */, FB290D3719B2C6E3004C83CF /* Connection.m in Sources */,
FBCC0E9D19F00659009729EB /* mkcert.c in Sources */, FBCC0E9D19F00659009729EB /* mkcert.c in Sources */,
FB290D3819B2C6E3004C83CF /* ConnectionHandler.m in Sources */, FB290D3819B2C6E3004C83CF /* ConnectionHandler.m in Sources */,

View File

@ -28,10 +28,17 @@ static OpusDecoder *opusDecoder;
#define PCM_BUFFER_SIZE 1024 #define PCM_BUFFER_SIZE 1024
#define OUTPUT_BUS 0 #define OUTPUT_BUS 0
static short* decodedPcmBuffer; struct AUDIO_BUFFER_QUEUE_ENTRY {
static int filledPcmBuffer; struct AUDIO_BUFFER_QUEUE_ENTRY *next;
int length;
int offset;
char data[0];
};
static short decodedPcmBuffer[512];
static NSLock *audioLock;
static struct AUDIO_BUFFER_QUEUE_ENTRY *audioBufferQueue;
static AudioComponentInstance audioUnit; static AudioComponentInstance audioUnit;
static bool started = false;
static VideoDecoderRenderer* renderer; static VideoDecoderRenderer* renderer;
void DrSetup(int width, int height, int fps, void* context, int drFlags) void DrSetup(int width, int height, int fps, void* context, int drFlags)
@ -81,18 +88,13 @@ void ArInit(void)
opusDecoder = opus_decoder_create(48000, 2, &err); opusDecoder = opus_decoder_create(48000, 2, &err);
decodedPcmBuffer = malloc(PCM_BUFFER_SIZE); audioLock = [[NSLock alloc] init];
} }
void ArRelease(void) void ArRelease(void)
{ {
printf("Release audio\n"); printf("Release audio\n");
if (decodedPcmBuffer != NULL) {
free(decodedPcmBuffer);
decodedPcmBuffer = NULL;
}
if (opusDecoder != NULL) { if (opusDecoder != NULL) {
opus_decoder_destroy(opusDecoder); opus_decoder_destroy(opusDecoder);
opusDecoder = NULL; opusDecoder = NULL;
@ -102,6 +104,7 @@ void ArRelease(void)
void ArStart(void) void ArStart(void)
{ {
printf("Start audio\n"); printf("Start audio\n");
AudioOutputUnitStart(audioUnit);
} }
void ArStop(void) void ArStop(void)
@ -111,17 +114,31 @@ void ArStop(void)
void ArDecodeAndPlaySample(char* sampleData, int sampleLength) void ArDecodeAndPlaySample(char* sampleData, int sampleLength)
{ {
int decodedLength = opus_decode(opusDecoder, (unsigned char*)sampleData, sampleLength, decodedPcmBuffer, PCM_BUFFER_SIZE / 2, 0);
if (!started) { if (decodedLength > 0) {
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 // Return of opus_decode is samples per channel
filledPcmBuffer *= 4; decodedLength *= 4;
NSLog(@"pcmBuffer: %d", filledPcmBuffer); struct AUDIO_BUFFER_QUEUE_ENTRY *newEntry = malloc(sizeof(*newEntry) + decodedLength);
if (newEntry != NULL) {
newEntry->next = NULL;
newEntry->length = decodedLength;
newEntry->offset = 0;
memcpy(newEntry->data, decodedPcmBuffer, decodedLength);
[audioLock lock];
if (audioBufferQueue == NULL) {
audioBufferQueue = newEntry;
}
else {
struct AUDIO_BUFFER_QUEUE_ENTRY *lastEntry = audioBufferQueue;
while (lastEntry->next != NULL) {
lastEntry = lastEntry->next;
}
lastEntry->next = newEntry;
}
[audioLock unlock];
}
} }
} }
@ -193,15 +210,15 @@ void ClDisplayTransientMessage(char* message)
clCallbacks.displayMessage = ClDisplayMessage; clCallbacks.displayMessage = ClDisplayMessage;
clCallbacks.displayTransientMessage = ClDisplayTransientMessage; clCallbacks.displayTransientMessage = ClDisplayTransientMessage;
//////// Don't think any of this is used ///////// // Configure the audio session for our app
NSError *audioSessionError = nil; NSError *audioSessionError = nil;
AVAudioSession* audioSession = [AVAudioSession sharedInstance]; AVAudioSession* audioSession = [AVAudioSession sharedInstance];
[audioSession setPreferredSampleRate:48000.0 error:&audioSessionError];
[audioSession setPreferredSampleRate:48000.0 error:&audioSessionError];
[audioSession setCategory: AVAudioSessionCategoryPlayAndRecord error: &audioSessionError]; [audioSession setCategory: AVAudioSessionCategoryPlayAndRecord error: &audioSessionError];
[audioSession setPreferredOutputNumberOfChannels:2 error:&audioSessionError]; [audioSession setPreferredOutputNumberOfChannels:2 error:&audioSessionError];
[audioSession setPreferredIOBufferDuration:0.005 error:&audioSessionError];
[audioSession setActive: YES error: &audioSessionError]; [audioSession setActive: YES error: &audioSessionError];
//////////////////////////////////////////////////
OSStatus status; OSStatus status;
@ -217,27 +234,26 @@ void ClDisplayTransientMessage(char* message)
if (status) { if (status) {
NSLog(@"Unable to instantiate new AudioComponent: %d", (int32_t)status); NSLog(@"Unable to instantiate new AudioComponent: %d", (int32_t)status);
} }
AudioStreamBasicDescription audioFormat = {0}; AudioStreamBasicDescription audioFormat = {0};
audioFormat.mSampleRate = 48000; audioFormat.mSampleRate = 48000;
audioFormat.mBitsPerChannel = 16; audioFormat.mBitsPerChannel = 16;
audioFormat.mFormatID = kAudioFormatLinearPCM; audioFormat.mFormatID = kAudioFormatLinearPCM;
audioFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger; audioFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked;
audioFormat.mFramesPerPacket = 1;
audioFormat.mChannelsPerFrame = 2; audioFormat.mChannelsPerFrame = 2;
audioFormat.mBytesPerFrame = 960; audioFormat.mBytesPerFrame = audioFormat.mChannelsPerFrame * (audioFormat.mBitsPerChannel / 8);
audioFormat.mBytesPerPacket = 960; audioFormat.mBytesPerPacket = audioFormat.mBytesPerFrame;
audioFormat.mFramesPerPacket = audioFormat.mBytesPerPacket / audioFormat.mBytesPerFrame;
audioFormat.mReserved = 0; audioFormat.mReserved = 0;
status = AudioUnitSetProperty(audioUnit, status = AudioUnitSetProperty(audioUnit,
kAudioUnitProperty_StreamFormat, kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Output, kAudioUnitScope_Input,
OUTPUT_BUS, OUTPUT_BUS,
&audioFormat, &audioFormat,
sizeof(audioFormat)); sizeof(audioFormat));
if (status) { if (status) {
NSLog(@"Unable to set audio unit to output: %d", (int32_t)status); NSLog(@"Unable to set audio unit to input: %d", (int32_t)status);
} }
AURenderCallbackStruct callbackStruct = {0}; AURenderCallbackStruct callbackStruct = {0};
@ -246,7 +262,7 @@ void ClDisplayTransientMessage(char* message)
status = AudioUnitSetProperty(audioUnit, status = AudioUnitSetProperty(audioUnit,
kAudioUnitProperty_SetRenderCallback, kAudioUnitProperty_SetRenderCallback,
kAudioUnitScope_Global, kAudioUnitScope_Input,
OUTPUT_BUS, OUTPUT_BUS,
&callbackStruct, &callbackStruct,
sizeof(callbackStruct)); sizeof(callbackStruct));
@ -272,15 +288,60 @@ static OSStatus playbackCallback(void *inRefCon,
// Fill them up as much as you can. Remember to set the size value in each buffer to match how // 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. // much data is in the buffer.
NSLog(@"Playback callback");
for (int i = 0; i < ioData->mNumberBuffers; i++) { for (int i = 0; i < ioData->mNumberBuffers; i++) {
ioData->mBuffers[i].mNumberChannels = 2; ioData->mBuffers[i].mNumberChannels = 2;
int min = MIN(ioData->mBuffers[i].mDataByteSize, filledPcmBuffer);
NSLog(@"Min: %d", min); if (ioData->mBuffers[i].mDataByteSize != 0) {
memcpy(ioData->mBuffers[i].mData, decodedPcmBuffer, min); int thisBufferOffset = 0;
ioData->mBuffers[i].mDataByteSize = min;
filledPcmBuffer -= min; FillBufferAgain:
// Make sure there's data to write
if (ioData->mBuffers[i].mDataByteSize - thisBufferOffset == 0) {
continue;
}
// Wait for a buffer to be available
// FIXME: This needs optimization to avoid busy waiting for buffers
struct AUDIO_BUFFER_QUEUE_ENTRY *audioEntry = NULL;
while (audioEntry == NULL)
{
[audioLock lock];
if (audioBufferQueue != NULL) {
// Dequeue this entry temporarily
audioEntry = audioBufferQueue;
audioBufferQueue = audioBufferQueue->next;
}
[audioLock unlock];
}
// Figure out how much data we can write
int min = MIN(ioData->mBuffers[i].mDataByteSize - thisBufferOffset, audioEntry->length);
// Copy data to the audio buffer
memcpy(&ioData->mBuffers[i].mData[thisBufferOffset], &audioEntry->data[audioEntry->offset], min);
thisBufferOffset += min;
if (min < audioEntry->length) {
// This entry still has unused data
audioEntry->length -= min;
audioEntry->offset += min;
// Requeue the entry
[audioLock lock];
audioEntry->next = audioBufferQueue;
audioBufferQueue = audioEntry;
[audioLock unlock];
}
else {
// This entry is fully depleted so free it
free(audioEntry);
// Try to grab another sample to fill this buffer with
goto FillBufferAgain;
}
ioData->mBuffers[i].mDataByteSize = thisBufferOffset;
}
} }
return noErr; return noErr;

13
Limelight/StreamView.h Normal file
View File

@ -0,0 +1,13 @@
//
// StreamView.h
// Limelight
//
// Created by Cameron Gutman on 10/19/14.
// Copyright (c) 2014 Limelight Stream. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface StreamView : UIView
@end

59
Limelight/StreamView.m Normal file
View File

@ -0,0 +1,59 @@
//
// StreamView.m
// Limelight
//
// Created by Cameron Gutman on 10/19/14.
// Copyright (c) 2014 Limelight Stream. All rights reserved.
//
#import "StreamView.h"
#include <Limelight.h>
@implementation StreamView {
CGPoint touchLocation;
BOOL touchMoved;
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [[event allTouches] anyObject];
touchLocation = [touch locationInView:self];
touchMoved = false;
NSLog(@"Touch down");
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [[event allTouches] anyObject];
CGPoint currentLocation = [touch locationInView:self];
if (touchLocation.x != currentLocation.x ||
touchLocation.y != currentLocation.y)
{
LiSendMouseMoveEvent(touchLocation.x - currentLocation.x,
touchLocation.y - currentLocation.y);
touchMoved = true;
touchLocation = currentLocation;
}
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(@"Touch up");
if (!touchMoved) {
NSLog(@"Sending left mouse button press");
LiSendMouseButtonEvent(BUTTON_ACTION_PRESS, BUTTON_LEFT);
// Wait 100 ms to simulate a real button press
usleep(100 * 1000);
LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_LEFT);
}
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
}
@end

View File

@ -14,6 +14,8 @@
- (id)initWithView:(UIView*)view; - (id)initWithView:(UIView*)view;
- (size_t)updateBufferForRange:(CMBlockBufferRef)existingBuffer data:(unsigned char *)data offset:(int)offset length:(int)nalLength;
- (void)submitDecodeBuffer:(unsigned char *)data length:(int)length; - (void)submitDecodeBuffer:(unsigned char *)data length:(int)length;
@end @end

View File

@ -25,7 +25,7 @@
displayLayer.bounds = view.bounds; displayLayer.bounds = view.bounds;
displayLayer.backgroundColor = [UIColor greenColor].CGColor; displayLayer.backgroundColor = [UIColor greenColor].CGColor;
displayLayer.position = CGPointMake(CGRectGetMidX(view.bounds), CGRectGetMidY(view.bounds)); displayLayer.position = CGPointMake(CGRectGetMidX(view.bounds), CGRectGetMidY(view.bounds));
displayLayer.videoGravity = AVLayerVideoGravityResize; displayLayer.videoGravity = AVLayerVideoGravityResizeAspect;
[view.layer addSublayer:displayLayer]; [view.layer addSublayer:displayLayer];
// We need some parameter sets before we can properly start decoding frames // We need some parameter sets before we can properly start decoding frames
@ -36,31 +36,66 @@
return self; return self;
} }
#define ES_START_PREFIX_SIZE 4 #define FRAME_START_PREFIX_SIZE 4
#define NALU_START_PREFIX_SIZE 3
- (size_t)updateBufferForRange:(CMBlockBufferRef)existingBuffer data:(unsigned char *)data offset:(int)offset length:(int)nalLength
{
OSStatus status;
size_t oldOffset = CMBlockBufferGetDataLength(existingBuffer);
status = CMBlockBufferAppendMemoryBlock(existingBuffer, NULL,
((4 + nalLength) - NALU_START_PREFIX_SIZE),
kCFAllocatorDefault, NULL, 0,
((4 + nalLength) - NALU_START_PREFIX_SIZE), 0);
if (status != noErr) {
NSLog(@"CMBlockBufferAppendMemoryBlock failed: %d", (int)status);
return 0;
}
int dataLength = nalLength - NALU_START_PREFIX_SIZE;
const uint8_t lengthBytes[] = {(uint8_t)(dataLength >> 24), (uint8_t)(dataLength >> 16),
(uint8_t)(dataLength >> 8), (uint8_t)dataLength};
status = CMBlockBufferReplaceDataBytes(lengthBytes, existingBuffer,
oldOffset, 4);
if (status != noErr) {
NSLog(@"CMBlockBufferReplaceDataBytes failed: %d", (int)status);
return 0;
}
status = CMBlockBufferReplaceDataBytes(&data[offset+NALU_START_PREFIX_SIZE], existingBuffer,
oldOffset + 4, dataLength);
if (status != noErr) {
NSLog(@"CMBlockBufferReplaceDataBytes failed: %d", (int)status);
return 0;
}
return 4 + dataLength;
}
- (void)submitDecodeBuffer:(unsigned char *)data length:(int)length - (void)submitDecodeBuffer:(unsigned char *)data length:(int)length
{ {
unsigned char nalType = data[ES_START_PREFIX_SIZE] & 0x1F; unsigned char nalType = data[FRAME_START_PREFIX_SIZE] & 0x1F;
OSStatus status; OSStatus status;
if (formatDesc == NULL && (nalType == 0x7 || nalType == 0x8)) { if (formatDesc == NULL && (nalType == 0x7 || nalType == 0x8)) {
if (waitingForSps && nalType == 0x7) { if (waitingForSps && nalType == 0x7) {
NSLog(@"Got SPS"); NSLog(@"Got SPS");
spsData = [NSData dataWithBytes:&data[ES_START_PREFIX_SIZE] length:length - ES_START_PREFIX_SIZE]; spsData = [NSData dataWithBytes:&data[FRAME_START_PREFIX_SIZE] length:length - FRAME_START_PREFIX_SIZE];
waitingForSps = false; waitingForSps = false;
} }
// Nvidia's stream has 2 PPS NALUs so we'll wait for both of them // Nvidia's stream has 2 PPS NALUs so we'll wait for both of them
else if ((waitingForPpsA || waitingForPpsB) && nalType == 0x8) { else if ((waitingForPpsA || waitingForPpsB) && nalType == 0x8) {
// Read the NALU's PPS index to figure out which PPS this is // Read the NALU's PPS index to figure out which PPS this is
printf("PPS BYTE: %02x", data[ES_START_PREFIX_SIZE + 1]);
if (waitingForPpsA) { if (waitingForPpsA) {
NSLog(@"Got PPS 1"); NSLog(@"Got PPS 1");
ppsDataA = [NSData dataWithBytes:&data[ES_START_PREFIX_SIZE] length:length - ES_START_PREFIX_SIZE]; ppsDataA = [NSData dataWithBytes:&data[FRAME_START_PREFIX_SIZE] length:length - FRAME_START_PREFIX_SIZE];
waitingForPpsA = false; waitingForPpsA = false;
ppsDataAFirstByte = data[ES_START_PREFIX_SIZE + 1]; ppsDataAFirstByte = data[FRAME_START_PREFIX_SIZE + 1];
} }
else if (data[ES_START_PREFIX_SIZE + 1] != ppsDataAFirstByte) { else if (data[FRAME_START_PREFIX_SIZE + 1] != ppsDataAFirstByte) {
NSLog(@"Got PPS 2"); NSLog(@"Got PPS 2");
ppsDataA = [NSData dataWithBytes:&data[ES_START_PREFIX_SIZE] length:length - ES_START_PREFIX_SIZE]; ppsDataA = [NSData dataWithBytes:&data[FRAME_START_PREFIX_SIZE] length:length - FRAME_START_PREFIX_SIZE];
waitingForPpsB = false; waitingForPpsB = false;
} }
} }
@ -100,29 +135,39 @@
// Now we're decoding actual frame data here // Now we're decoding actual frame data here
CMBlockBufferRef blockBuffer; CMBlockBufferRef blockBuffer;
status = CMBlockBufferCreateWithMemoryBlock(NULL, data, length, kCFAllocatorNull, NULL, 0, length, 0, &blockBuffer);
status = CMBlockBufferCreateEmpty(NULL, 0, 0, &blockBuffer);
if (status != noErr) { if (status != noErr) {
NSLog(@"CMBlockBufferCreateWithMemoryBlock failed: %d", (int)status); NSLog(@"CMBlockBufferCreateEmpty failed: %d", (int)status);
return; return;
} }
int lastOffset = -1;
for (int i = 0; i < length - FRAME_START_PREFIX_SIZE; i++) {
// Search for a NALU
if (data[i] == 0 && data[i+1] == 0 && data[i+2] == 1) {
// It's the start of a new NALU
if (lastOffset != -1) {
// We've seen a start before this so enqueue that NALU
[self updateBufferForRange:blockBuffer data:data offset:lastOffset length:i - lastOffset];
}
lastOffset = i;
}
}
// Compute the new length prefix to replace the 00 00 00 01 if (lastOffset != -1) {
int dataLength = length - ES_START_PREFIX_SIZE; // Enqueue the remaining data
const uint8_t lengthBytes[] = {(uint8_t)(dataLength >> 24), (uint8_t)(dataLength >> 16), [self updateBufferForRange:blockBuffer data:data offset:lastOffset length:length - lastOffset];
(uint8_t)(dataLength >> 8), (uint8_t)dataLength};
status = CMBlockBufferReplaceDataBytes(lengthBytes, blockBuffer, 0, 4);
if (status != noErr) {
NSLog(@"CMBlockBufferReplaceDataBytes failed: %d", (int)status);
return;
} }
CMSampleBufferRef sampleBuffer; CMSampleBufferRef sampleBuffer;
const size_t sampleSizeArray[] = {length};
status = CMSampleBufferCreate(kCFAllocatorDefault, status = CMSampleBufferCreate(kCFAllocatorDefault,
blockBuffer, true, NULL, blockBuffer,
true, NULL,
NULL, formatDesc, 1, 0, NULL, formatDesc, 1, 0,
NULL, 1, sampleSizeArray, NULL, 0, NULL,
&sampleBuffer); &sampleBuffer);
if (status != noErr) { if (status != noErr) {
NSLog(@"CMSampleBufferCreate failed: %d", (int)status); NSLog(@"CMSampleBufferCreate failed: %d", (int)status);

View File

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="6245" systemVersion="14A386a" targetRuntime="iOS.CocoaTouch.iPad" propertyAccessControl="none" useAutolayout="YES" initialViewController="wb7-af-jn8"> <document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="6245" systemVersion="14A389" targetRuntime="iOS.CocoaTouch.iPad" propertyAccessControl="none" useAutolayout="YES" initialViewController="wb7-af-jn8">
<dependencies> <dependencies>
<deployment defaultVersion="1808" identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="6238"/> <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="6238"/>
</dependencies> </dependencies>
<scenes> <scenes>
@ -73,7 +72,7 @@
<viewControllerLayoutGuide type="top" id="NG3-N1-D4k"/> <viewControllerLayoutGuide type="top" id="NG3-N1-D4k"/>
<viewControllerLayoutGuide type="bottom" id="3MH-n6-BSR"/> <viewControllerLayoutGuide type="bottom" id="3MH-n6-BSR"/>
</layoutGuides> </layoutGuides>
<view key="view" contentMode="scaleToFill" id="VPm-Ae-rc4" userLabel="RenderView"> <view key="view" contentMode="scaleToFill" id="VPm-Ae-rc4" userLabel="RenderView" customClass="StreamView">
<rect key="frame" x="0.0" y="0.0" width="1024" height="768"/> <rect key="frame" x="0.0" y="0.0" width="1024" height="768"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<color key="backgroundColor" white="0.33333333333333331" alpha="1" colorSpace="calibratedWhite"/> <color key="backgroundColor" white="0.33333333333333331" alpha="1" colorSpace="calibratedWhite"/>

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="6245" systemVersion="14A386a" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" initialViewController="dgh-JZ-Q7z"> <document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="6245" systemVersion="14A389" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" initialViewController="dgh-JZ-Q7z">
<dependencies> <dependencies>
<deployment defaultVersion="1808" identifier="iOS"/> <deployment defaultVersion="2048" identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="6238"/> <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="6238"/>
</dependencies> </dependencies>
<scenes> <scenes>
@ -88,7 +88,7 @@
<viewControllerLayoutGuide type="top" id="DRq-YB-9Rh"/> <viewControllerLayoutGuide type="top" id="DRq-YB-9Rh"/>
<viewControllerLayoutGuide type="bottom" id="KH1-hM-RYW"/> <viewControllerLayoutGuide type="bottom" id="KH1-hM-RYW"/>
</layoutGuides> </layoutGuides>
<view key="view" contentMode="scaleToFill" id="eir-e9-IPE"> <view key="view" contentMode="scaleToFill" id="eir-e9-IPE" customClass="StreamView">
<rect key="frame" x="0.0" y="0.0" width="320" height="568"/> <rect key="frame" x="0.0" y="0.0" width="320" height="568"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<color key="backgroundColor" white="0.33333333333333331" alpha="1" colorSpace="calibratedWhite"/> <color key="backgroundColor" white="0.33333333333333331" alpha="1" colorSpace="calibratedWhite"/>