/*
 *  VideoSource.cpp
 *  ARToolKit5
 *
 *  This file is part of ARToolKit.
 *
 *  ARToolKit is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  ARToolKit is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with ARToolKit.  If not, see <http://www.gnu.org/licenses/>.
 *
 *  As a special exception, the copyright holders of this library give you
 *  permission to link this library with independent modules to produce an
 *  executable, regardless of the license terms of these independent modules, and to
 *  copy and distribute the resulting executable under terms of your choice,
 *  provided that you also meet, for each linked independent module, the terms and
 *  conditions of the license of that module. An independent module is a module
 *  which is neither derived from nor based on this library. If you modify this
 *  library, you may extend this exception to your version of the library, but you
 *  are not obligated to do so. If you do not wish to do so, delete this exception
 *  statement from your version.
 *
 *  Copyright 2015 Daqri, LLC.
 *  Copyright 2010-2015 ARToolworks, Inc.
 *
 *  Author(s): Julian Looser, Philip Lamb.
 *
 */

#include <ARWrapper/Platform.h>
#include <ARWrapper/VideoSource.h>
#include <ARWrapper/Error.h>
#if TARGET_PLATFORM_ANDROID
#  include <ARWrapper/AndroidVideoSource.h>
#endif
#include <ARWrapper/ARToolKitVideoSource.h>
#include <ARWrapper/ARController.h>
#include <ARWrapper/ColorConversion.h>

#if defined(HAVE_ARM_NEON) || defined(HAVE_ARM64_NEON)
#  include <arm_neon.h>
#  ifdef ANDROID
#    include "cpu-features.h"
#  endif
#endif

#define MAX(x,y) (x > y ? x : y)
#define MIN(x,y) (x < y ? x : y)
#define CLAMP(x,r1,r2) (MIN(MAX(x,r1),r2))

VideoSource* VideoSource::newVideoSource()
{    
#if TARGET_PLATFORM_ANDROID
	return new AndroidVideoSource();
#else
	return new ARToolKitVideoSource();
#endif
    
}

VideoSource::VideoSource() : 
    deviceState(DEVICE_CLOSED),
	cameraParam(NULL),
    cameraParamBuffer(NULL),
    cameraParamBufferLen(0L),
    cparamLT(NULL),
    videoConfiguration(NULL),
    videoWidth(0),
    videoHeight(0),
    pixelFormat((AR_PIXEL_FORMAT)(-1)),
#if defined(HAVE_ARM_NEON) || defined(HAVE_ARM64_NEON)
    m_fastPath(-1),
#endif
#ifndef _WINRT
    glPixIntFormat(0),
    glPixFormat(0),
    glPixType(0),
#endif
    frameBuffer(NULL),
    frameBuffer2(NULL),
    frameStamp(0),
    m_error(ARW_ERROR_NONE)
{
        
}

VideoSource::~VideoSource() {

	if (videoConfiguration) {
		free(videoConfiguration);
		videoConfiguration = NULL;
	}

	if (cameraParam) {
		free(cameraParam);
		cameraParam = NULL;
	}

	if (cameraParamBuffer) {
		free(cameraParamBuffer);
		cameraParamBuffer = NULL;
	}

}

void VideoSource::setError(int error)
{
    if (m_error == ARW_ERROR_NONE) {
        m_error = error;
    }
}

int VideoSource::getError()
{
    int temp = m_error;
    if (temp != ARW_ERROR_NONE) {
        m_error = ARW_ERROR_NONE;
    }
    return temp;
}

void VideoSource::configure(const char* vconf, const char* cparaName, const char* cparaBuff, size_t cparaBuffLen) {
    ARController::logv(AR_LOG_LEVEL_DEBUG, "VideoSource::configure(): called");
	if (vconf) {
		size_t len = strlen(vconf);
		videoConfiguration = (char*)malloc(sizeof(char) * len + 1);
		strcpy(videoConfiguration, vconf);
        ARController::logv(AR_LOG_LEVEL_INFO, "Setting video configuration '%s'.", videoConfiguration);
	}

	if (cparaName) {
		size_t len = strlen(cparaName);
		cameraParam = (char*)malloc(sizeof(char) * len + 1);
		strcpy(cameraParam, cparaName);
        ARController::logv(AR_LOG_LEVEL_INFO, "Settting camera parameters file '%s'.", cameraParam);
	}

	if (cparaBuff) {
		cameraParamBufferLen = cparaBuffLen;
		cameraParamBuffer = (char*)malloc(sizeof(char) * cameraParamBufferLen);
		memcpy(cameraParamBuffer, cparaBuff, cameraParamBufferLen);
        ARController::logv(AR_LOG_LEVEL_INFO, "Settting camera parameters buffer: %ld bytes.", cameraParamBufferLen);
	}
    ARController::logv(AR_LOG_LEVEL_DEBUG, "VideoSource::configure(): exiting");
}

bool VideoSource::isOpen() {
	return deviceState != DEVICE_CLOSED;
}

bool VideoSource::isRunning() {
	return deviceState == DEVICE_RUNNING;
}


ARParamLT* VideoSource::getCameraParameters() {
	return cparamLT;
}

int VideoSource::getVideoWidth() {
	return videoWidth;
}
	
int VideoSource::getVideoHeight() {
	return videoHeight;
}

AR_PIXEL_FORMAT VideoSource::getPixelFormat() {
	return pixelFormat;
}

ARUint8* VideoSource::getFrame() {
	return frameBuffer;
}

int VideoSource::getFrameStamp() {
	return frameStamp;
}

bool VideoSource::updateTexture(Color* buffer) {
	
	static int lastFrameStamp = 0;

    if (!buffer) return false; // Sanity check.
    
    if (!frameBuffer) return false; // Check that a frame is actually available.
	
    // Extra check: don't update the array if the current frame is the same is previous one.
	if (lastFrameStamp == frameStamp) return false;
    
    int pixelSize = arUtilGetPixelSize(pixelFormat);
    switch (pixelFormat) {
        case AR_PIXEL_FORMAT_BGRA:
        case AR_PIXEL_FORMAT_BGR:
            for (int y = 0; y < videoHeight; y++) {
                ARUint8 *inp = &frameBuffer[videoWidth*y*pixelSize];
                Color *outp = &buffer[videoWidth*y];
                for (int pixelsToGo = videoWidth; pixelsToGo > 0; pixelsToGo--) {
                    outp->b = (float)*(inp + 0) / 255.0f;
                    outp->g = (float)*(inp + 1) / 255.0f;
                    outp->r = (float)*(inp + 2) / 255.0f;
                    outp->a = 1.0f ;
                    inp += pixelSize;
                    outp++;
                }
            }
            break;
        case AR_PIXEL_FORMAT_RGBA:
        case AR_PIXEL_FORMAT_RGB:
            for (int y = 0; y < videoHeight; y++) {
                ARUint8 *inp = &frameBuffer[videoWidth*y*pixelSize];
                Color *outp = &buffer[videoWidth*y];
                for (int pixelsToGo = videoWidth; pixelsToGo > 0; pixelsToGo--) {
                    outp->r = (float)*(inp + 0) / 255.0f;
                    outp->g = (float)*(inp + 1) / 255.0f;
                    outp->b = (float)*(inp + 2) / 255.0f;
                    outp->a = 1.0f;
                    inp += pixelSize;
                    outp++;
                }
            }
            break;
        case AR_PIXEL_FORMAT_ARGB:
            for (int y = 0; y < videoHeight; y++) {
                ARUint8 *inp = &frameBuffer[videoWidth*y*pixelSize];
                Color *outp = &buffer[videoWidth*y];
                for (int pixelsToGo = videoWidth; pixelsToGo > 0; pixelsToGo--) {
                    outp->r = (float)*(inp + 1) / 255.0f;
                    outp->g = (float)*(inp + 2) / 255.0f;
                    outp->b = (float)*(inp + 3) / 255.0f;
                    outp->a = 1.0f;
                    inp += pixelSize;
                    outp++;
                }
            }
            break;
        case AR_PIXEL_FORMAT_ABGR:
            for (int y = 0; y < videoHeight; y++) {
                ARUint8 *inp = &frameBuffer[videoWidth*y*pixelSize];
                Color *outp = &buffer[videoWidth*y];
                for (int pixelsToGo = videoWidth; pixelsToGo > 0; pixelsToGo--) {
                    outp->b = (float)*(inp + 1) / 255.0f;
                    outp->g = (float)*(inp + 2) / 255.0f;
                    outp->r = (float)*(inp + 3) / 255.0f;
                    outp->a = 1.0f;
                    inp += pixelSize;
                    outp++;
                }
            }
            break;
        case AR_PIXEL_FORMAT_MONO:
            for (int y = 0; y < videoHeight; y++) {
                ARUint8 *inp = &frameBuffer[videoWidth*y*pixelSize];
                Color *outp = &buffer[videoWidth*y];
                for (int pixelsToGo = videoWidth; pixelsToGo > 0; pixelsToGo--) {
                    outp->b = outp->g = outp->r = (float)*inp / 255.0f;
                    outp->a = 1.0f;
                    inp += pixelSize;
                    outp++;
                }
            }
            break;
        default:
            return false;
            break;
    }
    
    lastFrameStamp = frameStamp; // Record the new framestamp
    return true;

}

