mirror of
https://github.com/moonlight-stream/moonlight-ios.git
synced 2025-07-27 06:32:59 +00:00
275 lines
10 KiB
Objective-C
275 lines
10 KiB
Objective-C
//
|
|
// VideoDecoderRenderer.m
|
|
// Moonlight
|
|
//
|
|
// Created by Cameron Gutman on 10/18/14.
|
|
// Copyright (c) 2014 Moonlight Stream. All rights reserved.
|
|
//
|
|
|
|
#import "VideoDecoderRenderer.h"
|
|
|
|
#include "Limelight.h"
|
|
|
|
@implementation VideoDecoderRenderer {
|
|
UIView *_view;
|
|
|
|
AVSampleBufferDisplayLayer* displayLayer;
|
|
Boolean waitingForSps, waitingForPps;
|
|
|
|
NSData *spsData, *ppsData;
|
|
CMVideoFormatDescriptionRef formatDesc;
|
|
}
|
|
|
|
- (void)reinitializeDisplayLayer
|
|
{
|
|
CALayer *oldLayer = displayLayer;
|
|
|
|
displayLayer = [[AVSampleBufferDisplayLayer alloc] init];
|
|
displayLayer.bounds = _view.bounds;
|
|
displayLayer.backgroundColor = [UIColor blackColor].CGColor;
|
|
displayLayer.position = CGPointMake(CGRectGetMidX(_view.bounds), CGRectGetMidY(_view.bounds));
|
|
displayLayer.videoGravity = AVLayerVideoGravityResizeAspect;
|
|
|
|
if (oldLayer != nil) {
|
|
// Switch out the old display layer with the new one
|
|
[_view.layer replaceSublayer:oldLayer with:displayLayer];
|
|
}
|
|
else {
|
|
[_view.layer addSublayer:displayLayer];
|
|
}
|
|
|
|
// We need some parameter sets before we can properly start decoding frames
|
|
waitingForSps = true;
|
|
spsData = nil;
|
|
waitingForPps = true;
|
|
ppsData = nil;
|
|
|
|
if (formatDesc != nil) {
|
|
CFRelease(formatDesc);
|
|
formatDesc = nil;
|
|
}
|
|
}
|
|
|
|
- (id)initWithView:(UIView*)view
|
|
{
|
|
self = [super init];
|
|
|
|
_view = view;
|
|
|
|
[self reinitializeDisplayLayer];
|
|
|
|
return self;
|
|
}
|
|
|
|
#define FRAME_START_PREFIX_SIZE 4
|
|
#define NALU_START_PREFIX_SIZE 3
|
|
#define NAL_LENGTH_PREFIX_SIZE 4
|
|
#define NAL_TYPE_SPS 0x7
|
|
#define NAL_TYPE_PPS 0x8
|
|
|
|
- (void)updateBufferForRange:(CMBlockBufferRef)existingBuffer data:(unsigned char *)data offset:(int)offset length:(int)nalLength
|
|
{
|
|
OSStatus status;
|
|
size_t oldOffset = CMBlockBufferGetDataLength(existingBuffer);
|
|
|
|
// If we're at index 1 (first NALU in frame), enqueue this buffer to the memory block
|
|
// so it can handle freeing it when the block buffer is destroyed
|
|
if (offset == 1) {
|
|
int dataLength = nalLength - NALU_START_PREFIX_SIZE;
|
|
|
|
// Pass the real buffer pointer directly (no offset)
|
|
// This will give it to the block buffer to free when it's released.
|
|
// All further calls to CMBlockBufferAppendMemoryBlock will do so
|
|
// at an offset and will not be asking the buffer to be freed.
|
|
status = CMBlockBufferAppendMemoryBlock(existingBuffer, data,
|
|
nalLength + 1, // Add 1 for the offset we decremented
|
|
kCFAllocatorDefault,
|
|
NULL, 0, nalLength + 1, 0);
|
|
if (status != noErr) {
|
|
Log(LOG_E, @"CMBlockBufferReplaceDataBytes failed: %d", (int)status);
|
|
return;
|
|
}
|
|
|
|
// Write the length prefix to existing buffer
|
|
const uint8_t lengthBytes[] = {(uint8_t)(dataLength >> 24), (uint8_t)(dataLength >> 16),
|
|
(uint8_t)(dataLength >> 8), (uint8_t)dataLength};
|
|
status = CMBlockBufferReplaceDataBytes(lengthBytes, existingBuffer,
|
|
oldOffset, NAL_LENGTH_PREFIX_SIZE);
|
|
if (status != noErr) {
|
|
Log(LOG_E, @"CMBlockBufferReplaceDataBytes failed: %d", (int)status);
|
|
return;
|
|
}
|
|
} else {
|
|
// Append a 4 byte buffer to this block for the length prefix
|
|
status = CMBlockBufferAppendMemoryBlock(existingBuffer, NULL,
|
|
NAL_LENGTH_PREFIX_SIZE,
|
|
kCFAllocatorDefault, NULL, 0,
|
|
NAL_LENGTH_PREFIX_SIZE, 0);
|
|
if (status != noErr) {
|
|
Log(LOG_E, @"CMBlockBufferAppendMemoryBlock failed: %d", (int)status);
|
|
return;
|
|
}
|
|
|
|
// Write the length prefix to the new buffer
|
|
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, NAL_LENGTH_PREFIX_SIZE);
|
|
if (status != noErr) {
|
|
Log(LOG_E, @"CMBlockBufferReplaceDataBytes failed: %d", (int)status);
|
|
return;
|
|
}
|
|
|
|
// Attach the buffer by reference to the block buffer
|
|
status = CMBlockBufferAppendMemoryBlock(existingBuffer, &data[offset+NALU_START_PREFIX_SIZE],
|
|
dataLength,
|
|
kCFAllocatorNull, // Don't deallocate data on free
|
|
NULL, 0, dataLength, 0);
|
|
if (status != noErr) {
|
|
Log(LOG_E, @"CMBlockBufferReplaceDataBytes failed: %d", (int)status);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// This function must free data
|
|
- (int)submitDecodeBuffer:(unsigned char *)data length:(int)length
|
|
{
|
|
unsigned char nalType = data[FRAME_START_PREFIX_SIZE] & 0x1F;
|
|
OSStatus status;
|
|
|
|
// Check for previous decoder errors before doing anything
|
|
if (displayLayer.status == AVQueuedSampleBufferRenderingStatusFailed) {
|
|
Log(LOG_E, @"Display layer rendering failed: %@", displayLayer.error);
|
|
|
|
// Recreate the display layer
|
|
[self reinitializeDisplayLayer];
|
|
|
|
// Request an IDR frame to initialize the new decoder
|
|
return DR_NEED_IDR;
|
|
}
|
|
|
|
if (nalType == NAL_TYPE_SPS || nalType == NAL_TYPE_PPS) {
|
|
if (nalType == NAL_TYPE_SPS) {
|
|
Log(LOG_I, @"Got SPS");
|
|
spsData = [NSData dataWithBytes:&data[FRAME_START_PREFIX_SIZE] length:length - FRAME_START_PREFIX_SIZE];
|
|
waitingForSps = false;
|
|
|
|
// We got a new SPS so wait for a new PPS to match it
|
|
waitingForPps = true;
|
|
} else if (nalType == NAL_TYPE_PPS) {
|
|
Log(LOG_I, @"Got PPS");
|
|
ppsData = [NSData dataWithBytes:&data[FRAME_START_PREFIX_SIZE] length:length - FRAME_START_PREFIX_SIZE];
|
|
waitingForPps = false;
|
|
}
|
|
|
|
// See if we've got all the parameter sets we need
|
|
if (!waitingForSps && !waitingForPps) {
|
|
const uint8_t* const parameterSetPointers[] = { [spsData bytes], [ppsData bytes] };
|
|
const size_t parameterSetSizes[] = { [spsData length], [ppsData length] };
|
|
|
|
Log(LOG_I, @"Constructing new format description");
|
|
status = CMVideoFormatDescriptionCreateFromH264ParameterSets(kCFAllocatorDefault,
|
|
2, /* count of parameter sets */
|
|
parameterSetPointers,
|
|
parameterSetSizes,
|
|
NAL_LENGTH_PREFIX_SIZE,
|
|
&formatDesc);
|
|
if (status != noErr) {
|
|
Log(LOG_E, @"Failed to create format description: %d", (int)status);
|
|
formatDesc = NULL;
|
|
}
|
|
}
|
|
|
|
// Free the data buffer
|
|
free(data);
|
|
|
|
// No frame data to submit for these NALUs
|
|
return DR_OK;
|
|
}
|
|
|
|
if (formatDesc == NULL) {
|
|
// Can't decode if we haven't gotten our parameter sets yet
|
|
free(data);
|
|
return DR_OK;
|
|
}
|
|
|
|
if (nalType != 0x1 && nalType != 0x5) {
|
|
// Don't submit parameter set data
|
|
free(data);
|
|
return DR_OK;
|
|
}
|
|
|
|
// Now we're decoding actual frame data here
|
|
CMBlockBufferRef blockBuffer;
|
|
|
|
status = CMBlockBufferCreateEmpty(NULL, 0, 0, &blockBuffer);
|
|
if (status != noErr) {
|
|
Log(LOG_E, @"CMBlockBufferCreateEmpty failed: %d", (int)status);
|
|
free(data);
|
|
return DR_NEED_IDR;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
if (lastOffset != -1) {
|
|
// Enqueue the remaining data
|
|
[self updateBufferForRange:blockBuffer data:data offset:lastOffset length:length - lastOffset];
|
|
}
|
|
|
|
// From now on, CMBlockBuffer owns the data pointer and will free it when it's dereferenced
|
|
|
|
CMSampleBufferRef sampleBuffer;
|
|
|
|
status = CMSampleBufferCreate(kCFAllocatorDefault,
|
|
blockBuffer,
|
|
true, NULL,
|
|
NULL, formatDesc, 1, 0,
|
|
NULL, 0, NULL,
|
|
&sampleBuffer);
|
|
if (status != noErr) {
|
|
Log(LOG_E, @"CMSampleBufferCreate failed: %d", (int)status);
|
|
CFRelease(blockBuffer);
|
|
return DR_NEED_IDR;
|
|
}
|
|
|
|
CFArrayRef attachments = CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, YES);
|
|
CFMutableDictionaryRef dict = (CFMutableDictionaryRef)CFArrayGetValueAtIndex(attachments, 0);
|
|
|
|
CFDictionarySetValue(dict, kCMSampleAttachmentKey_DisplayImmediately, kCFBooleanTrue);
|
|
CFDictionarySetValue(dict, kCMSampleAttachmentKey_IsDependedOnByOthers, kCFBooleanTrue);
|
|
|
|
if (nalType == 1) {
|
|
// P-frame
|
|
CFDictionarySetValue(dict, kCMSampleAttachmentKey_NotSync, kCFBooleanTrue);
|
|
CFDictionarySetValue(dict, kCMSampleAttachmentKey_DependsOnOthers, kCFBooleanTrue);
|
|
} else {
|
|
// I-frame
|
|
CFDictionarySetValue(dict, kCMSampleAttachmentKey_NotSync, kCFBooleanFalse);
|
|
CFDictionarySetValue(dict, kCMSampleAttachmentKey_DependsOnOthers, kCFBooleanFalse);
|
|
}
|
|
|
|
[displayLayer enqueueSampleBuffer:sampleBuffer];
|
|
|
|
// Dereference the buffers
|
|
CFRelease(blockBuffer);
|
|
CFRelease(sampleBuffer);
|
|
|
|
return DR_OK;
|
|
}
|
|
|
|
@end
|