/* ------------------------------------------------------------------
* Copyright (C) 2009 Android Open Source Project
*
* 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.
* -------------------------------------------------------------------
*/
//#define LOG_NDEBUG 0
#define LOG_TAG "VideoMio72xx"
#include <utils/Log.h>
#include "android_surface_output_msm72xx.h"
#include <media/PVPlayer.h>
#define PLATFORM_PRIVATE_PMEM 1
#if HAVE_ANDROID_OS
#include <linux/android_pmem.h>
#endif
using namespace android;
static const char* pmem_adsp = "/dev/pmem_adsp";
static const char* pmem = "/dev/pmem";
OSCL_EXPORT_REF AndroidSurfaceOutputMsm72xx::AndroidSurfaceOutputMsm72xx() :
AndroidSurfaceOutput()
{
mHardwareCodec = false;
}
OSCL_EXPORT_REF AndroidSurfaceOutputMsm72xx::~AndroidSurfaceOutputMsm72xx()
{
}
// create a frame buffer for software codecs
OSCL_EXPORT_REF bool AndroidSurfaceOutputMsm72xx::initCheck()
{
// initialize only when we have all the required parameters
if (((iVideoParameterFlags & VIDEO_SUBFORMAT_VALID) == 0) || !checkVideoParameterFlags())
return mInitialized;
// release resources if previously initialized
closeFrameBuf();
// reset flags in case display format changes in the middle of a stream
resetVideoParameterFlags();
// copy parameters in case we need to adjust them
int displayWidth = iVideoDisplayWidth;
int displayHeight = iVideoDisplayHeight;
int frameWidth = iVideoWidth;
int frameHeight = iVideoHeight;
int frameSize;
// MSM72xx hardware codec uses semi-planar format
if (iVideoSubFormat == PVMF_MIME_YUV420_SEMIPLANAR_YVU) {
LOGV("using hardware codec");
mHardwareCodec = true;
// Workaround for tearing from SF
// But please make sure that the number of unique output
// buffers are more than 2; otherwise, the video playback
// will freeze due to starvation.
mNumberOfFramesToHold = 2;
} else {
LOGV("using software codec");
// YUV420 frames are 1.5 bytes/pixel
frameSize = (frameWidth * frameHeight * 3) / 2;
// create frame buffer heap
sp<MemoryHeapBase> master = new MemoryHeapBase(pmem_adsp, frameSize * kBufferCount);
if (master->heapID() < 0) {
LOGE("Error creating frame buffer heap");
return false;
}
master->setDevice(pmem);
sp<MemoryHeapPmem> heap = new MemoryHeapPmem(master, 0);
heap->slap();
mBufferHeap = ISurface::BufferHeap(displayWidth, displayHeight,
frameWidth, frameHeight, HAL_PIXEL_FORMAT_YCrCb_420_SP, heap);
master.clear();
mSurface->registerBuffers(mBufferHeap);
// create frame buffers
for (int i = 0; i < kBufferCount; i++) {
mFrameBuffers[i] = i * frameSize;
}
LOGV("video = %d x %d", displayWidth, displayHeight);
LOGV("frame = %d x %d", frameWidth, frameHeight);
LOGV("frame #bytes = %d", frameSize);
// register frame buffers with SurfaceFlinger
mFrameBufferIndex = 0;
}
mInitialized = true;
LOGV("sendEvent(MEDIA_SET_VIDEO_SIZE, %d, %d)", iVideoDisplayWidth, iVideoDisplayHeight);
mPvPlayer->sendEvent(MEDIA_SET_VIDEO_SIZE, iVideoDisplayWidth, iVideoDisplayHeight);
return mInitialized;
}
PVMFStatus AndroidSurfaceOutputMsm72xx::writeFrameBuf(uint8* aData, uint32 aDataLen, const PvmiMediaXferHeader& data_header_info)
{
// OK to drop frames if no surface
if (mSurface == 0) return PVMFSuccess;
// hardware codec
if (mHardwareCodec) {
// initialize frame buffer heap
if (mBufferHeap.heap == 0) {
LOGV("initializing for hardware");
LOGV("private data pointer is 0%p\n", data_header_info.private_data_ptr);
// check for correct video format
if (iVideoSubFormat != PVMF_MIME_YUV420_SEMIPLANAR_YVU) return PVMFFailure;
uint32 fd;
if (!getPmemFd(data_header_info.private_data_ptr, &fd)) {
LOGE("Error getting pmem heap from private_data_ptr");
return PVMFFailure;
}
// ugly hack to pass an sp<MemoryHeapBase> as an int
sp<MemoryHeapBase> master = (MemoryHeapBase *) fd;
master->setDevice(pmem);
// create new reference
uint32_t heap_flags = master->getFlags() & MemoryHeapBase::NO_CACHING;
sp<MemoryHeapPmem> heap = new MemoryHeapPmem(master, heap_flags);
heap->slap();
// register frame buffers with SurfaceFlinger
mBufferHeap = ISurface::BufferHeap(iVideoDisplayWidth, iVideoDisplayHeight,
iVideoWidth, iVideoHeight, HAL_PIXEL_FORMAT_YCrCb_420_SP, heap);
master.clear();
mSurface->registerBuffers(mBufferHeap);
}
// get pmem offset and post to SurfaceFlinger
if (!getOffset(data_header_info.private_data_ptr, &mOffset)) {
LOGE("Error getting pmem offset from private_data_ptr");
return PVMFFailure;
}
mSurface->postBuffer(mOffset);
} else {
// software codec
if (++mFrameBufferIndex == kBufferCount) mFrameBufferIndex = 0;
convertFrame(aData, static_cast<uint8*>(mBufferHeap.heap->base()) + mFrameBuffers[mFrameBufferIndex], aDataLen);
// post to SurfaceFlinger
mSurface->postBuffer(mFrameBuffers[mFrameBufferIndex]);
}
return PVMFSuccess;
}
// post the last video frame to refresh screen after pause
void AndroidSurfaceOutputMsm72xx::postLastFrame()
{
// ignore if no surface or heap
if ((mSurface == NULL) || (mBufferHeap.heap == NULL)) return;
if (mHardwareCodec) {
mSurface->postBuffer(mOffset);
} else {
mSurface->postBuffer(mFrameBuffers[mFrameBufferIndex]);
}
}
bool AndroidSurfaceOutputMsm72xx::getPmemFd(OsclAny *private_data_ptr, uint32 *pmemFD)
{
PLATFORM_PRIVATE_LIST *listPtr = NULL;
PLATFORM_PRIVATE_PMEM_INFO *pmemInfoPtr = NULL;
bool returnType = false;
LOGV("in getPmemfd - privatedataptr=%p\n",private_data_ptr);
listPtr = (PLATFORM_PRIVATE_LIST*) private_data_ptr;
for (uint32 i=0;i<listPtr->nEntries;i++)
{
if(listPtr->entryList->type == PLATFORM_PRIVATE_PMEM)
{
LOGV("in getPmemfd - entry type = %d\n",listPtr->entryList->type);
pmemInfoPtr = (PLATFORM_PRIVATE_PMEM_INFO*) (listPtr->entryList->entry);
returnType = true;
if(pmemInfoPtr){
*pmemFD = pmemInfoPtr->pmem_fd;
LOGV("in getPmemfd - pmemFD = %d\n",*pmemFD);
}
break;
}
}
return returnType;
}
bool AndroidSurfaceOutputMsm72xx::getOffset(OsclAny *private_data_ptr, uint32 *offset)
{
PLATFORM_PRIVATE_LIST *listPtr = NULL;
PLATFORM_PRIVATE_PMEM_INFO *pmemInfoPtr = NULL;
bool returnType = false;
listPtr = (PLATFORM_PRIVATE_LIST*) private_data_ptr;
LOGV("in getOffset: listPtr = %p\n",listPtr);
for (uint32 i=0;i<listPtr->nEntries;i++)
{
if(listPtr->entryList->type == PLATFORM_PRIVATE_PMEM)
{
LOGV(" in getOffset: entrytype = %d\n",listPtr->entryList->type);
pmemInfoPtr = (PLATFORM_PRIVATE_PMEM_INFO*) (listPtr->entryList->entry);
returnType = true;
if(pmemInfoPtr){
*offset = pmemInfoPtr->offset;
LOGV("in getOffset: offset = %d\n",*offset);
}
break;
}
}
return returnType;
}
static inline void* byteOffset(void* p, size_t offset) { return (void*)((uint8_t*)p + offset); }
void AndroidSurfaceOutputMsm72xx::convertFrame(void* src, void* dst, size_t len)
{
// copy the Y plane
size_t y_plane_size = iVideoWidth * iVideoHeight;
//LOGV("len=%u, y_plane_size=%u", len, y_plane_size);
memcpy(dst, src, y_plane_size + iVideoWidth);
// re-arrange U's and V's
uint16_t* pu = (uint16_t*)byteOffset(src, y_plane_size);
uint16_t* pv = (uint16_t*)byteOffset(pu, y_plane_size / 4);
uint32_t* p = (uint32_t*)byteOffset(dst, y_plane_size);
int count = y_plane_size / 8;
//LOGV("u = %p, v = %p, p = %p, count = %d", pu, pv, p, count);
do {
uint32_t u = *pu++;
uint32_t v = *pv++;
*p++ = ((u & 0xff) << 8) | ((u & 0xff00) << 16) | (v & 0xff) | ((v & 0xff00) << 8);
} while (--count);
}
// factory function for playerdriver linkage
extern "C" AndroidSurfaceOutputMsm72xx* createVideoMio()
{
return new AndroidSurfaceOutputMsm72xx();
}