#if defined(HAVE_ARM_NEON)
static void YCbCr422BiPlanarToRGBA_ARM_neon_asm(uint8_t * __restrict dest, uint8_t * __restrict srcY, uint8_t * __restrict srcCbCr, int rowBytes, int rows)
{
    __asm__ volatile("    vstmdb      sp!, {d8-d15}    \n" // Save any VFP or NEON registers that should be preserved (S16-S31 / Q4-Q7).
                     // Setup factors etc.
                     "    mov         r4,  #179        \n" // R Cr factor =  1.402. 179/128 =  1.398 (179 = 0x00b3)
                     "    mvn         r5,  #90         \n" // G Cr factor = -0.714. -91/128 = -0.711 (-91 = 0xffa5 = NOT 90).
                     "    mvn         r6,  #43         \n" // G Cb factor = -0.344. -44/128 = -0.344 (-44 = 0xffd4 = NOT 43).
                     "    vdup.16     q0,  r4          \n" // Load q0 (d0-d1) with 8 copies of the 16 LSBs of R Cr.
                     "    vdup.16     q1,  r5          \n" // Load q1 (d2-d3) with 8 copies of the 16 LSBs of G Cr.
                     "    vdup.16     q2,  r6          \n" // Load q2 (d4-d5) with 8 copies of the 16 LSBs of G Cb.
                     "    mov         r4,  #227        \n" // B Cb factor =  1.772. 227/128 =  1.774 (227 = 0x00e3).
                     "    mov         r5,  #0x80       \n" // XOR with this value converts unsigned 8-bit val to signed 8-bit val - 128.
                     "    vdup.16     q3,  r4          \n" // Load q3 (d6-d7) with 8 copies of the 16 LSBs of B Cb.
                     "    vdup.8      d8,  r5          \n" // Load d8 (q4[0]) with 8 copies of the 8 LSBs of r5.
                     "    vmov.i8     d31, #0xFF       \n" // Load d31 (A channel of destRGBA) with FF.
                     // Setup loop-related stuff.
                     "    mov         r4,  %3          \n" // Save arg 3 (rowBytes) in r4 for later use in pointer arithmetic.
                     "    lsr         r6,  %4,  #1     \n" // Save arg 4 (rows) divided by 2 in r6 for loop count.
                     "0:						       \n"
                     "    lsr         r5,  %3,  #4     \n" // Save arg 3 (rowBytes) divided by 16 in r5 for loop count.
                     "1:						       \n"
                     // Read 8 CbCr pixels, convert to 16-bit.
                     "    vld2.8      {d10-d11}, [%2]! \n" // Load 8 CbCr pixels, de-interleaving Cb into d10 (q5[0]), Cr into d11 (q5[1]).
                     "    veor.8      d10, d10, d8     \n" // Subtract 128 from Cb. Result is signed.
                     "    veor.8      d11, d11, d8     \n" // Subtract 128 from Cr. Result is signed.
                     "    vmovl.s8    q6,  d11         \n" // Sign-extend Cr to 16 bit in q6 (d12-d13).
                     "    vmovl.s8    q5,  d10         \n" // Sign-extend Cb to 16 bit in q5 (d10-d11) (overwriting).
                     // Calculate red, then scale width by 2.
                     "    vmul.s16    q8,  q0,  q6     \n" // R is now signed 16 bit in q8 (d16-d17).
                     "    vmov        q9,  q8          \n" // Copy into q9 (d18-d19).
                     "    vzip.16     q8,  q9          \n" // Interleave the original and the copy.
                     // Calculate green, then scale width by 2.
                     "    vmul.s16    q10, q1,  q6     \n"
                     "    vmla.s16    q10, q2,  q5     \n" // G is now signed 16 bit in q10 (d20-d21).
                     "    vmov        q11, q10         \n" // Copy into q11 (d22-d23).
                     "    vzip.16     q10, q11         \n" // Interleave the original and the copy.
                     // Calculate blue, then scale width by 2.
                     "    vmul.s16    q12, q3,  q5     \n" // B is now signed 16 bit in q12 (d24-d25).
                     "    vmov        q13, q12         \n" // Copy into q13 (d26-d27).
                     "    vzip.16     q12, q13         \n" // Interleave the original and the copy.
                     // Read 16 Y from first row.
                     "    vld1.8      {d10,d11}, [%1]  \n" // Load 16 Y pixels into d10 (q5[0]) and d11 (q5[1]). N.B. No post-increment.
                     "    vshll.u8    q6,  d11, #7     \n" // Multiply second 8 pixels by 128, store in q6 (d12-d13).
                     "    vshll.u8    q5,  d10, #7     \n" // Multiply first 8 pixels by 128, store in q5 (d10-d11) (overwriting).
                     // Add luma and chroma values. First 8 pixels.
                     "    vqadd.s16    q7,  q5,  q8     \n" // Y+R
                     "    vqshrun.s16 d28, q7,  #7     \n" // Divide by 128, and clamp to range [0, 255].
                     "    vqadd.s16    q7,  q5,  q10    \n" // Y+G
                     "    vqshrun.s16 d29, q7,  #7     \n" // Divide by 128, and clamp to range [0, 255].
                     "    vqadd.s16    q7,  q5,  q12    \n" // Y+B
                     "    vqshrun.s16 d30, q7,  #7     \n" // Divide by 128, and clamp to range [0, 255].
                     "    vst4.8      {d28-d31}, [%0]! \n" // Interleave.
                     // Add luma and chroma values. Second 8 pixels.
                     "    vqadd.s16    q7,  q6,  q9     \n" // Y+R
                     "    vqshrun.s16 d28, q7,  #7     \n" // Divide by 128, and clamp to range [0, 255].
                     "    vqadd.s16    q7,  q6,  q11    \n" // Y+G
                     "    vqshrun.s16 d29, q7,  #7     \n" // Divide by 128, and clamp to range [0, 255].
                     "    vqadd.s16    q7,  q6,  q13    \n" // Y+B
                     "    vqshrun.s16 d30, q7,  #7     \n" // Divide by 128, and clamp to range [0, 255].
                     "    vst4.8      {d28-d31}, [%0]! \n" // Interleave.
                     // 16 pixels of first row done.
                     "    add         %1,  %1,  r4     \n" // Advance srcY by rowBytes to move to next row.
                     "    sub         %0,  %0,  #64    \n" // Back up dest by 16 pixels/64 bytes.
                     "    add         %0,  %0,  r4, LSL #2 \n" // Advance dest by 4xrowBytes.
                     // Read 16 Y from second row.
                     "    vld1.8      {d10,d11}, [%1]! \n" // Load 16 Y pixels into d10 (q5[0]) and d11 (q5[1]).
                     "    vshll.u8    q6,  d11, #7     \n" // Multiply second 8 pixels by 128, store in q6 (d12-d13).
                     "    vshll.u8    q5,  d10, #7     \n" // Multiply first 8 pixels by 128, store in q5 (d10-d11) (overwriting).
                     // Add luma and chroma values. First 8 pixels.
                     "    vqadd.s16    q7,  q5,  q8     \n" // Y+R
                     "    vqshrun.s16 d28, q7,  #7     \n" // Divide by 128, and clamp to range [0, 255].
                     "    vqadd.s16    q7,  q5,  q10    \n" // Y+G
                     "    vqshrun.s16 d29, q7,  #7     \n" // Divide by 128, and clamp to range [0, 255].
                     "    vqadd.s16    q7,  q5,  q12    \n" // Y+B
                     "    vqshrun.s16 d30, q7,  #7     \n" // Divide by 128, and clamp to range [0, 255].
                     "    vst4.8      {d28-d31}, [%0]! \n" // Interleave.
                     // Add luma and chroma values. Second 8 pixels.
                     "    vqadd.s16    q7,  q6,  q9     \n" // Y+R
                     "    vqshrun.s16 d28, q7,  #7     \n" // Divide by 128, and clamp to range [0, 255].
                     "    vqadd.s16    q7,  q6,  q11    \n" // Y+G
                     "    vqshrun.s16 d29, q7,  #7     \n" // Divide by 128, and clamp to range [0, 255].
                     "    vqadd.s16    q7,  q6,  q13    \n" // Y+B
                     "    vqshrun.s16 d30, q7,  #7     \n" // Divide by 128, and clamp to range [0, 255].
                     "    vst4.8      {d28-d31}, [%0]! \n" // Interleave.
                     // 16 pixels of second row done.
                     "    sub         %1,  %1,  r4     \n" // Back up srcY by rowBytes to move to previous row.
                     "    sub         %0,  %0,  r4, LSL #2 \n" // Back up dest by 4xrowBytes.
                     "    subs        r5,  r5,  #1     \n" // Decrement iteration count.
                     "    bne         1b               \n" // Repeat unil iteration count is not zero.
                     // Two rows done.
                     "    add         %1,  %1,  r4     \n" // Advance srcY by rowBytes to skip row we've already done.
                     "    add         %0,  %0,  r4, LSL #2 \n" // Advance dest by 4xrowBytes to skip row we've already done.
                     "    subs        r6,  r6,  #1     \n" // Decrement iteration count.
                     "    bne         0b               \n" // Repeat unil iteration count is not zero.
                     "    vldmia      sp!, {d8-d15}    \n" // Restore any VFP or NEON registers that were saved.
                     :
                     : "r"(dest), "r"(srcY), "r"(srcCbCr), "r"(rowBytes), "r"(rows)
                     : "cc", "r4", "r5", "r6"
                     );
}

