mirror of
https://github.com/moonlight-stream/moonlight-ios.git
synced 2026-04-20 15:30:17 +00:00
Merge branch 'master' of github.com:limelight-stream/limelight-ios
# By Cameron Gutman # Via Cameron Gutman * 'master' of github.com:limelight-stream/limelight-ios: Video is mostly working now. It's just chopped off in the view now. Add connection listener callbacks to fix crash on starting streaming Implement code for new video decoder (untested) based on http://stackoverflow.com/questions/25980070/how-to-use-avsamplebufferdisplaylayer-in-ios-8-for-rtp-h264-streams-with-gstream Target 8.0 so we can use Metal for decoding Rip out the old video renderer and decoder
This commit is contained in:
@@ -7,10 +7,11 @@
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "VideoDecoderRenderer.h"
|
||||
|
||||
@interface Connection : NSOperation <NSStreamDelegate>
|
||||
|
||||
-(id) initWithHost:(int)ipaddr width:(int)width height:(int)height;
|
||||
-(id) initWithHost:(int)ipaddr width:(int)width height:(int)height renderer:(VideoDecoderRenderer*)renderer;
|
||||
-(void) main;
|
||||
|
||||
@end
|
||||
|
||||
@@ -7,13 +7,12 @@
|
||||
//
|
||||
|
||||
#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;
|
||||
@@ -31,23 +30,20 @@ static OpusDecoder *opusDecoder;
|
||||
|
||||
static short* decodedPcmBuffer;
|
||||
static int filledPcmBuffer;
|
||||
NSLock* audioRendererBlock;
|
||||
AudioComponentInstance audioUnit;
|
||||
bool started = false;
|
||||
|
||||
static AudioComponentInstance audioUnit;
|
||||
static bool started = false;
|
||||
static VideoDecoderRenderer* renderer;
|
||||
|
||||
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());
|
||||
unsigned char* data = (unsigned char*) malloc(decodeUnit->fullLength);
|
||||
if (data != NULL) {
|
||||
int offset = 0;
|
||||
int err;
|
||||
|
||||
PLENTRY entry = decodeUnit->bufferList;
|
||||
while (entry != NULL) {
|
||||
@@ -56,10 +52,7 @@ void DrSubmitDecodeUnit(PDECODE_UNIT decodeUnit)
|
||||
entry = entry->next;
|
||||
}
|
||||
|
||||
err = nv_avc_decode(data, decodeUnit->fullLength);
|
||||
if (err != 0) {
|
||||
printf("Decode failed: %d\n", err);
|
||||
}
|
||||
[renderer submitDecodeBuffer:data length:decodeUnit->fullLength];
|
||||
|
||||
free(data);
|
||||
}
|
||||
@@ -68,19 +61,16 @@ void DrSubmitDecodeUnit(PDECODE_UNIT decodeUnit)
|
||||
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)
|
||||
@@ -131,20 +121,57 @@ void ArDecodeAndPlaySample(char* sampleData, int sampleLength)
|
||||
// Return of opus_decode is samples per channel
|
||||
filledPcmBuffer *= 4;
|
||||
|
||||
NSLog(@"pcmBuffer: %d", filledPcmBuffer);
|
||||
//[audioRendererBlock lock];
|
||||
|
||||
NSLog(@"pcmBuffer: %d", filledPcmBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
-(id) initWithHost:(int)ipaddr width:(int)width height:(int)height
|
||||
void ClStageStarting(int stage)
|
||||
{
|
||||
NSLog(@"Starting stage: %d", stage);
|
||||
}
|
||||
|
||||
void ClStageComplete(int stage)
|
||||
{
|
||||
NSLog(@"Stage %d complete", stage);
|
||||
}
|
||||
|
||||
void ClStageFailed(int stage, long errorCode)
|
||||
{
|
||||
NSLog(@"Stage %d failed: %ld", stage, errorCode);
|
||||
}
|
||||
|
||||
void ClConnectionStarted(void)
|
||||
{
|
||||
NSLog(@"Connection started");
|
||||
}
|
||||
|
||||
void ClConnectionTerminated(long errorCode)
|
||||
{
|
||||
NSLog(@"ConnectionTerminated: %ld", errorCode);
|
||||
}
|
||||
|
||||
void ClDisplayMessage(char* message)
|
||||
{
|
||||
NSLog(@"DisplayMessage: %s", message);
|
||||
}
|
||||
|
||||
void ClDisplayTransientMessage(char* message)
|
||||
{
|
||||
NSLog(@"DisplayTransientMessage: %s", message);
|
||||
}
|
||||
|
||||
-(id) initWithHost:(int)ipaddr width:(int)width height:(int)height renderer:(VideoDecoderRenderer*)myRenderer
|
||||
{
|
||||
self = [super init];
|
||||
host = ipaddr;
|
||||
renderer = myRenderer;
|
||||
|
||||
streamConfig.width = width;
|
||||
streamConfig.height = height;
|
||||
streamConfig.fps = 30;
|
||||
streamConfig.bitrate = 5000;
|
||||
streamConfig.packetSize = 1024;
|
||||
// FIXME: RI AES members
|
||||
|
||||
drCallbacks.setup = DrSetup;
|
||||
drCallbacks.start = DrStart;
|
||||
@@ -158,6 +185,13 @@ void ArDecodeAndPlaySample(char* sampleData, int sampleLength)
|
||||
arCallbacks.release = ArRelease;
|
||||
arCallbacks.decodeAndPlaySample = ArDecodeAndPlaySample;
|
||||
|
||||
clCallbacks.stageStarting = ClStageStarting;
|
||||
clCallbacks.stageComplete = ClStageComplete;
|
||||
clCallbacks.stageFailed = ClStageFailed;
|
||||
clCallbacks.connectionStarted = ClConnectionStarted;
|
||||
clCallbacks.connectionTerminated = ClConnectionTerminated;
|
||||
clCallbacks.displayMessage = ClDisplayMessage;
|
||||
clCallbacks.displayTransientMessage = ClDisplayTransientMessage;
|
||||
|
||||
//////// Don't think any of this is used /////////
|
||||
NSError *audioSessionError = nil;
|
||||
@@ -249,7 +283,6 @@ static OSStatus playbackCallback(void *inRefCon,
|
||||
filledPcmBuffer -= min;
|
||||
}
|
||||
|
||||
//[audioRendererBlock unlock];
|
||||
return noErr;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
//
|
||||
|
||||
#import "MainFrameViewController.h"
|
||||
#import "VideoDepacketizer.h"
|
||||
#import "ConnectionHandler.h"
|
||||
#import "Computer.h"
|
||||
#import "CryptoManager.h"
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "StreamView.h"
|
||||
|
||||
@interface StreamFrameViewController : UIViewController
|
||||
|
||||
|
||||
@@ -8,9 +8,8 @@
|
||||
|
||||
#import "StreamFrameViewController.h"
|
||||
#import "MainFrameViewController.h"
|
||||
#import "VideoDepacketizer.h"
|
||||
#import "Connection.h"
|
||||
#import "VideoRenderer.h"
|
||||
#import "VideoDecoderRenderer.h"
|
||||
#import "ConnectionHandler.h"
|
||||
|
||||
#include <sys/socket.h>
|
||||
@@ -28,28 +27,14 @@
|
||||
[super viewDidLoad];
|
||||
|
||||
[UIApplication sharedApplication].idleTimerDisabled = YES;
|
||||
|
||||
VideoDecoderRenderer* renderer = [[VideoDecoderRenderer alloc]initWithView:self.view];
|
||||
|
||||
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];
|
||||
Connection* conn = [[Connection alloc] initWithHost:inet_addr([[ConnectionHandler resolveHost:[NSString stringWithUTF8String:[MainFrameViewController getHostAddr]]] UTF8String]) width:1280 height:720
|
||||
renderer: renderer];
|
||||
|
||||
NSOperationQueue* opQueue = [[NSOperationQueue alloc] init];
|
||||
[opQueue addOperation:conn];
|
||||
[opQueue addOperation:[[VideoRenderer alloc]initWithTarget:streamView]];
|
||||
|
||||
}
|
||||
|
||||
- (void)didReceiveMemoryWarning
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
//
|
||||
// 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
|
||||
@@ -1,73 +0,0 @@
|
||||
//
|
||||
// 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
|
||||
@@ -1,46 +0,0 @@
|
||||
//
|
||||
// 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
|
||||
@@ -1,337 +0,0 @@
|
||||
//
|
||||
// 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,
|
||||
(unsigned char*)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;
|
||||
}
|
||||
|
||||
const unsigned char* data = (unsigned char*)our_yuv_frame->data;
|
||||
|
||||
// Convert the YUV image to RGB
|
||||
err = sws_scale(scaler_ctx,
|
||||
&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,
|
||||
(unsigned char*)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,
|
||||
(unsigned char*)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
|
||||
@@ -1,21 +0,0 @@
|
||||
//
|
||||
// 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
|
||||
@@ -1,78 +0,0 @@
|
||||
//
|
||||
// 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
|
||||
@@ -1,18 +0,0 @@
|
||||
//
|
||||
// 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
|
||||
@@ -1,51 +0,0 @@
|
||||
//
|
||||
// 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
|
||||
19
Limelight/VideoDecoderRenderer.h
Normal file
19
Limelight/VideoDecoderRenderer.h
Normal file
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// VideoDecoderRenderer.h
|
||||
// Limelight
|
||||
//
|
||||
// Created by Cameron Gutman on 10/18/14.
|
||||
// Copyright (c) 2014 Limelight Stream. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@import AVFoundation;
|
||||
|
||||
@interface VideoDecoderRenderer : NSObject
|
||||
|
||||
- (id)initWithView:(UIView*)view;
|
||||
|
||||
- (void)submitDecodeBuffer:(unsigned char *)data length:(int)length;
|
||||
|
||||
@end
|
||||
152
Limelight/VideoDecoderRenderer.m
Normal file
152
Limelight/VideoDecoderRenderer.m
Normal file
@@ -0,0 +1,152 @@
|
||||
//
|
||||
// VideoDecoderRenderer.m
|
||||
// Limelight
|
||||
//
|
||||
// Created by Cameron Gutman on 10/18/14.
|
||||
// Copyright (c) 2014 Limelight Stream. All rights reserved.
|
||||
//
|
||||
|
||||
#import "VideoDecoderRenderer.h"
|
||||
|
||||
@implementation VideoDecoderRenderer {
|
||||
AVSampleBufferDisplayLayer* displayLayer;
|
||||
Boolean waitingForSps, waitingForPpsA, waitingForPpsB;
|
||||
|
||||
NSData *spsData, *ppsDataA, *ppsDataB;
|
||||
unsigned char ppsDataAFirstByte;
|
||||
CMVideoFormatDescriptionRef formatDesc;
|
||||
}
|
||||
|
||||
- (id)initWithView:(UIView*)view
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
displayLayer = [[AVSampleBufferDisplayLayer alloc] init];
|
||||
displayLayer.bounds = view.bounds;
|
||||
displayLayer.backgroundColor = [UIColor greenColor].CGColor;
|
||||
displayLayer.position = CGPointMake(CGRectGetMidX(view.bounds), CGRectGetMidY(view.bounds));
|
||||
displayLayer.videoGravity = AVLayerVideoGravityResize;
|
||||
[view.layer addSublayer:displayLayer];
|
||||
|
||||
// We need some parameter sets before we can properly start decoding frames
|
||||
waitingForSps = true;
|
||||
waitingForPpsA = true;
|
||||
waitingForPpsB = true;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
#define ES_START_PREFIX_SIZE 4
|
||||
- (void)submitDecodeBuffer:(unsigned char *)data length:(int)length
|
||||
{
|
||||
unsigned char nalType = data[ES_START_PREFIX_SIZE] & 0x1F;
|
||||
OSStatus status;
|
||||
|
||||
if (formatDesc == NULL && (nalType == 0x7 || nalType == 0x8)) {
|
||||
if (waitingForSps && nalType == 0x7) {
|
||||
NSLog(@"Got SPS");
|
||||
spsData = [NSData dataWithBytes:&data[ES_START_PREFIX_SIZE] length:length - ES_START_PREFIX_SIZE];
|
||||
waitingForSps = false;
|
||||
}
|
||||
// Nvidia's stream has 2 PPS NALUs so we'll wait for both of them
|
||||
else if ((waitingForPpsA || waitingForPpsB) && nalType == 0x8) {
|
||||
// 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) {
|
||||
NSLog(@"Got PPS 1");
|
||||
ppsDataA = [NSData dataWithBytes:&data[ES_START_PREFIX_SIZE] length:length - ES_START_PREFIX_SIZE];
|
||||
waitingForPpsA = false;
|
||||
ppsDataAFirstByte = data[ES_START_PREFIX_SIZE + 1];
|
||||
}
|
||||
else if (data[ES_START_PREFIX_SIZE + 1] != ppsDataAFirstByte) {
|
||||
NSLog(@"Got PPS 2");
|
||||
ppsDataA = [NSData dataWithBytes:&data[ES_START_PREFIX_SIZE] length:length - ES_START_PREFIX_SIZE];
|
||||
waitingForPpsB = false;
|
||||
}
|
||||
}
|
||||
|
||||
// See if we've got all the parameter sets we need
|
||||
if (!waitingForSps && !waitingForPpsA && !waitingForPpsB) {
|
||||
const uint8_t* const parameterSetPointers[] = { [spsData bytes], [ppsDataA bytes], [ppsDataB bytes] };
|
||||
const size_t parameterSetSizes[] = { [spsData length], [ppsDataA length], [ppsDataB length] };
|
||||
|
||||
NSLog(@"Constructing format description");
|
||||
status = CMVideoFormatDescriptionCreateFromH264ParameterSets(kCFAllocatorDefault,
|
||||
2, /* count of parameter sets */
|
||||
parameterSetPointers,
|
||||
parameterSetSizes,
|
||||
4 /* size of length prefix */,
|
||||
&formatDesc);
|
||||
if (status != noErr) {
|
||||
NSLog(@"Failed to create format description: %d", (int)status);
|
||||
formatDesc = NULL;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// No frame data to submit for these NALUs
|
||||
return;
|
||||
}
|
||||
|
||||
if (formatDesc == NULL) {
|
||||
// Can't decode if we haven't gotten our parameter sets yet
|
||||
return;
|
||||
}
|
||||
|
||||
if (nalType != 0x1 && nalType != 0x5) {
|
||||
// Don't submit parameter set data
|
||||
return;
|
||||
}
|
||||
|
||||
// Now we're decoding actual frame data here
|
||||
CMBlockBufferRef blockBuffer;
|
||||
status = CMBlockBufferCreateWithMemoryBlock(NULL, data, length, kCFAllocatorNull, NULL, 0, length, 0, &blockBuffer);
|
||||
if (status != noErr) {
|
||||
NSLog(@"CMBlockBufferCreateWithMemoryBlock failed: %d", (int)status);
|
||||
return;
|
||||
}
|
||||
|
||||
// Compute the new length prefix to replace the 00 00 00 01
|
||||
int dataLength = length - ES_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, blockBuffer, 0, 4);
|
||||
if (status != noErr) {
|
||||
NSLog(@"CMBlockBufferReplaceDataBytes failed: %d", (int)status);
|
||||
return;
|
||||
}
|
||||
|
||||
CMSampleBufferRef sampleBuffer;
|
||||
const size_t sampleSizeArray[] = {length};
|
||||
|
||||
status = CMSampleBufferCreate(kCFAllocatorDefault,
|
||||
blockBuffer, true, NULL,
|
||||
NULL, formatDesc, 1, 0,
|
||||
NULL, 1, sampleSizeArray,
|
||||
&sampleBuffer);
|
||||
if (status != noErr) {
|
||||
NSLog(@"CMSampleBufferCreate failed: %d", (int)status);
|
||||
return;
|
||||
}
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
@end
|
||||
Reference in New Issue
Block a user