You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1055 lines
40 KiB
1055 lines
40 KiB
/* |
|
* Copyright (C) 2012 Intel Corporation. All rights reserved. |
|
* |
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
* you may not use this file except in compliance with the License. |
|
* You may obtain a copy of the License at |
|
* |
|
* http://www.apache.org/licenses/LICENSE-2.0 |
|
* |
|
* Unless required by applicable law or agreed to in writing, software |
|
* distributed under the License is distributed on an "AS IS" BASIS, |
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
* See the License for the specific language governing permissions and |
|
* limitations under the License. |
|
* |
|
*/ |
|
#include <cutils/properties.h> |
|
#include <system/graphics.h> |
|
#include <nativebase/nativebase.h> |
|
|
|
#include "isv_worker.h" |
|
#ifndef TARGET_VPP_USE_GEN |
|
#include <hal_public.h> |
|
#else |
|
#include <ufo/graphics.h> |
|
#endif |
|
|
|
//#define LOG_NDEBUG 0 |
|
#undef LOG_TAG |
|
#define LOG_TAG "isv-omxil" |
|
|
|
#define CHECK_VASTATUS(str) \ |
|
do { \ |
|
if (vaStatus != VA_STATUS_SUCCESS) { \ |
|
ALOGE("%s failed\n", str); \ |
|
return STATUS_ERROR;} \ |
|
}while(0); |
|
|
|
enum STRENGTH { |
|
STRENGTH_LOW = 0, |
|
STRENGTH_MEDIUM, |
|
STRENGTH_HIGH |
|
}; |
|
|
|
#define DENOISE_DEBLOCK_STRENGTH STRENGTH_MEDIUM |
|
#define COLOR_STRENGTH STRENGTH_MEDIUM |
|
#ifdef TARGET_VPP_USE_GEN |
|
#define COLOR_NUM 4 |
|
#else |
|
#define COLOR_NUM 2 |
|
#endif |
|
|
|
#define MAX_FRC_OUTPUT 4 /*for frcx4*/ |
|
|
|
using namespace android; |
|
|
|
ISVWorker::ISVWorker() |
|
:mNumForwardReferences(0), |
|
mVAContext(VA_INVALID_ID), |
|
mWidth(0), mHeight(0), |
|
mDisplay(NULL), mVADisplay(NULL), |
|
mVAConfig(VA_INVALID_ID), |
|
mForwardReferences(NULL), |
|
mPrevInput(0), mPrevOutput(0), |
|
mNumFilterBuffers(0), |
|
mFilterFrc(VA_INVALID_ID), mFilters(0), |
|
mInputIndex(0), mOutputIndex(0), |
|
mOutputCount(0) { |
|
memset(&mFilterBuffers, VA_INVALID_ID, VAProcFilterCount * sizeof(VABufferID)); |
|
memset(&mFilterParam, 0, sizeof(mFilterParam)); |
|
} |
|
|
|
bool ISVWorker::isSupport() const { |
|
bool support = false; |
|
|
|
int num_entrypoints = vaMaxNumEntrypoints(mVADisplay); |
|
VAEntrypoint * entrypoints = (VAEntrypoint *)malloc(num_entrypoints * sizeof(VAEntrypoint)); |
|
if (entrypoints == NULL) { |
|
ALOGE("failed to malloc entrypoints array\n"); |
|
return false; |
|
} |
|
|
|
// check if it contains VPP entry point VAEntrypointVideoProc |
|
VAStatus vaStatus = vaQueryConfigEntrypoints(mVADisplay, VAProfileNone, entrypoints, &num_entrypoints); |
|
if (vaStatus != VA_STATUS_SUCCESS) { |
|
ALOGE("vaQueryConfigEntrypoints failed"); |
|
return false; |
|
} |
|
for (int i = 0; !support && i < num_entrypoints; i++) { |
|
support = entrypoints[i] == VAEntrypointVideoProc; |
|
} |
|
free(entrypoints); |
|
entrypoints = NULL; |
|
|
|
return support; |
|
} |
|
|
|
uint32_t ISVWorker::getProcBufCount() { |
|
return getOutputBufCount(mInputIndex); |
|
} |
|
|
|
uint32_t ISVWorker::getFillBufCount() { |
|
return getOutputBufCount(mOutputIndex); |
|
} |
|
|
|
uint32_t ISVWorker::getOutputBufCount(uint32_t index) { |
|
uint32_t bufCount = 1; |
|
if (((mFilters & FilterFrameRateConversion) != 0) |
|
&& index > 0) |
|
bufCount = mFilterParam.frcRate - (((mFilterParam.frcRate == FRC_RATE_2_5X) ? (index & 1): 0)); |
|
return bufCount; |
|
} |
|
|
|
|
|
status_t ISVWorker::init(uint32_t width, uint32_t height) { |
|
ALOGV("init"); |
|
|
|
if (mDisplay != NULL) { |
|
ALOGE("VA is particially started"); |
|
return STATUS_ERROR; |
|
} |
|
mDisplay = new Display; |
|
*mDisplay = ANDROID_DISPLAY_HANDLE; |
|
|
|
mVADisplay = vaGetDisplay(mDisplay); |
|
if (mVADisplay == NULL) { |
|
ALOGE("vaGetDisplay failed"); |
|
return STATUS_ERROR; |
|
} |
|
|
|
int majorVersion, minorVersion; |
|
VAStatus vaStatus = vaInitialize(mVADisplay, &majorVersion, &minorVersion); |
|
CHECK_VASTATUS("vaInitialize"); |
|
|
|
// Check if VPP entry point is supported |
|
if (!isSupport()) { |
|
ALOGE("VPP is not supported on current platform"); |
|
return STATUS_NOT_SUPPORT; |
|
} |
|
|
|
// Find out the format for the target |
|
VAConfigAttrib attrib; |
|
attrib.type = VAConfigAttribRTFormat; |
|
vaStatus = vaGetConfigAttributes(mVADisplay, VAProfileNone, VAEntrypointVideoProc, &attrib, 1); |
|
CHECK_VASTATUS("vaGetConfigAttributes"); |
|
|
|
if ((attrib.value & VA_RT_FORMAT_YUV420) == 0) { |
|
ALOGE("attribute is %x vs wanted %x", attrib.value, VA_RT_FORMAT_YUV420); |
|
return STATUS_NOT_SUPPORT; |
|
} |
|
|
|
ALOGV("ready to create config"); |
|
// Create the configuration |
|
vaStatus = vaCreateConfig(mVADisplay, VAProfileNone, VAEntrypointVideoProc, &attrib, 1, &mVAConfig); |
|
CHECK_VASTATUS("vaCreateConfig"); |
|
|
|
|
|
// Create Context |
|
ALOGV("ready to create context"); |
|
mWidth = width; |
|
mHeight = height; |
|
vaStatus = vaCreateContext(mVADisplay, mVAConfig, mWidth, mHeight, 0, NULL, 0, &mVAContext); |
|
CHECK_VASTATUS("vaCreateContext"); |
|
|
|
ALOGV("VA has been successfully started"); |
|
return STATUS_OK; |
|
} |
|
|
|
status_t ISVWorker::deinit() { |
|
{ |
|
Mutex::Autolock autoLock(mPipelineBufferLock); |
|
while (!mPipelineBuffers.isEmpty()) { |
|
VABufferID pipelineBuffer = mPipelineBuffers.itemAt(0); |
|
if (VA_STATUS_SUCCESS != vaDestroyBuffer(mVADisplay, pipelineBuffer)) |
|
ALOGW("%s: failed to destroy va buffer id %d", __func__, pipelineBuffer); |
|
mPipelineBuffers.removeAt(0); |
|
} |
|
} |
|
|
|
if (mNumFilterBuffers != 0) { |
|
for (uint32_t i = 0; i < mNumFilterBuffers; i++) { |
|
if(VA_STATUS_SUCCESS != vaDestroyBuffer(mVADisplay, mFilterBuffers[i])) |
|
ALOGW("%s: failed to destroy va buffer id %d", __func__, mFilterBuffers[i]); |
|
} |
|
mNumFilterBuffers = 0; |
|
memset(&mFilterBuffers, VA_INVALID_ID, VAProcFilterCount * sizeof(VABufferID)); |
|
mFilterFrc = VA_INVALID_ID; |
|
} |
|
|
|
if (mForwardReferences != NULL) { |
|
free(mForwardReferences); |
|
mForwardReferences = NULL; |
|
mNumForwardReferences = 0; |
|
} |
|
|
|
if (mVAContext != VA_INVALID_ID) { |
|
vaDestroyContext(mVADisplay, mVAContext); |
|
mVAContext = VA_INVALID_ID; |
|
} |
|
|
|
if (mVAConfig != VA_INVALID_ID) { |
|
vaDestroyConfig(mVADisplay, mVAConfig); |
|
mVAConfig = VA_INVALID_ID; |
|
} |
|
|
|
if (mVADisplay) { |
|
vaTerminate(mVADisplay); |
|
mVADisplay = NULL; |
|
} |
|
|
|
if (mDisplay) { |
|
delete mDisplay; |
|
mDisplay = NULL; |
|
} |
|
|
|
return STATUS_OK; |
|
} |
|
|
|
status_t ISVWorker::allocSurface(uint32_t* width, uint32_t* height, |
|
uint32_t stride, uint32_t format, unsigned long handle, int32_t* surfaceId) |
|
{ |
|
if (mWidth == 0 || mHeight == 0) { |
|
ALOGE("%s: isv worker has not been initialized.", __func__); |
|
return STATUS_ERROR; |
|
} |
|
|
|
#ifndef TARGET_VPP_USE_GEN |
|
*width = mWidth; |
|
*height = mHeight; |
|
#endif |
|
// Create VASurfaces |
|
VASurfaceAttrib attribs[3]; |
|
VASurfaceAttribExternalBuffers vaExtBuf; |
|
|
|
memset(&vaExtBuf, 0, sizeof(VASurfaceAttribExternalBuffers)); |
|
switch(format) { |
|
case HAL_PIXEL_FORMAT_YV12: |
|
vaExtBuf.pixel_format = VA_FOURCC_YV12; |
|
vaExtBuf.num_planes = 3; |
|
vaExtBuf.pitches[0] = stride; |
|
vaExtBuf.pitches[1] = stride / 2; |
|
vaExtBuf.pitches[2] = stride / 2; |
|
vaExtBuf.pitches[3] = 0; |
|
vaExtBuf.offsets[0] = 0; |
|
vaExtBuf.offsets[1] = stride * *height; |
|
vaExtBuf.offsets[2] = vaExtBuf.offsets[1] + (stride / 2) * (*height / 2); |
|
vaExtBuf.offsets[3] = 0; |
|
break; |
|
case HAL_PIXEL_FORMAT_INTEL_YV12: |
|
vaExtBuf.pixel_format = VA_FOURCC_YV12; |
|
vaExtBuf.num_planes = 3; |
|
vaExtBuf.pitches[0] = stride; |
|
vaExtBuf.pitches[1] = stride / 2; |
|
vaExtBuf.pitches[2] = stride / 2; |
|
vaExtBuf.pitches[3] = 0; |
|
vaExtBuf.offsets[0] = 0; |
|
// The height of HAL_PIXEL_FORMAT_INTEL_YV12 gralloc buffer has been aligned with 32 pixels. |
|
vaExtBuf.offsets[1] = stride * ((*height + 31) & ~31); |
|
vaExtBuf.offsets[2] = vaExtBuf.offsets[1] + (stride / 2) * (((*height + 31) & ~31) / 2); |
|
vaExtBuf.offsets[3] = 0; |
|
break; |
|
#ifdef TARGET_VPP_USE_GEN |
|
case HAL_PIXEL_FORMAT_NV12_Y_TILED_INTEL: |
|
case HAL_PIXEL_FORMAT_NV12_X_TILED_INTEL: |
|
//it will be removed in future, it indicate the same format with HAL_PIXEL_FORMAT_NV12_Y_TILED_INTEL |
|
case HAL_PIXEL_FORMAT_YUV420PackedSemiPlanar_Tiled_INTEL: |
|
#else |
|
case HAL_PIXEL_FORMAT_NV12_VED: |
|
case HAL_PIXEL_FORMAT_NV12_VEDT: |
|
#endif |
|
vaExtBuf.pixel_format = VA_FOURCC_NV12; |
|
vaExtBuf.num_planes = 2; |
|
vaExtBuf.pitches[0] = stride; |
|
vaExtBuf.pitches[1] = stride; |
|
vaExtBuf.pitches[2] = 0; |
|
vaExtBuf.pitches[3] = 0; |
|
vaExtBuf.offsets[0] = 0; |
|
vaExtBuf.offsets[1] = stride * *height; |
|
vaExtBuf.offsets[2] = 0; |
|
vaExtBuf.offsets[3] = 0; |
|
break; |
|
default: |
|
ALOGE("%s: can't support this format 0x%08x", __func__, format); |
|
return STATUS_ERROR; |
|
} |
|
vaExtBuf.width = *width; |
|
vaExtBuf.height = *height; |
|
vaExtBuf.data_size = stride * *height * 1.5; |
|
vaExtBuf.num_buffers = 1; |
|
#ifndef TARGET_VPP_USE_GEN |
|
if (format == HAL_PIXEL_FORMAT_NV12_VEDT) { |
|
ALOGV("set TILING flag"); |
|
vaExtBuf.flags |= VA_SURFACE_EXTBUF_DESC_ENABLE_TILING; |
|
} |
|
#endif |
|
vaExtBuf.flags |= VA_SURFACE_ATTRIB_MEM_TYPE_ANDROID_GRALLOC; |
|
vaExtBuf.buffers = (long unsigned int*)&handle; |
|
|
|
attribs[0].type = (VASurfaceAttribType)VASurfaceAttribMemoryType; |
|
attribs[0].flags = VA_SURFACE_ATTRIB_SETTABLE; |
|
attribs[0].value.type = VAGenericValueTypeInteger; |
|
attribs[0].value.value.i = VA_SURFACE_ATTRIB_MEM_TYPE_ANDROID_GRALLOC; |
|
|
|
attribs[1].type = (VASurfaceAttribType)VASurfaceAttribExternalBufferDescriptor; |
|
attribs[1].flags = VA_SURFACE_ATTRIB_SETTABLE; |
|
attribs[1].value.type = VAGenericValueTypePointer; |
|
attribs[1].value.value.p = &vaExtBuf; |
|
|
|
attribs[2].type = (VASurfaceAttribType)VASurfaceAttribUsageHint; |
|
attribs[2].flags = VA_SURFACE_ATTRIB_SETTABLE; |
|
attribs[2].value.type = VAGenericValueTypeInteger; |
|
attribs[2].value.value.i = VA_SURFACE_ATTRIB_USAGE_HINT_VPP_READ; |
|
|
|
ALOGV("%s: Ext buffer: width %d, height %d, data_size %d, pitch %d", __func__, |
|
vaExtBuf.width, vaExtBuf.height, vaExtBuf.data_size, vaExtBuf.pitches[0]); |
|
VAStatus vaStatus = vaCreateSurfaces(mVADisplay, VA_RT_FORMAT_YUV420, vaExtBuf.width, |
|
vaExtBuf.height, (VASurfaceID*)surfaceId, 1, attribs, 3); |
|
CHECK_VASTATUS("vaCreateSurfaces"); |
|
|
|
return (vaStatus == VA_STATUS_SUCCESS) ? STATUS_OK : STATUS_ERROR; |
|
} |
|
|
|
status_t ISVWorker::freeSurface(int32_t* surfaceId) |
|
{ |
|
VAStatus vaStatus = VA_STATUS_SUCCESS; |
|
vaDestroySurfaces(mVADisplay, (VASurfaceID*)surfaceId, 1); |
|
CHECK_VASTATUS("vaDestroySurfaces"); |
|
|
|
return (vaStatus == VA_STATUS_SUCCESS) ? STATUS_OK : STATUS_ERROR; |
|
} |
|
|
|
status_t ISVWorker::configFilters(uint32_t filters, |
|
const FilterParam* filterParam) |
|
{ |
|
status_t ret = STATUS_OK; |
|
|
|
if (!filterParam) { |
|
ALOGE("%s: invalid filterParam", __func__); |
|
return STATUS_ERROR; |
|
} |
|
|
|
if (filters != 0) { |
|
mFilterParam.srcWidth = filterParam->srcWidth; |
|
mFilterParam.srcHeight = filterParam->srcHeight; |
|
mFilterParam.dstWidth = filterParam->dstWidth; |
|
mFilterParam.dstHeight = filterParam->dstHeight; |
|
mFilterParam.frameRate = filterParam->frameRate; |
|
mFilterParam.frcRate = filterParam->frcRate; |
|
} |
|
|
|
if (mFilters != filters) { |
|
mFilters = filters; |
|
ALOGI("%s: mFilters 0x%x, fps %d, frc rate %d", __func__, mFilters, mFilterParam.frameRate, mFilterParam.frcRate); |
|
ret = setupFilters(); |
|
} |
|
|
|
return ret; |
|
} |
|
|
|
bool ISVWorker::isFpsSupport(int32_t fps, int32_t *fpsSet, int32_t fpsSetCnt) { |
|
bool ret = false; |
|
for (int32_t i = 0; i < fpsSetCnt; i++) { |
|
if (fps == fpsSet[i]) { |
|
ret = true; |
|
break; |
|
} |
|
} |
|
|
|
return ret; |
|
} |
|
|
|
status_t ISVWorker::setupFilters() { |
|
ALOGV("setupFilters"); |
|
VAProcFilterParameterBuffer deblock, denoise, sharpen, stde; |
|
VAProcFilterParameterBufferDeinterlacing deint; |
|
VAProcFilterParameterBufferColorBalance color[COLOR_NUM]; |
|
VAProcFilterParameterBufferFrameRateConversion frc; |
|
VABufferID deblockId, denoiseId, deintId, sharpenId, colorId, frcId, stdeId; |
|
uint32_t numCaps; |
|
VAProcFilterCap deblockCaps, denoiseCaps, sharpenCaps, frcCaps, stdeCaps; |
|
VAProcFilterCapDeinterlacing deinterlacingCaps[VAProcDeinterlacingCount]; |
|
VAProcFilterCapColorBalance colorCaps[COLOR_NUM]; |
|
VAStatus vaStatus; |
|
uint32_t numSupportedFilters = VAProcFilterCount; |
|
VAProcFilterType supportedFilters[VAProcFilterCount]; |
|
|
|
if (mNumFilterBuffers != 0) { |
|
for (uint32_t i = 0; i < mNumFilterBuffers; i++) { |
|
if (VA_STATUS_SUCCESS != vaDestroyBuffer(mVADisplay, mFilterBuffers[i])) |
|
ALOGW("%s: failed to destroy va buffer %d", __func__, mFilterBuffers[i]); |
|
//return STATUS_ERROR; |
|
} |
|
memset(&mFilterBuffers, VA_INVALID_ID, VAProcFilterCount * sizeof(VABufferID)); |
|
mFilterFrc = VA_INVALID_ID; |
|
mNumFilterBuffers = 0; |
|
} |
|
|
|
// query supported filters |
|
vaStatus = vaQueryVideoProcFilters(mVADisplay, mVAContext, supportedFilters, &numSupportedFilters); |
|
CHECK_VASTATUS("vaQueryVideoProcFilters"); |
|
|
|
// create filter buffer for each filter |
|
for (uint32_t i = 0; i < numSupportedFilters; i++) { |
|
switch (supportedFilters[i]) { |
|
case VAProcFilterDeblocking: |
|
if ((mFilters & FilterDeblocking) != 0) { |
|
// check filter caps |
|
numCaps = 1; |
|
vaStatus = vaQueryVideoProcFilterCaps(mVADisplay, mVAContext, |
|
VAProcFilterDeblocking, |
|
&deblockCaps, |
|
&numCaps); |
|
CHECK_VASTATUS("vaQueryVideoProcFilterCaps for deblocking"); |
|
// create parameter buffer |
|
deblock.type = VAProcFilterDeblocking; |
|
deblock.value = deblockCaps.range.min_value + DENOISE_DEBLOCK_STRENGTH * deblockCaps.range.step; |
|
vaStatus = vaCreateBuffer(mVADisplay, mVAContext, |
|
VAProcFilterParameterBufferType, sizeof(deblock), 1, |
|
&deblock, &deblockId); |
|
CHECK_VASTATUS("vaCreateBuffer for deblocking"); |
|
mFilterBuffers[mNumFilterBuffers] = deblockId; |
|
mNumFilterBuffers++; |
|
} |
|
break; |
|
case VAProcFilterNoiseReduction: |
|
if((mFilters & FilterNoiseReduction) != 0) { |
|
// check filter caps |
|
numCaps = 1; |
|
vaStatus = vaQueryVideoProcFilterCaps(mVADisplay, mVAContext, |
|
VAProcFilterNoiseReduction, |
|
&denoiseCaps, |
|
&numCaps); |
|
CHECK_VASTATUS("vaQueryVideoProcFilterCaps for denoising"); |
|
// create parameter buffer |
|
denoise.type = VAProcFilterNoiseReduction; |
|
#ifdef TARGET_VPP_USE_GEN |
|
char propValueString[PROPERTY_VALUE_MAX]; |
|
|
|
// placeholder for vpg driver: can't support denoise factor auto adjust, so leave config to user. |
|
property_get("vpp.filter.denoise.factor", propValueString, "64.0"); |
|
denoise.value = atof(propValueString); |
|
denoise.value = (denoise.value < 0.0f) ? 0.0f : denoise.value; |
|
denoise.value = (denoise.value > 64.0f) ? 64.0f : denoise.value; |
|
#else |
|
denoise.value = denoiseCaps.range.min_value + DENOISE_DEBLOCK_STRENGTH * denoiseCaps.range.step; |
|
#endif |
|
vaStatus = vaCreateBuffer(mVADisplay, mVAContext, |
|
VAProcFilterParameterBufferType, sizeof(denoise), 1, |
|
&denoise, &denoiseId); |
|
CHECK_VASTATUS("vaCreateBuffer for denoising"); |
|
mFilterBuffers[mNumFilterBuffers] = denoiseId; |
|
mNumFilterBuffers++; |
|
} |
|
break; |
|
case VAProcFilterDeinterlacing: |
|
if ((mFilters & FilterDeinterlacing) != 0) { |
|
numCaps = VAProcDeinterlacingCount; |
|
vaStatus = vaQueryVideoProcFilterCaps(mVADisplay, mVAContext, |
|
VAProcFilterDeinterlacing, |
|
&deinterlacingCaps[0], |
|
&numCaps); |
|
CHECK_VASTATUS("vaQueryVideoProcFilterCaps for deinterlacing"); |
|
for (uint32_t i = 0; i < numCaps; i++) |
|
{ |
|
VAProcFilterCapDeinterlacing * const cap = &deinterlacingCaps[i]; |
|
if (cap->type != VAProcDeinterlacingBob) // desired Deinterlacing Type |
|
continue; |
|
|
|
deint.type = VAProcFilterDeinterlacing; |
|
deint.algorithm = VAProcDeinterlacingBob; |
|
vaStatus = vaCreateBuffer(mVADisplay, |
|
mVAContext, |
|
VAProcFilterParameterBufferType, |
|
sizeof(deint), 1, |
|
&deint, &deintId); |
|
CHECK_VASTATUS("vaCreateBuffer for deinterlacing"); |
|
mFilterBuffers[mNumFilterBuffers] = deintId; |
|
mNumFilterBuffers++; |
|
} |
|
} |
|
break; |
|
case VAProcFilterSharpening: |
|
if((mFilters & FilterSharpening) != 0) { |
|
// check filter caps |
|
numCaps = 1; |
|
vaStatus = vaQueryVideoProcFilterCaps(mVADisplay, mVAContext, |
|
VAProcFilterSharpening, |
|
&sharpenCaps, |
|
&numCaps); |
|
CHECK_VASTATUS("vaQueryVideoProcFilterCaps for sharpening"); |
|
// create parameter buffer |
|
sharpen.type = VAProcFilterSharpening; |
|
#ifdef TARGET_VPP_USE_GEN |
|
char propValueString[PROPERTY_VALUE_MAX]; |
|
|
|
// placeholder for vpg driver: can't support sharpness factor auto adjust, so leave config to user. |
|
property_get("vpp.filter.sharpen.factor", propValueString, "8.0"); |
|
sharpen.value = atof(propValueString); |
|
sharpen.value = (sharpen.value < 0.0f) ? 0.0f : sharpen.value; |
|
sharpen.value = (sharpen.value > 64.0f) ? 64.0f : sharpen.value; |
|
#else |
|
sharpen.value = sharpenCaps.range.default_value; |
|
#endif |
|
vaStatus = vaCreateBuffer(mVADisplay, mVAContext, |
|
VAProcFilterParameterBufferType, sizeof(sharpen), 1, |
|
&sharpen, &sharpenId); |
|
CHECK_VASTATUS("vaCreateBuffer for sharpening"); |
|
mFilterBuffers[mNumFilterBuffers] = sharpenId; |
|
mNumFilterBuffers++; |
|
} |
|
break; |
|
case VAProcFilterColorBalance: |
|
if((mFilters & FilterColorBalance) != 0) { |
|
uint32_t featureCount = 0; |
|
// check filter caps |
|
// FIXME: it's not used at all! |
|
numCaps = COLOR_NUM; |
|
vaStatus = vaQueryVideoProcFilterCaps(mVADisplay, mVAContext, |
|
VAProcFilterColorBalance, |
|
colorCaps, |
|
&numCaps); |
|
CHECK_VASTATUS("vaQueryVideoProcFilterCaps for color balance"); |
|
// create parameter buffer |
|
for (uint32_t i = 0; i < numCaps; i++) { |
|
if (colorCaps[i].type == VAProcColorBalanceAutoSaturation) { |
|
color[i].type = VAProcFilterColorBalance; |
|
color[i].attrib = VAProcColorBalanceAutoSaturation; |
|
color[i].value = colorCaps[i].range.min_value + COLOR_STRENGTH * colorCaps[i].range.step; |
|
featureCount++; |
|
} |
|
else if (colorCaps[i].type == VAProcColorBalanceAutoBrightness) { |
|
color[i].type = VAProcFilterColorBalance; |
|
color[i].attrib = VAProcColorBalanceAutoBrightness; |
|
color[i].value = colorCaps[i].range.min_value + COLOR_STRENGTH * colorCaps[i].range.step; |
|
featureCount++; |
|
} |
|
} |
|
#ifdef TARGET_VPP_USE_GEN |
|
//TODO: VPG need to support check input value by colorCaps. |
|
enum {kHue = 0, kSaturation, kBrightness, kContrast}; |
|
char propValueString[PROPERTY_VALUE_MAX]; |
|
color[kHue].type = VAProcFilterColorBalance; |
|
color[kHue].attrib = VAProcColorBalanceHue; |
|
|
|
// placeholder for vpg driver: can't support auto color balance, so leave config to user. |
|
property_get("vpp.filter.procamp.hue", propValueString, "179.0"); |
|
color[kHue].value = atof(propValueString); |
|
color[kHue].value = (color[kHue].value < -180.0f) ? -180.0f : color[kHue].value; |
|
color[kHue].value = (color[kHue].value > 180.0f) ? 180.0f : color[kHue].value; |
|
featureCount++; |
|
|
|
color[kSaturation].type = VAProcFilterColorBalance; |
|
color[kSaturation].attrib = VAProcColorBalanceSaturation; |
|
property_get("vpp.filter.procamp.saturation", propValueString, "1.0"); |
|
color[kSaturation].value = atof(propValueString); |
|
color[kSaturation].value = (color[kSaturation].value < 0.0f) ? 0.0f : color[kSaturation].value; |
|
color[kSaturation].value = (color[kSaturation].value > 10.0f) ? 10.0f : color[kSaturation].value; |
|
featureCount++; |
|
|
|
color[kBrightness].type = VAProcFilterColorBalance; |
|
color[kBrightness].attrib = VAProcColorBalanceBrightness; |
|
property_get("vpp.filter.procamp.brightness", propValueString, "0.0"); |
|
color[kBrightness].value = atof(propValueString); |
|
color[kBrightness].value = (color[kBrightness].value < -100.0f) ? -100.0f : color[kBrightness].value; |
|
color[kBrightness].value = (color[kBrightness].value > 100.0f) ? 100.0f : color[kBrightness].value; |
|
featureCount++; |
|
|
|
color[kContrast].type = VAProcFilterColorBalance; |
|
color[kContrast].attrib = VAProcColorBalanceContrast; |
|
property_get("vpp.filter.procamp.contrast", propValueString, "1.0"); |
|
color[kContrast].value = atof(propValueString); |
|
color[kContrast].value = (color[kContrast].value < 0.0f) ? 0.0f : color[kContrast].value; |
|
color[kContrast].value = (color[kContrast].value > 10.0f) ? 10.0f : color[kContrast].value; |
|
featureCount++; |
|
#endif |
|
vaStatus = vaCreateBuffer(mVADisplay, mVAContext, |
|
VAProcFilterParameterBufferType, sizeof(*color), featureCount, |
|
color, &colorId); |
|
CHECK_VASTATUS("vaCreateBuffer for color balance"); |
|
mFilterBuffers[mNumFilterBuffers] = colorId; |
|
mNumFilterBuffers++; |
|
} |
|
break; |
|
case VAProcFilterFrameRateConversion: |
|
if((mFilters & FilterFrameRateConversion) != 0) { |
|
frc.type = VAProcFilterFrameRateConversion; |
|
frc.input_fps = mFilterParam.frameRate; |
|
switch (mFilterParam.frcRate){ |
|
case FRC_RATE_1X: |
|
frc.output_fps = frc.input_fps; |
|
break; |
|
case FRC_RATE_2X: |
|
frc.output_fps = frc.input_fps * 2; |
|
break; |
|
case FRC_RATE_2_5X: |
|
frc.output_fps = frc.input_fps * 5/2; |
|
break; |
|
case FRC_RATE_4X: |
|
frc.output_fps = frc.input_fps * 4; |
|
break; |
|
} |
|
vaStatus = vaCreateBuffer(mVADisplay, mVAContext, |
|
VAProcFilterParameterBufferType, sizeof(frc), 1, |
|
&frc, &frcId); |
|
CHECK_VASTATUS("vaCreateBuffer for frc"); |
|
mFilterBuffers[mNumFilterBuffers] = frcId; |
|
mNumFilterBuffers++; |
|
mFilterFrc = frcId; |
|
} |
|
break; |
|
case VAProcFilterSkinToneEnhancement: |
|
if((mFilters & FilterSkinToneEnhancement) != 0) { |
|
// check filter caps |
|
numCaps = 1; |
|
vaStatus = vaQueryVideoProcFilterCaps(mVADisplay, mVAContext, |
|
VAProcFilterSkinToneEnhancement, |
|
&stdeCaps, |
|
&numCaps); |
|
CHECK_VASTATUS("vaQueryVideoProcFilterCaps for skintone"); |
|
// create parameter buffer |
|
stde.type = VAProcFilterSkinToneEnhancement; |
|
#ifdef TARGET_VPP_USE_GEN |
|
char propValueString[PROPERTY_VALUE_MAX]; |
|
|
|
// placeholder for vpg driver: can't support skintone factor auto adjust, so leave config to user. |
|
property_get("vpp.filter.skintone.factor", propValueString, "8.0"); |
|
stde.value = atof(propValueString); |
|
stde.value = (stde.value < 0.0f) ? 0.0f : stde.value; |
|
stde.value = (stde.value > 8.0f) ? 8.0f : stde.value; |
|
#else |
|
stde.value = stdeCaps.range.default_value; |
|
#endif |
|
vaStatus = vaCreateBuffer(mVADisplay, mVAContext, |
|
VAProcFilterParameterBufferType, sizeof(stde), 1, |
|
&stde, &stdeId); |
|
CHECK_VASTATUS("vaCreateBuffer for skintone"); |
|
mFilterBuffers[mNumFilterBuffers] = stdeId; |
|
mNumFilterBuffers++; |
|
} |
|
break; |
|
default: |
|
ALOGW("%s: Not supported filter 0x%08x", __func__, supportedFilters[i]); |
|
break; |
|
} |
|
} |
|
|
|
return setupPipelineCaps(); |
|
} |
|
|
|
status_t ISVWorker::setupPipelineCaps() { |
|
ALOGV("setupPipelineCaps"); |
|
//TODO color standards |
|
VAProcPipelineCaps pipelineCaps; |
|
VAStatus vaStatus; |
|
pipelineCaps.input_color_standards = in_color_standards; |
|
pipelineCaps.num_input_color_standards = VAProcColorStandardCount; |
|
pipelineCaps.output_color_standards = out_color_standards; |
|
pipelineCaps.num_output_color_standards = VAProcColorStandardCount; |
|
|
|
vaStatus = vaQueryVideoProcPipelineCaps(mVADisplay, mVAContext, |
|
mFilterBuffers, mNumFilterBuffers, |
|
&pipelineCaps); |
|
CHECK_VASTATUS("vaQueryVideoProcPipelineCaps"); |
|
|
|
if (mForwardReferences != NULL) { |
|
free(mForwardReferences); |
|
mForwardReferences = NULL; |
|
mNumForwardReferences = 0; |
|
} |
|
|
|
mNumForwardReferences = pipelineCaps.num_forward_references; |
|
if (mNumForwardReferences > 0) { |
|
mForwardReferences = (VASurfaceID*)malloc(mNumForwardReferences * sizeof(VASurfaceID)); |
|
if (mForwardReferences == NULL) |
|
return STATUS_ALLOCATION_ERROR; |
|
memset(mForwardReferences, 0, mNumForwardReferences * sizeof(VASurfaceID)); |
|
} |
|
return STATUS_OK; |
|
} |
|
|
|
status_t ISVWorker::process(ISVBuffer* inputBuffer, Vector<ISVBuffer*> outputBuffer, |
|
uint32_t outputCount, bool isEOS, uint32_t flags) { |
|
ALOGV("process: outputCount=%d, mInputIndex=%d", outputCount, mInputIndex); |
|
VASurfaceID input; |
|
VASurfaceID output[MAX_FRC_OUTPUT]; |
|
VABufferID pipelineId; |
|
VAProcPipelineParameterBuffer *pipeline; |
|
VAProcFilterParameterBufferFrameRateConversion *frc; |
|
VAStatus vaStatus = STATUS_OK; |
|
uint32_t i = 0; |
|
|
|
if (isEOS) { |
|
if (mInputIndex == 0) { |
|
ALOGV("%s: don't need to flush VSP", __func__); |
|
return STATUS_OK; |
|
} |
|
input = VA_INVALID_SURFACE; |
|
outputCount = 1; |
|
output[0] = mPrevOutput; |
|
} else { |
|
if (!inputBuffer || outputBuffer.size() != outputCount) { |
|
ALOGE("%s: invalid input/output buffer", __func__); |
|
return STATUS_ERROR; |
|
} |
|
|
|
if (outputCount < 1 || outputCount > 4) { |
|
ALOGE("%s: invalid outputCount", __func__); |
|
return STATUS_ERROR; |
|
} |
|
|
|
input = inputBuffer->getSurface(); |
|
for (i = 0; i < outputCount; i++) { |
|
output[i] = outputBuffer[i]->getSurface(); |
|
if (output[i] == VA_INVALID_SURFACE) { |
|
ALOGE("invalid output buffer"); |
|
return STATUS_ERROR; |
|
} |
|
} |
|
} |
|
|
|
// reference frames setting |
|
if (mNumForwardReferences > 0) { |
|
/* add previous frame into reference array*/ |
|
for (i = 1; i < mNumForwardReferences; i++) { |
|
mForwardReferences[i - 1] = mForwardReferences[i]; |
|
} |
|
|
|
//make last reference to input |
|
mForwardReferences[mNumForwardReferences - 1] = mPrevInput; |
|
} |
|
|
|
mPrevInput = input; |
|
|
|
// create pipeline parameter buffer |
|
vaStatus = vaCreateBuffer(mVADisplay, |
|
mVAContext, |
|
VAProcPipelineParameterBufferType, |
|
sizeof(VAProcPipelineParameterBuffer), |
|
1, |
|
NULL, |
|
&pipelineId); |
|
CHECK_VASTATUS("vaCreateBuffer for VAProcPipelineParameterBufferType"); |
|
|
|
ALOGV("before vaBeginPicture"); |
|
vaStatus = vaBeginPicture(mVADisplay, mVAContext, output[0]); |
|
CHECK_VASTATUS("vaBeginPicture"); |
|
|
|
// map pipeline paramter buffer |
|
vaStatus = vaMapBuffer(mVADisplay, pipelineId, (void**)&pipeline); |
|
CHECK_VASTATUS("vaMapBuffer for pipeline parameter buffer"); |
|
|
|
// frc pamameter setting |
|
if ((mFilters & FilterFrameRateConversion) != 0) { |
|
vaStatus = vaMapBuffer(mVADisplay, mFilterFrc, (void **)&frc); |
|
CHECK_VASTATUS("vaMapBuffer for frc parameter buffer"); |
|
if (isEOS) |
|
frc->num_output_frames = 0; |
|
else |
|
frc->num_output_frames = outputCount - 1; |
|
frc->output_frames = output + 1; |
|
} |
|
|
|
// pipeline parameter setting |
|
VARectangle dst_region; |
|
dst_region.x = 0; |
|
dst_region.y = 0; |
|
dst_region.width = mFilterParam.dstWidth; |
|
dst_region.height = mFilterParam.dstHeight; |
|
|
|
VARectangle src_region; |
|
src_region.x = 0; |
|
src_region.y = 0; |
|
src_region.width = mFilterParam.srcWidth; |
|
src_region.height = mFilterParam.srcHeight; |
|
|
|
if (isEOS) { |
|
pipeline->surface = 0; |
|
pipeline->pipeline_flags = VA_PIPELINE_FLAG_END; |
|
} |
|
else { |
|
pipeline->surface = input; |
|
pipeline->pipeline_flags = 0; |
|
} |
|
#ifdef TARGET_VPP_USE_GEN |
|
pipeline->surface_region = &src_region; |
|
pipeline->output_region = &dst_region; |
|
pipeline->surface_color_standard = VAProcColorStandardBT601; |
|
pipeline->output_color_standard = VAProcColorStandardBT601; |
|
#else |
|
pipeline->surface_region = NULL; |
|
pipeline->output_region = NULL;//&output_region; |
|
pipeline->surface_color_standard = VAProcColorStandardNone; |
|
pipeline->output_color_standard = VAProcColorStandardNone; |
|
/* real rotate state will be decided in psb video */ |
|
pipeline->rotation_state = 0; |
|
#endif |
|
/* FIXME: set more meaningful background color */ |
|
pipeline->output_background_color = 0; |
|
pipeline->filters = mFilterBuffers; |
|
pipeline->num_filters = mNumFilterBuffers; |
|
pipeline->forward_references = mForwardReferences; |
|
pipeline->num_forward_references = mNumForwardReferences; |
|
pipeline->backward_references = NULL; |
|
pipeline->num_backward_references = 0; |
|
|
|
//currently, we only transfer TOP field to frame, no frame rate change. |
|
if (flags & (OMX_BUFFERFLAG_TFF | OMX_BUFFERFLAG_BFF)) { |
|
pipeline->filter_flags = VA_TOP_FIELD; |
|
} else { |
|
pipeline->filter_flags = VA_FRAME_PICTURE; |
|
} |
|
|
|
if ((mFilters & FilterFrameRateConversion) != 0) { |
|
vaStatus = vaUnmapBuffer(mVADisplay, mFilterFrc); |
|
CHECK_VASTATUS("vaUnmapBuffer for frc parameter buffer"); |
|
} |
|
|
|
vaStatus = vaUnmapBuffer(mVADisplay, pipelineId); |
|
CHECK_VASTATUS("vaUnmapBuffer for pipeline parameter buffer"); |
|
|
|
ALOGV("before vaRenderPicture"); |
|
// Send parameter to driver |
|
vaStatus = vaRenderPicture(mVADisplay, mVAContext, &pipelineId, 1); |
|
CHECK_VASTATUS("vaRenderPicture"); |
|
|
|
ALOGV("before vaEndPicture"); |
|
vaStatus = vaEndPicture(mVADisplay, mVAContext); |
|
CHECK_VASTATUS("vaEndPicture"); |
|
|
|
if (isEOS) { |
|
vaStatus = vaSyncSurface(mVADisplay, mPrevOutput); |
|
CHECK_VASTATUS("vaSyncSurface"); |
|
if (VA_STATUS_SUCCESS != vaDestroyBuffer(mVADisplay, pipelineId)) { |
|
ALOGE("%s: failed to destroy va buffer %d", __func__, pipelineId); |
|
return STATUS_ERROR; |
|
} |
|
return STATUS_OK; |
|
} |
|
|
|
mPrevOutput = output[0]; |
|
mInputIndex++; |
|
|
|
Mutex::Autolock autoLock(mPipelineBufferLock); |
|
mPipelineBuffers.push_back(pipelineId); |
|
|
|
ALOGV("process, exit"); |
|
return STATUS_OK; |
|
} |
|
|
|
status_t ISVWorker::fill(Vector<ISVBuffer*> outputBuffer, uint32_t outputCount) { |
|
ALOGV("fill, outputCount=%d, mOutputIndex=%d",outputCount, mOutputIndex); |
|
// get output surface |
|
VASurfaceID output[MAX_FRC_OUTPUT]; |
|
VAStatus vaStatus; |
|
VASurfaceStatus surStatus; |
|
|
|
if (outputCount < 1) |
|
return STATUS_ERROR; |
|
// map GraphicBuffer to VASurface |
|
for (uint32_t i = 0; i < outputCount; i++) { |
|
|
|
output[i] = outputBuffer[i]->getSurface(); |
|
if (output[i] == VA_INVALID_SURFACE) { |
|
ALOGE("invalid output buffer"); |
|
return STATUS_ERROR; |
|
} |
|
//FIXME: only enable sync mode |
|
#if 0 |
|
vaStatus = vaQuerySurfaceStatus(mVADisplay, output[i],&surStatus); |
|
CHECK_VASTATUS("vaQuerySurfaceStatus"); |
|
if (surStatus == VASurfaceRendering) { |
|
ALOGV("Rendering %d", i); |
|
/* The behavior of driver is: all output of one process task are return in one interruption. |
|
The whole outputs of one FRC task are all ready or none of them is ready. |
|
If the behavior changed, it hurts the performance. |
|
*/ |
|
if (0 != i) { |
|
ALOGW("*****Driver behavior changed. The performance is hurt."); |
|
ALOGW("Please check driver behavior: all output of one task return in one interruption."); |
|
} |
|
vaStatus = STATUS_DATA_RENDERING; |
|
break; |
|
} |
|
|
|
if ((surStatus != VASurfaceRendering) && (surStatus != VASurfaceReady)) { |
|
ALOGE("surface statu Error %d", surStatus); |
|
vaStatus = STATUS_ERROR; |
|
} |
|
#endif |
|
vaStatus = vaSyncSurface(mVADisplay, output[i]); |
|
CHECK_VASTATUS("vaSyncSurface"); |
|
vaStatus = STATUS_OK; |
|
mOutputCount++; |
|
//dumpYUVFrameData(output[i]); |
|
} |
|
|
|
{ |
|
Mutex::Autolock autoLock(mPipelineBufferLock); |
|
if (vaStatus == STATUS_OK) { |
|
VABufferID pipelineBuffer = mPipelineBuffers.itemAt(0); |
|
if (VA_STATUS_SUCCESS != vaDestroyBuffer(mVADisplay, pipelineBuffer)) { |
|
ALOGE("%s: failed to destroy va buffer %d", __func__, pipelineBuffer); |
|
return STATUS_ERROR; |
|
} |
|
mPipelineBuffers.removeAt(0); |
|
mOutputIndex++; |
|
} |
|
} |
|
|
|
ALOGV("fill, exit"); |
|
return vaStatus; |
|
} |
|
|
|
// Debug only |
|
#define FRAME_OUTPUT_FILE_NV12 "/storage/sdcard0/vpp_output.nv12" |
|
status_t ISVWorker::dumpYUVFrameData(VASurfaceID surfaceID) { |
|
status_t ret; |
|
if (surfaceID == VA_INVALID_SURFACE) |
|
return STATUS_ERROR; |
|
|
|
VAStatus vaStatus; |
|
VAImage image; |
|
unsigned char *data_ptr; |
|
|
|
vaStatus = vaDeriveImage(mVADisplay, |
|
surfaceID, |
|
&image); |
|
CHECK_VASTATUS("vaDeriveImage"); |
|
|
|
vaStatus = vaMapBuffer(mVADisplay, image.buf, (void **)&data_ptr); |
|
CHECK_VASTATUS("vaMapBuffer"); |
|
|
|
ret = writeNV12(mFilterParam.srcWidth, mFilterParam.srcHeight, data_ptr, image.pitches[0], image.pitches[1]); |
|
if (ret != STATUS_OK) { |
|
ALOGV("writeNV12 error"); |
|
return STATUS_ERROR; |
|
} |
|
|
|
vaStatus = vaUnmapBuffer(mVADisplay, image.buf); |
|
CHECK_VASTATUS("vaUnMapBuffer"); |
|
|
|
vaStatus = vaDestroyImage(mVADisplay,image.image_id); |
|
CHECK_VASTATUS("vaDestroyImage"); |
|
|
|
return STATUS_OK; |
|
} |
|
|
|
status_t ISVWorker::reset() { |
|
status_t ret; |
|
ALOGV("reset"); |
|
if (mOutputCount > 0) { |
|
ALOGI("======mVPPInputCount=%d, mVPPRenderCount=%d======", |
|
mInputIndex, mOutputCount); |
|
} |
|
mInputIndex = 0; |
|
mOutputIndex = 0; |
|
mOutputCount = 0; |
|
|
|
{ |
|
Mutex::Autolock autoLock(mPipelineBufferLock); |
|
while (!mPipelineBuffers.isEmpty()) { |
|
VABufferID pipelineBuffer = mPipelineBuffers.itemAt(0); |
|
if (VA_STATUS_SUCCESS != vaDestroyBuffer(mVADisplay, pipelineBuffer)) { |
|
ALOGE("%s: failed to destroy va buffer %d", __func__, pipelineBuffer); |
|
return STATUS_ERROR; |
|
} |
|
mPipelineBuffers.removeAt(0); |
|
} |
|
} |
|
|
|
if (mNumFilterBuffers != 0) { |
|
for (uint32_t i = 0; i < mNumFilterBuffers; i++) { |
|
if (VA_STATUS_SUCCESS != vaDestroyBuffer(mVADisplay, mFilterBuffers[i])) |
|
ALOGW("%s: failed to destroy va buffer %d", __func__, mFilterBuffers[i]); |
|
//return STATUS_ERROR; |
|
} |
|
mNumFilterBuffers = 0; |
|
memset(&mFilterBuffers, VA_INVALID_ID, VAProcFilterCount * sizeof(VABufferID)); |
|
mFilterFrc = VA_INVALID_ID; |
|
} |
|
|
|
// we need to clear the cache for reference surfaces |
|
if (mForwardReferences != NULL) { |
|
free(mForwardReferences); |
|
mForwardReferences = NULL; |
|
mNumForwardReferences = 0; |
|
} |
|
|
|
if (mVAContext != VA_INVALID_ID) { |
|
vaDestroyContext(mVADisplay, mVAContext); |
|
mVAContext = VA_INVALID_ID; |
|
} |
|
VAStatus vaStatus = vaCreateContext(mVADisplay, mVAConfig, mWidth, mHeight, 0, NULL, 0, &mVAContext); |
|
CHECK_VASTATUS("vaCreateContext"); |
|
|
|
return setupFilters(); |
|
} |
|
|
|
uint32_t ISVWorker::getVppOutputFps() { |
|
uint32_t outputFps; |
|
//mFilterParam.frcRate is 1 if FRC is disabled or input FPS is not changed by VPP. |
|
if (FRC_RATE_2_5X == mFilterParam.frcRate) { |
|
outputFps = mFilterParam.frameRate * 5 / 2; |
|
} else { |
|
outputFps = mFilterParam.frameRate * mFilterParam.frcRate; |
|
} |
|
|
|
ALOGV("vpp is on in settings %d %d %d", outputFps, mFilterParam.frameRate, mFilterParam.frcRate); |
|
return outputFps; |
|
} |
|
|
|
|
|
status_t ISVWorker::writeNV12(int width, int height, unsigned char *out_buf, int y_pitch, int uv_pitch) { |
|
size_t result; |
|
int frame_size; |
|
unsigned char *y_start, *uv_start; |
|
int h; |
|
|
|
FILE *ofile = fopen(FRAME_OUTPUT_FILE_NV12, "ab"); |
|
if(ofile == NULL) { |
|
ALOGE("Open %s failed!", FRAME_OUTPUT_FILE_NV12); |
|
return STATUS_ERROR; |
|
} |
|
|
|
if (out_buf == NULL) |
|
{ |
|
fclose(ofile); |
|
return STATUS_ERROR; |
|
} |
|
if ((width % 2) || (height % 2)) |
|
{ |
|
fclose(ofile); |
|
return STATUS_ERROR; |
|
} |
|
// Set frame size |
|
frame_size = height * width * 3/2; |
|
|
|
/* write y */ |
|
y_start = out_buf; |
|
for (h = 0; h < height; ++h) { |
|
result = fwrite(y_start, sizeof(unsigned char), width, ofile); |
|
y_start += y_pitch; |
|
} |
|
|
|
/* write uv */ |
|
uv_start = out_buf + uv_pitch * height; |
|
for (h = 0; h < height / 2; ++h) { |
|
result = fwrite(uv_start, sizeof(unsigned char), width, ofile); |
|
uv_start += uv_pitch; |
|
} |
|
// Close file |
|
fclose(ofile); |
|
return STATUS_OK; |
|
} |
|
|
|
|