static void YCrCb422BiPlanarToRGBA_ARM_neon_asm(uint8_t * __restrict dest, uint8_t * __restrict srcY, uint8_t * __restrict srcCrCb, int rowBytes, int rows)
{
    __asm__ volatile("    vstmdb      sp!, {d8-d15}    \n" // Save any VFP or NEON registers that should be preserved (S16-S31 / Q4-Q7).
                     // Setup factors etc.
                     "    mov         r4,  #179        \n" // R Cr factor =  1.402. 179/128 =  1.398 (179 = 0x00b3)
                     "    mvn         r5,  #90         \n" // G Cr factor = -0.714. -91/128 = -0.711 (-91 = 0xffa5 = NOT 90).
                     "    mvn         r6,  #43         \n" // G Cb factor = -0.344. -44/128 = -0.344 (-44 = 0xffd4 = NOT 43).
                     "    vdup.16     q0,  r4          \n" // Load q0 (d0-d1) with 8 copies of the 16 LSBs of R Cr.
                     "    vdup.16     q1,  r5          \n" // Load q1 (d2-d3) with 8 copies of the 16 LSBs of G Cr.
                     "    vdup.16     q2,  r6          \n" // Load q2 (d4-d5) with 8 copies of the 16 LSBs of G Cb.
                     "    mov         r4,  #227        \n" // B Cb factor =  1.772. 227/128 =  1.774 (227 = 0x00e3).
                     "    mov         r5,  #0x80       \n" // XOR with this value converts unsigned 8-bit val to signed 8-bit val - 128.
                     "    vdup.16     q3,  r4          \n" // Load q3 (d6-d7) with 8 copies of the 16 LSBs of B Cb.
                     "    vdup.8      d8,  r5          \n" // Load d8 (q4[0]) with 8 copies of the 8 LSBs of r5.
                     "    vmov.i8     d31, #0xFF       \n" // Load d31 (A channel of destRGBA) with FF.
                     // Setup loop-related stuff.
                     "    mov         r4,  %3          \n" // Save arg 3 (rowBytes) in r4 for later use in pointer arithmetic.
                     "    lsr         r6,  %4,  #1     \n" // Save arg 4 (rows) divided by 2 in r6 for loop count.
                     "0:						       \n"
                     "    lsr         r5,  %3,  #4     \n" // Save arg 3 (rowBytes) divided by 16 in r5 for loop count.
                     "1:						       \n"
                     // Read 8 CbCr pixels, convert to 16-bit.
                     "    vld2.8      {d10-d11}, [%2]! \n" // Load 8 CrCb pixels, de-interleaving Cr into d10 (q5[0]), Cb into d11 (q5[1]).
                     "    veor.8      d10, d10, d8     \n" // Subtract 128 from Cr. Result is signed.
                     "    veor.8      d11, d11, d8     \n" // Subtract 128 from Cb. Result is signed.
                     "    vmovl.s8    q6,  d11         \n" // Sign-extend Cb to 16 bit in q6 (d12-d13).
                     "    vmovl.s8    q5,  d10         \n" // Sign-extend Cr to 16 bit in q5 (d10-d11) (overwriting).
                     // Calculate red, then scale width by 2.
                     "    vmul.s16    q8,  q0,  q5     \n" // R is now signed 16 bit in q8 (d16-d17).
                     "    vmov        q9,  q8          \n" // Copy into q9 (d18-d19).
                     "    vzip.16     q8,  q9          \n" // Interleave the original and the copy.
                     // Calculate green, then scale width by 2.
                     "    vmul.s16    q10, q1,  q5     \n"
                     "    vmla.s16    q10, q2,  q6     \n" // G is now signed 16 bit in q10 (d20-d21).
                     "    vmov        q11, q10         \n" // Copy into q11 (d22-d23).
                     "    vzip.16     q10, q11         \n" // Interleave the original and the copy.
                     // Calculate blue, then scale width by 2.
                     "    vmul.s16    q12, q3,  q6     \n" // B is now signed 16 bit in q12 (d24-d25).
                     "    vmov        q13, q12         \n" // Copy into q13 (d26-d27).
                     "    vzip.16     q12, q13         \n" // Interleave the original and the copy.
                     // Read 16 Y from first row.
                     "    vld1.8      {d10,d11}, [%1]  \n" // Load 16 Y pixels into d10 (q5[0]) and d11 (q5[1]). N.B. No post-increment.
                     "    vshll.u8    q6,  d11, #7     \n" // Multiply second 8 pixels by 128, store in q6 (d12-d13).
                     "    vshll.u8    q5,  d10, #7     \n" // Multiply first 8 pixels by 128, store in q5 (d10-d11) (overwriting).
                     // Add luma and chroma values. First 8 pixels.
                     "    vqadd.s16    q7,  q5,  q8     \n" // Y+R
                     "    vqshrun.s16 d28, q7,  #7     \n" // Divide by 128, and clamp to range [0, 255].
                     "    vqadd.s16    q7,  q5,  q10    \n" // Y+G
                     "    vqshrun.s16 d29, q7,  #7     \n" // Divide by 128, and clamp to range [0, 255].
                     "    vqadd.s16    q7,  q5,  q12    \n" // Y+B
                     "    vqshrun.s16 d30, q7,  #7     \n" // Divide by 128, and clamp to range [0, 255].
                     "    vst4.8      {d28-d31}, [%0]! \n" // Interleave.
                     // Add luma and chroma values. Second 8 pixels.
                     "    vqadd.s16    q7,  q6,  q9     \n" // Y+R
                     "    vqshrun.s16 d28, q7,  #7     \n" // Divide by 128, and clamp to range [0, 255].
                     "    vqadd.s16    q7,  q6,  q11    \n" // Y+G
                     "    vqshrun.s16 d29, q7,  #7     \n" // Divide by 128, and clamp to range [0, 255].
                     "    vqadd.s16    q7,  q6,  q13    \n" // Y+B
                     "    vqshrun.s16 d30, q7,  #7     \n" // Divide by 128, and clamp to range [0, 255].
                     "    vst4.8      {d28-d31}, [%0]! \n" // Interleave.
                     // 16 pixels of first row done.
                     "    add         %1,  %1,  r4     \n" // Advance srcY by rowBytes to move to next row.
                     "    sub         %0,  %0,  #64    \n" // Back up dest by 16 pixels/64 bytes.
                     "    add         %0,  %0,  r4, LSL #2 \n" // Advance dest by 4xrowBytes.
                     // Read 16 Y from second row.
                     "    vld1.8      {d10,d11}, [%1]! \n" // Load 16 Y pixels into d10 (q5[0]) and d11 (q5[1]).
                     "    vshll.u8    q6,  d11, #7     \n" // Multiply second 8 pixels by 128, store in q6 (d12-d13).
                     "    vshll.u8    q5,  d10, #7     \n" // Multiply first 8 pixels by 128, store in q5 (d10-d11) (overwriting).
                     // Add luma and chroma values. First 8 pixels.
                     "    vqadd.s16    q7,  q5,  q8     \n" // Y+R
                     "    vqshrun.s16 d28, q7,  #7     \n" // Divide by 128, and clamp to range [0, 255].
                     "    vqadd.s16    q7,  q5,  q10    \n" // Y+G
                     "    vqshrun.s16 d29, q7,  #7     \n" // Divide by 128, and clamp to range [0, 255].
                     "    vqadd.s16    q7,  q5,  q12    \n" // Y+B
                     "    vqshrun.s16 d30, q7,  #7     \n" // Divide by 128, and clamp to range [0, 255].
                     "    vst4.8      {d28-d31}, [%0]! \n" // Interleave.
                     // Add luma and chroma values. Second 8 pixels.
                     "    vqadd.s16    q7,  q6,  q9     \n" // Y+R
                     "    vqshrun.s16 d28, q7,  #7     \n" // Divide by 128, and clamp to range [0, 255].
                     "    vqadd.s16    q7,  q6,  q11    \n" // Y+G
                     "    vqshrun.s16 d29, q7,  #7     \n" // Divide by 128, and clamp to range [0, 255].
                     "    vqadd.s16    q7,  q6,  q13    \n" // Y+B
                     "    vqshrun.s16 d30, q7,  #7     \n" // Divide by 128, and clamp to range [0, 255].
                     "    vst4.8      {d28-d31}, [%0]! \n" // Interleave.
                     // 16 pixels of second row done.
                     "    sub         %1,  %1,  r4     \n" // Back up srcY by rowBytes to move to previous row.
                     "    sub         %0,  %0,  r4, LSL #2 \n" // Back up dest by 4xrowBytes.
                     "    subs        r5,  r5,  #1     \n" // Decrement iteration count.
                     "    bne         1b               \n" // Repeat unil iteration count is not zero.
                     // Two rows done.
                     "    add         %1,  %1,  r4     \n" // Advance srcY by rowBytes to skip row we've already done.
                     "    add         %0,  %0,  r4, LSL #2 \n" // Advance dest by 4xrowBytes to skip row we've already done.
                     "    subs        r6,  r6,  #1     \n" // Decrement iteration count.
                     "    bne         0b               \n" // Repeat unil iteration count is not zero.
                     "    vldmia      sp!, {d8-d15}    \n" // Restore any VFP or NEON registers that were saved.
                     :
                     : "r"(dest), "r"(srcY), "r"(srcCrCb), "r"(rowBytes), "r"(rows)
                     : "cc", "r4", "r5", "r6"
                     );
}
#elif defined(HAVE_ARM64_NEON)

static void YCbCr422BiPlanarToRGBA_ARM_neon_asm(uint8_t * __restrict dest, uint8_t * __restrict srcY, uint8_t * __restrict srcCbCr, int32_t rowBytes, int32_t rows)
{
    __asm__ volatile(// Push SIMD registers that should be preserved (d8-d15).
                     "    sub         sp,     sp,     #64     \n"
                     "    stp         d8,     d9,     [sp,#0] \n"
                     "    stp         d10,    d11,    [sp,#16] \n"
                     "    stp         d12,    d13,    [sp,#32] \n"
                     "    stp         d14,    d15,    [sp,#48] \n"
                     // Setup factors etc.
                     "    movi        v0.8h,  #179           \n" // Load v0 with 8 copies of the 16 LSBs of R Cr factor =  1.402. 179/128 =  1.398 (179 = 0x00b3)
                     "    mvni        v1.8h,  #90            \n" // Load v1 with 8 copies of the 16 LSBs of G Cr factor = -0.714. -91/128 = -0.711 (-91 = 0xffa5 = NOT 90).
                     "    mvni        v2.8h,  #43            \n" // Load v2 with 8 copies of the 16 LSBs of G Cb factor = -0.344. -44/128 = -0.344 (-44 = 0xffd4 = NOT 43).
                     "    movi        v3.8h,  #227           \n" // Load v3 with 8 copies of the 16 LSBs of B Cb factor =  1.772. 227/128 =  1.774 (227 = 0x00e3).
                     "    movi        v4.8b,  #0x80          \n" // Load v4 with 8 copies of the 8 LSBs of (XOR with this value converts unsigned 8-bit val to signed 8-bit val - 128).
                     "    movi        v31.8b, #0xFF          \n" // Load v31 (A channel of destRGBA) with FF.
                     // Setup loop-related stuff.
                     "    mov         w4,     %w3            \n" // Save arg 3 (rowBytes) in w4 for later use in pointer arithmetic.
                     "    lsr         w6,     %w4,    #1     \n" // Save arg 4 (rows) divided by 2 in w6 for loop count.
                     "0:						             \n"
                     "    lsr         w5,     %w3,    #4     \n" // Save arg 3 (rowBytes) divided by 16 in w5 for loop count.
                     "1:						             \n"
                     // Read 8 CbCr pixels, convert to 16-bit.
                     "    ld2         {v10.8b, v11.8b}, [%2],#16 \n" // Load 8 CbCr pixels, de-interleaving Cb into lower half of v10, Cr into lower half of v11.
                     "    eor         v10.8b, v10.8b, v4.8b  \n" // Subtract 128 from Cb. Result is signed.
                     "    eor         v11.8b, v11.8b, v4.8b  \n" // Subtract 128 from Cr. Result is signed.
                     "    sxtl        v5.8h,  v10.8b         \n" // Sign-extend Cb to 16 bit in v5.
                     "    sxtl        v6.8h,  v11.8b         \n" // Sign-extend Cr to 16 bit in v6.
                     // Calculate red, then scale width by 2.
                     "    mul         v7.8h,  v0.8h,  v6.8h  \n" // R is now signed 16 bit in v4.
                     "    zip1        v8.8h,  v7.8h,  v7.8h  \n" // Interleave the original and the copy.
                     "    zip2        v9.8h,  v7.8h,  v7.8h  \n" // Interleave the original and the copy.
                     // Calculate green, then scale width by 2.
                     "    mul         v7.8h,  v1.8h,  v6.8h  \n"
                     "    mla         v7.8h,  v2.8h,  v5.8h  \n" // G is now signed 16 bit in v4.
                     "    zip1        v10.8h, v7.8h,  v7.8h  \n" // Interleave the original and the copy.
                     "    zip2        v11.8h, v7.8h,  v7.8h  \n" // Interleave the original and the copy.
                     // Calculate blue, then scale width by 2.
                     "    mul         v7.8h,  v3.8h,  v5.8h  \n" // B is now signed 16 bit in v4.
                     "    zip1        v12.8h, v7.8h,  v7.8h  \n" // Interleave the original and the copy.
                     "    zip2        v13.8h, v7.8h,  v7.8h  \n" // Interleave the original and the copy.
                     // Read 16 Y from first row.
                     "    ld1         {v5.16b}, [%1]         \n" // Load 16 Y pixels into v5. N.B. No post-increment.
                     "    ushll2      v6.8h,  v5.16b, #7     \n" // Multiply second 8 pixels by 128, store in v6.
                     "    ushll       v5.8h,  v5.8b,  #7     \n" // Multiply first 8 pixels by 128, store in v5 (overwriting).
                     // Add luma and chroma values. First 8 pixels.
                     "    sqadd       v7.8h,  v5.8h,  v8.8h  \n" // Y+R
                     "    sqshrun     v28.8b, v7.8h,  #7     \n" // Divide by 128, and clamp to range [0, 255].
                     "    sqadd       v7.8h,  v5.8h,  v10.8h \n" // Y+G
                     "    sqshrun     v29.8b, v7.8h,  #7     \n" // Divide by 128, and clamp to range [0, 255].
                     "    sqadd       v7.8h,  v5.8h,  v12.8h \n" // Y+B
                     "    sqshrun     v30.8b, v7.8h,  #7     \n" // Divide by 128, and clamp to range [0, 255].
                     "    st4         {v28.8b,v29.8b,v30.8b,v31.8b}, [%0],#32 \n" // Interleave.
                     // Add luma and chroma values. Second 8 pixels.
                     "    sqadd       v7.8h,  v6.8h,  v9.8h  \n" // Y+R
                     "    sqshrun     v28.8b, v7.8h,  #7     \n" // Divide by 128, and clamp to range [0, 255].
                     "    sqadd       v7.8h,  v6.8h,  v11.8h \n" // Y+G
                     "    sqshrun     v29.8b, v7.8h,  #7     \n" // Divide by 128, and clamp to range [0, 255].
                     "    sqadd       v7.8h,  v6.8h,  v13.8h \n" // Y+B
                     "    sqshrun     v30.8b, v7.8h,  #7     \n" // Divide by 128, and clamp to range [0, 255].
                     "    st4         {v28.8b,v29.8b,v30.8b,v31.8b}, [%0],#32 \n" // Interleave.
                     // 16 pixels of first row done.
                     "    add         %1,     %1,     w4, UXTW   \n" // Advance srcY by rowBytes to move to next row.
                     "    sub         %0,     %0,     #64    \n" // Back up dest by 16 pixels/64 bytes.
                     "    add         %0,     %0,     w4, UXTW #2 \n" // Advance dest by 4xrowBytes.
                     // Read 16 Y from second row.
                     "    ld1         {v5.16b}, [%1],#16     \n" // Load 16 Y pixels into v5.
                     "    ushll2      v6.8h,  v5.16b, #7     \n" // Multiply second 8 pixels by 128, store in v6.
                     "    ushll       v5.8h,  v5.8b,  #7     \n" // Multiply first 8 pixels by 128, store in v5 (overwriting).
                     // Add luma and chroma values. First 8 pixels.
                     "    sqadd       v7.8h,  v5.8h,  v8.8h  \n" // Y+R
                     "    sqshrun     v28.8b, v7.8h,  #7     \n" // Divide by 128, and clamp to range [0, 255].
                     "    sqadd       v7.8h,  v5.8h,  v10.8h \n" // Y+G
                     "    sqshrun     v29.8b, v7.8h,  #7     \n" // Divide by 128, and clamp to range [0, 255].
                     "    sqadd       v7.8h,  v5.8h,  v12.8h \n" // Y+B
                     "    sqshrun     v30.8b, v7.8h,  #7     \n" // Divide by 128, and clamp to range [0, 255].
                     "    st4         {v28.8b,v29.8b,v30.8b,v31.8b}, [%0],#32 \n" // Interleave.
                     // Add luma and chroma values. Second 8 pixels.
                     "    sqadd       v7.8h,  v6.8h,  v9.8h  \n" // Y+R
                     "    sqshrun     v28.8b, v7.8h,  #7     \n" // Divide by 128, and clamp to range [0, 255].
                     "    sqadd       v7.8h,  v6.8h,  v11.8h \n" // Y+G
                     "    sqshrun     v29.8b, v7.8h,  #7     \n" // Divide by 128, and clamp to range [0, 255].
                     "    sqadd       v7.8h,  v6.8h,  v13.8h \n" // Y+B
                     "    sqshrun     v30.8b, v7.8h,  #7     \n" // Divide by 128, and clamp to range [0, 255].
                     "    st4         {v28.8b,v29.8b,v30.8b,v31.8b}, [%0],#32 \n" // Interleave.
                     // 16 pixels of second row done.
                     "    sub         %1,     %1,     w4, UXTW   \n" // Back up srcY by rowBytes to move to previous row.
                     "    sub         %0,     %0,     w4, UXTW #2 \n" // Back up dest by 4xrowBytes.
                     "    subs        w5,     w5,     #1     \n" // Decrement iteration count.
                     "    b.ne        1b                     \n" // Repeat unil iteration count is not zero.
                     // Two rows done.
                     "    add         %1,     %1,     w4, UXTW   \n" // Advance srcY by rowBytes to skip row we've already done.
                     "    add         %0,     %0,     w4, UXTW #2 \n" // Advance dest by 4xrowBytes to skip row we've already done.
                     "    subs        w6,     w6,     #1     \n" // Decrement iteration count.
                     "    b.ne        0b                     \n" // Repeat unil iteration count is not zero.
                     // Pop saved SIMD registers.
                     "    ldp         d8,     d9,     [sp,#0]  \n"
                     "    ldp         d10,    d11,    [sp,#16] \n"
                     "    ldp         d12,    d13,    [sp,#32] \n"
                     "    ldp         d14,    d15,    [sp,#48] \n"
                     "    add         sp,     sp,     #64      \n"
                     :
                     : "r"(dest), "r"(srcY), "r"(srcCbCr), "r"(rowBytes), "r"(rows)
                     : "cc", "w4", "w5", "w6"
                     );
}

static void YCrCb422BiPlanarToRGBA_ARM_neon_asm(uint8_t * __restrict dest, uint8_t * __restrict srcY, uint8_t * __restrict srcCrCb, int32_t rowBytes, int32_t rows)
{
    __asm__ volatile(// Push SIMD registers that should be preserved (d8-d15).
                     "    sub         sp,     sp,     #64     \n"
                     "    stp         d8,     d9,     [sp,#0] \n"
                     "    stp         d10,    d11,    [sp,#16] \n"
                     "    stp         d12,    d13,    [sp,#32] \n"
                     "    stp         d14,    d15,    [sp,#48] \n"
                     // Setup factors etc.
                     "    movi        v0.8h,  #179           \n" // Load v0 with 8 copies of the 16 LSBs of R Cr factor =  1.402. 179/128 =  1.398 (179 = 0x00b3)
                     "    mvni        v1.8h,  #90            \n" // Load v1 with 8 copies of the 16 LSBs of G Cr factor = -0.714. -91/128 = -0.711 (-91 = 0xffa5 = NOT 90).
                     "    mvni        v2.8h,  #43            \n" // Load v2 with 8 copies of the 16 LSBs of G Cb factor = -0.344. -44/128 = -0.344 (-44 = 0xffd4 = NOT 43).
                     "    movi        v3.8h,  #227           \n" // Load v3 with 8 copies of the 16 LSBs of B Cb factor =  1.772. 227/128 =  1.774 (227 = 0x00e3).
                     "    movi        v4.8b,  #0x80          \n" // Load v4 with 8 copies of the 8 LSBs of (XOR with this value converts unsigned 8-bit val to signed 8-bit val - 128).
                     "    movi        v31.8b, #0xFF          \n" // Load v31 (A channel of destRGBA) with FF.
                     // Setup loop-related stuff.
                     "    mov         w4,     %w3            \n" // Save arg 3 (rowBytes) in w4 for later use in pointer arithmetic.
                     "    lsr         w6,     %w4,    #1     \n" // Save arg 4 (rows) divided by 2 in w6 for loop count.
                     "0:						             \n"
                     "    lsr         w5,     %w3,    #4     \n" // Save arg 3 (rowBytes) divided by 16 in w5 for loop count.
                     "1:						             \n"
                     // Read 8 CbCr pixels, convert to 16-bit.
                     "    ld2         {v10.8b, v11.8b}, [%2],#16 \n" // Load 8 CrCb pixels, de-interleaving Cr into lower half of v10, Cb into lower half of v11.
                     "    eor         v10.8b, v10.8b, v4.8b  \n" // Subtract 128 from Cr. Result is signed.
                     "    eor         v11.8b, v11.8b, v4.8b  \n" // Subtract 128 from Cb. Result is signed.
                     "    sxtl        v5.8h,  v10.8b         \n" // Sign-extend Cr to 16 bit in v5.
                     "    sxtl        v6.8h,  v11.8b         \n" // Sign-extend Cb to 16 bit in v6.
                     // Calculate red, then scale width by 2.
                     "    mul         v7.8h,  v0.8h,  v5.8h  \n" // R is now signed 16 bit in v4.
                     "    zip1        v8.8h,  v7.8h,  v7.8h  \n" // Interleave the original and the copy.
                     "    zip2        v9.8h,  v7.8h,  v7.8h  \n" // Interleave the original and the copy.
                     // Calculate green, then scale width by 2.
                     "    mul         v7.8h,  v1.8h,  v5.8h  \n"
                     "    mla         v7.8h,  v2.8h,  v6.8h  \n" // G is now signed 16 bit in v4.
                     "    zip1        v10.8h, v7.8h,  v7.8h  \n" // Interleave the original and the copy.
                     "    zip2        v11.8h, v7.8h,  v7.8h  \n" // Interleave the original and the copy.
                     // Calculate blue, then scale width by 2.
                     "    mul         v7.8h,  v3.8h,  v6.8h  \n" // B is now signed 16 bit in v4.
                     "    zip1        v12.8h, v7.8h,  v7.8h  \n" // Interleave the original and the copy.
                     "    zip2        v13.8h, v7.8h,  v7.8h  \n" // Interleave the original and the copy.
                     // Read 16 Y from first row.
                     "    ld1         {v5.16b}, [%1]         \n" // Load 16 Y pixels into v5. N.B. No post-increment.
                     "    ushll2      v6.8h,  v5.16b, #7     \n" // Multiply second 8 pixels by 128, store in v6.
                     "    ushll       v5.8h,  v5.8b,  #7     \n" // Multiply first 8 pixels by 128, store in v5 (overwriting).
                     // Add luma and chroma values. First 8 pixels.
                     "    sqadd       v7.8h,  v5.8h,  v8.8h  \n" // Y+R
                     "    sqshrun     v28.8b, v7.8h,  #7     \n" // Divide by 128, and clamp to range [0, 255].
                     "    sqadd       v7.8h,  v5.8h,  v10.8h \n" // Y+G
                     "    sqshrun     v29.8b, v7.8h,  #7     \n" // Divide by 128, and clamp to range [0, 255].
                     "    sqadd       v7.8h,  v5.8h,  v12.8h \n" // Y+B
                     "    sqshrun     v30.8b, v7.8h,  #7     \n" // Divide by 128, and clamp to range [0, 255].
                     "    st4         {v28.8b,v29.8b,v30.8b,v31.8b}, [%0],#32 \n" // Interleave.
                     // Add luma and chroma values. Second 8 pixels.
                     "    sqadd       v7.8h,  v6.8h,  v9.8h  \n" // Y+R
                     "    sqshrun     v28.8b, v7.8h,  #7     \n" // Divide by 128, and clamp to range [0, 255].
                     "    sqadd       v7.8h,  v6.8h,  v11.8h \n" // Y+G
                     "    sqshrun     v29.8b, v7.8h,  #7     \n" // Divide by 128, and clamp to range [0, 255].
                     "    sqadd       v7.8h,  v6.8h,  v13.8h \n" // Y+B
                     "    sqshrun     v30.8b, v7.8h,  #7     \n" // Divide by 128, and clamp to range [0, 255].
                     "    st4         {v28.8b,v29.8b,v30.8b,v31.8b}, [%0],#32 \n" // Interleave.
                     // 16 pixels of first row done.
                     "    add         %1,     %1,     w4, UXTW   \n" // Advance srcY by rowBytes to move to next row.
                     "    sub         %0,     %0,     #64    \n" // Back up dest by 16 pixels/64 bytes.
                     "    add         %0,     %0,     w4, UXTW #2 \n" // Advance dest by 4xrowBytes.
                     // Read 16 Y from second row.
                     "    ld1         {v5.16b}, [%1],#16     \n" // Load 16 Y pixels into v5.
                     "    ushll2      v6.8h,  v5.16b, #7     \n" // Multiply second 8 pixels by 128, store in v6.
                     "    ushll       v5.8h,  v5.8b,  #7     \n" // Multiply first 8 pixels by 128, store in v5 (overwriting).
                     // Add luma and chroma values. First 8 pixels.
                     "    sqadd       v7.8h,  v5.8h,  v8.8h  \n" // Y+R
                     "    sqshrun     v28.8b, v7.8h,  #7     \n" // Divide by 128, and clamp to range [0, 255].
                     "    sqadd       v7.8h,  v5.8h,  v10.8h \n" // Y+G
                     "    sqshrun     v29.8b, v7.8h,  #7     \n" // Divide by 128, and clamp to range [0, 255].
                     "    sqadd       v7.8h,  v5.8h,  v12.8h \n" // Y+B
                     "    sqshrun     v30.8b, v7.8h,  #7     \n" // Divide by 128, and clamp to range [0, 255].
                     "    st4         {v28.8b,v29.8b,v30.8b,v31.8b}, [%0],#32 \n" // Interleave.
                     // Add luma and chroma values. Second 8 pixels.
                     "    sqadd       v7.8h,  v6.8h,  v9.8h  \n" // Y+R
                     "    sqshrun     v28.8b, v7.8h,  #7     \n" // Divide by 128, and clamp to range [0, 255].
                     "    sqadd       v7.8h,  v6.8h,  v11.8h \n" // Y+G
                     "    sqshrun     v29.8b, v7.8h,  #7     \n" // Divide by 128, and clamp to range [0, 255].
                     "    sqadd       v7.8h,  v6.8h,  v13.8h \n" // Y+B
                     "    sqshrun     v30.8b, v7.8h,  #7     \n" // Divide by 128, and clamp to range [0, 255].
                     "    st4         {v28.8b,v29.8b,v30.8b,v31.8b}, [%0],#32 \n" // Interleave.
                     // 16 pixels of second row done.
                     "    sub         %1,     %1,     w4, UXTW   \n" // Back up srcY by rowBytes to move to previous row.
                     "    sub         %0,     %0,     w4, UXTW #2 \n" // Back up dest by 4xrowBytes.
                     "    subs        w5,     w5,     #1     \n" // Decrement iteration count.
                     "    b.ne        1b                     \n" // Repeat unil iteration count is not zero.
                     // Two rows done.
                     "    add         %1,     %1,     w4, UXTW   \n" // Advance srcY by rowBytes to skip row we've already done.
                     "    add         %0,     %0,     w4, UXTW #2 \n" // Advance dest by 4xrowBytes to skip row we've already done.
                     "    subs        w6,     w6,     #1     \n" // Decrement iteration count.
                     "    b.ne        0b                     \n" // Repeat unil iteration count is not zero.
                     // Pop saved SIMD registers.
                     "    ldp         d8,     d9,     [sp,#0]  \n"
                     "    ldp         d10,    d11,    [sp,#16] \n"
                     "    ldp         d12,    d13,    [sp,#32] \n"
                     "    ldp         d14,    d15,    [sp,#48] \n"
                     "    add         sp,     sp,     #64      \n"
                     :
                     : "r"(dest), "r"(srcY), "r"(srcCrCb), "r"(rowBytes), "r"(rows)
                     : "cc", "w4", "w5", "w6"
                     );
}
#endif

#if defined(HAVE_ARM_NEON) || defined(HAVE_ARM64_NEON)
bool VideoSource::fastPath()
{
    if (m_fastPath == -1) {
        if (videoWidth % 16 != 0 || videoHeight % 2 != 0) {
            m_fastPath = 0;
        } else {
#  if defined(ANDROID) && !defined(HAVE_ARM64_NEON)
            // Not all Android devices with ARMv7 are guaranteed to have NEON, so check.
            uint64_t features = android_getCpuFeatures();
            m_fastPath = (features & ANDROID_CPU_ARM_FEATURE_ARMv7) && (features & ANDROID_CPU_ARM_FEATURE_NEON);
#  else
            m_fastPath = 1;
#  endif
            if (m_fastPath) ARLOGi("VideoSource::updateTexture32 will use ARM NEON acceleration.\n");
        }
    }
    return (m_fastPath == 1);
}
#endif // HAVE_ARM_NEON || HAVE_ARM64_NEON

bool VideoSource::updateTexture32(uint32_t *buffer) {
    
    static int lastFrameStamp = 0;
    
    if (!buffer) return false; // Sanity check.
    
    if (!frameBuffer) return false; // Check that a frame is actually available.
    
    // Extra check: don't update the array if the current frame is the same is previous one.
    if (lastFrameStamp == frameStamp) return false;
    
    int pixelSize = arUtilGetPixelSize(pixelFormat);
    switch (pixelFormat) {
        case AR_PIXEL_FORMAT_BGRA:
        case AR_PIXEL_FORMAT_BGR:
            for (int y = 0; y < videoHeight; y++) {
                ARUint8 *inp = &frameBuffer[videoWidth*y*pixelSize];
                uint32_t *outp = &buffer[videoWidth*y];
                for (int pixelsToGo = videoWidth; pixelsToGo > 0; pixelsToGo--) {
#ifdef AR_LITTLE_ENDIAN
                    *outp = 0xff000000 | (*(inp + 0) << 16) | (*(inp + 1) << 8) | (*(inp + 2));
#else
                    *outp = (*(inp + 2) << 24) | (*(inp + 1) << 16) | (*(inp + 0) << 8) | 0xff;
#endif
                    inp += pixelSize;
                    outp++;
                }
            }
            break;
        case AR_PIXEL_FORMAT_RGBA:
        case AR_PIXEL_FORMAT_RGB:
            for (int y = 0; y < videoHeight; y++) {
                ARUint8 *inp = &frameBuffer[videoWidth*y*pixelSize];
                uint32_t *outp = &buffer[videoWidth*y];
                for (int pixelsToGo = videoWidth; pixelsToGo > 0; pixelsToGo--) {
#ifdef AR_LITTLE_ENDIAN
                    *outp = 0xff000000 | (*(inp + 2) << 16) | (*(inp + 1) << 8) | (*(inp + 0));
#else
                    *outp = (*(inp + 0) << 24) | (*(inp + 1) << 16) | (*(inp + 2) << 8) | 0xff;
#endif
                    inp += pixelSize;
                    outp++;
                }
            }
            break;
        case AR_PIXEL_FORMAT_ARGB:
            for (int y = 0; y < videoHeight; y++) {
                ARUint8 *inp = &frameBuffer[videoWidth*y*pixelSize];
                uint32_t *outp = &buffer[videoWidth*y];
                for (int pixelsToGo = videoWidth; pixelsToGo > 0; pixelsToGo--) {
#ifdef AR_LITTLE_ENDIAN
                    *outp = 0xff000000 | (*(inp + 3) << 16) | (*(inp + 2) << 8) | (*(inp + 1));
#else
                    *outp = (*(inp + 1) << 24) | (*(inp + 2) << 16) | (*(inp + 3) << 8) | 0xff;
#endif
                    inp += pixelSize;
                    outp++;
                }
            }
            break;
        case AR_PIXEL_FORMAT_ABGR:
            for (int y = 0; y < videoHeight; y++) {
                ARUint8 *inp = &frameBuffer[videoWidth*y*pixelSize];
                uint32_t *outp = &buffer[videoWidth*y];
                for (int pixelsToGo = videoWidth; pixelsToGo > 0; pixelsToGo--) {
#ifdef AR_LITTLE_ENDIAN
                    *outp = 0xff000000 | (*(inp + 1) << 16) | (*(inp + 2) << 8) | (*(inp + 3));
#else
                    *outp = (*(inp + 3) << 24) | (*(inp + 2) << 16) | (*(inp + 1) << 8) | 0xff;
#endif
                    inp += pixelSize;
                    outp++;
                }
            }
            break;
        case AR_PIXEL_FORMAT_MONO:
            for (int y = 0; y < videoHeight; y++) {
                ARUint8 *inp = &frameBuffer[videoWidth*y];
                uint32_t *outp = &buffer[videoWidth*y];
                for (int pixelsToGo = videoWidth; pixelsToGo > 0; pixelsToGo--) {
#ifdef AR_LITTLE_ENDIAN
                    *outp = 0xff000000 | (*inp << 16) | (*inp << 8) | *inp;
#else
                    *outp = (*inp << 24) | (*inp << 16) | (*inp << 8) | 0xff;
#endif
                    inp++;
                    outp++;
                }
            }
            break;
        case AR_PIXEL_FORMAT_420f:
        {
#if defined(HAVE_ARM_NEON) || defined(HAVE_ARM64_NEON)
            if (fastPath()) {
                YCbCr422BiPlanarToRGBA_ARM_neon_asm((uint8_t *)buffer, frameBuffer, frameBuffer2, videoWidth, videoHeight);
            } else {
#endif
                ARUint8 *pY0 = frameBuffer, *pY1 = frameBuffer + videoWidth;
                ARUint8 *pCbCr = frameBuffer2;
                uint8_t *outp0 = (uint8_t *)buffer;
                uint8_t *outp1 = ((uint8_t *)buffer) + videoWidth*4;
                int wd2 = videoWidth >> 1;
                int hd2 = videoHeight >> 1;
                for (int y = 0; y < hd2; y++) { // Groups of two pixels.
                    for (int x = 0; x < wd2; x++) { // Groups of two pixels.
                        int16_t Cb, Cr;
                        int16_t Y0, Y1, R, G, B;
                        uint8_t R0, R1, G0, G1, B0, B1;
                        Cb = ((int16_t)(*(pCbCr++))) - 128;
                        Cr = ((int16_t)(*(pCbCr++))) - 128;
                        R = (        179*Cr) >> 7;
                        G = (-44*Cb - 91*Cr) >> 7;
                        B = (227*Cb        ) >> 7;
                        Y0 = *(pY0++);
                        Y1 = *(pY0++);
                        R0 = (uint8_t)CLAMP(Y0 + R, 0, 255);
                        R1 = (uint8_t)CLAMP(Y1 + R, 0, 255);
                        G0 = (uint8_t)CLAMP(Y0 + G, 0, 255);
                        G1 = (uint8_t)CLAMP(Y1 + G, 0, 255);
                        B0 = (uint8_t)CLAMP(Y0 + B, 0, 255);
                        B1 = (uint8_t)CLAMP(Y1 + B, 0, 255);
                        *(outp0++) = R0;
                        *(outp0++) = G0;
                        *(outp0++) = B0;
                        *(outp0++) = 255;
                        *(outp1++) = R1;
                        *(outp1++) = G1;
                        *(outp1++) = B1;
                        *(outp1++) = 255;
                        Y0 = *(pY1++);
                        Y1 = *(pY1++);
                        R0 = (uint8_t)CLAMP(Y0 + R, 0, 255);
                        R1 = (uint8_t)CLAMP(Y1 + R, 0, 255);
                        G0 = (uint8_t)CLAMP(Y0 + G, 0, 255);
                        G1 = (uint8_t)CLAMP(Y1 + G, 0, 255);
                        B0 = (uint8_t)CLAMP(Y0 + B, 0, 255);
                        B1 = (uint8_t)CLAMP(Y1 + B, 0, 255);
                        *(outp0++) = R0;
                        *(outp0++) = G0;
                        *(outp0++) = B0;
                        *(outp0++) = 255;
                        *(outp1++) = R1;
                        *(outp1++) = G1;
                        *(outp1++) = B1;
                        *(outp1++) = 255;
                    }
                    pY0 += videoWidth;
                    pY1 += videoWidth;
                    outp0 += videoWidth*4;
                    outp1 += videoWidth*4;
                }
#if defined(HAVE_ARM_NEON) || defined(HAVE_ARM64_NEON)
            }
#endif
        }
            break;
        case AR_PIXEL_FORMAT_NV21:
        {
#if defined(HAVE_ARM_NEON) || defined(HAVE_ARM64_NEON)
            if (fastPath()) {
                YCrCb422BiPlanarToRGBA_ARM_neon_asm((uint8_t *)buffer, frameBuffer, frameBuffer2, videoWidth, videoHeight);
            } else {
#endif
                //color_convert_common((unsigned char *)frameBuffer, (unsigned char *)frameBuffer2, videoWidth, videoHeight, (unsigned char *)buffer);
                ARUint8 *pY0 = frameBuffer, *pY1 = frameBuffer + videoWidth;
                ARUint8 *pCrCb = frameBuffer2;
                uint8_t *outp0 = (uint8_t *)buffer;
                uint8_t *outp1 = ((uint8_t *)buffer) + videoWidth*4;
                int wd2 = videoWidth >> 1;
                int hd2 = videoHeight >> 1;
                for (int y = 0; y < hd2; y++) { // Groups of two pixels.
                    for (int x = 0; x < wd2; x++) { // Groups of two pixels.
                        int16_t Cb, Cr;
                        int16_t Y0, Y1, R, G, B;
                        uint8_t R0, R1, G0, G1, B0, B1;
                        Cr = ((int16_t)(*(pCrCb++))) - 128;
                        Cb = ((int16_t)(*(pCrCb++))) - 128;
                        R = (        179*Cr) >> 7;
                        G = (-44*Cb - 91*Cr) >> 7;
                        B = (227*Cb        ) >> 7;
                        Y0 = *(pY0++);
                        Y1 = *(pY0++);
                        R0 = (uint8_t)CLAMP(Y0 + R, 0, 255);
                        R1 = (uint8_t)CLAMP(Y1 + R, 0, 255);
                        G0 = (uint8_t)CLAMP(Y0 + G, 0, 255);
                        G1 = (uint8_t)CLAMP(Y1 + G, 0, 255);
                        B0 = (uint8_t)CLAMP(Y0 + B, 0, 255);
                        B1 = (uint8_t)CLAMP(Y1 + B, 0, 255);
                        *(outp0++) = R0;
                        *(outp0++) = G0;
                        *(outp0++) = B0;
                        *(outp0++) = 255;
                        *(outp1++) = R1;
                        *(outp1++) = G1;
                        *(outp1++) = B1;
                        *(outp1++) = 255;
                        Y0 = *(pY1++);
                        Y1 = *(pY1++);
                        R0 = (uint8_t)CLAMP(Y0 + R, 0, 255);
                        R1 = (uint8_t)CLAMP(Y1 + R, 0, 255);
                        G0 = (uint8_t)CLAMP(Y0 + G, 0, 255);
                        G1 = (uint8_t)CLAMP(Y1 + G, 0, 255);
                        B0 = (uint8_t)CLAMP(Y0 + B, 0, 255);
                        B1 = (uint8_t)CLAMP(Y1 + B, 0, 255);
                        *(outp0++) = R0;
                        *(outp0++) = G0;
                        *(outp0++) = B0;
                        *(outp0++) = 255;
                        *(outp1++) = R1;
                        *(outp1++) = G1;
                        *(outp1++) = B1;
                        *(outp1++) = 255;
                    }
                    pY0 += videoWidth;
                    pY1 += videoWidth;
                    outp0 += videoWidth*4;
                    outp1 += videoWidth*4;
                }
#if defined(HAVE_ARM_NEON) || defined(HAVE_ARM64_NEON)
            }
#endif
        }
            break;
        default:
            return false;
            break;
    }
    
    lastFrameStamp = frameStamp; // Record the new framestamp
    return true;
    
}

#ifndef _WINRT
void VideoSource::updateTextureGL(int textureID) {

	static int lastFrameStamp = 0;

	// Don't update the array if the current frame is the same as previous one.
	if (lastFrameStamp == frameStamp) return;

	// Record the new framestamp
	lastFrameStamp = frameStamp;

	if (textureID && frameBuffer) { // Could also chcek glIsTexture(textureID), but it is slow.
		
		//int val;
		//glGetIntegerv(GL_TEXTURE_BINDING_2D, &val);
		glBindTexture(GL_TEXTURE_2D, textureID);
#if TARGET_PLATFORM_IOS
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        glTexImage2D(GL_TEXTURE_2D, 0, glPixIntFormat, videoWidth, videoHeight, 0, glPixFormat, glPixType, frameBuffer);
#else
		glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, videoWidth, videoHeight, glPixFormat, glPixType, frameBuffer);
#endif
		//glBindTexture(GL_TEXTURE_2D, val);

	}
}
#endif // !_WINRT
