blob: 0e687e25b3a46d9d79aee96249a87ef6b0fa33ee [file] [log] [blame]
/* playerdriver.cpp
**
** Copyright 2007, The 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 "PlayerDriver"
#include <utils/Log.h>
#include <sys/prctl.h>
#include <sys/resource.h>
// TODO: I don't think the include below is needed.
#include <media/mediaplayer.h>
#include <media/thread_init.h>
#include <utils/threads.h>
#include <utils/List.h>
#include <utils/SortedVector.h>
#include <cutils/properties.h>
#include <binder/Parcel.h>
#include <utils/Timers.h>
#include <media/MediaPlayerInterface.h>
#include <media/Metadata.h>
#include "playerdriver.h"
#include <media/PVPlayer.h>
#include "oscl_base.h"
#include "oscl_exception.h"
#include "oscl_scheduler.h"
#include "oscl_scheduler_ao.h"
#include "oscl_exception.h"
#include "oscl_mem_basic_functions.h" // oscl_memcpy
#include "oscl_mem.h"
#include "oscl_mem_audit.h"
#include "oscl_error.h"
#include "oscl_utf8conv.h"
#include "oscl_string_utils.h"
#include "media_clock_converter.h"
#include "pv_player_factory.h"
#include "pv_player_interface.h"
#include "pv_engine_observer.h"
#include "pvmi_media_io_fileoutput.h"
#include "pv_player_datasourceurl.h"
#include "pv_player_datasinkfilename.h"
#include "pv_player_datasinkpvmfnode.h"
#include "pvmf_errorinfomessage_extension.h"
#include "pvmf_duration_infomessage.h"
#include "android_surface_output.h"
#include "android_audio_output.h"
#include "android_audio_stream.h"
#include "pv_media_output_node_factory.h"
#include "pvmf_format_type.h" // for PVMFFormatType
#include "pvmf_node_interface.h"
#include "pvmf_source_context_data.h"
#include "pvmf_download_data_source.h"
#include "OMX_Core.h"
#include "pv_omxcore.h"
// color converter
#include "cczoomrotation16.h"
//for KMJ DRM Plugin
#include "pvmf_local_data_source.h"
#include "pvmf_recognizer_registry.h"
using namespace android;
# ifndef PAGESIZE
# define PAGESIZE 4096
# endif
// library and function name to retrieve device-specific MIOs
static const char* MIO_LIBRARY_NAME = "libopencorehw.so";
static const char* VIDEO_MIO_FACTORY_NAME = "createVideoMio";
typedef AndroidSurfaceOutput* (*VideoMioFactory)();
static const nsecs_t kBufferingUpdatePeriod = seconds(10);
namespace {
// For the event's buffer format is:
// 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | buffering percent |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//
// @param buffer Pointer to the start of the buffering status data.
// @param size Of the buffer
// @param[out] percentage On return contains the amout of buffering.
// The value is in [0,100]
// @return true if a valid buffering update was found. false otherwise.
bool GetBufferingPercentage(const void *buffer,
const size_t size,
int *percentage)
{
if (buffer == NULL) {
LOGE("Invalid buffer: NULL");
return false;
}
if (sizeof(int) != size)
{
LOGE("Invalid percentage buffer size %d (expected %d)", size, sizeof(int));
return false;
}
// TODO: The PVEvent class should expose a memcopy method
// that does bound checking instead of having clients reaching
// for its internal buffer.
oscl_memcpy(percentage, buffer, sizeof(int));
// Clamp the value and complain loudly.
if (*percentage < 0 || *percentage > 100)
{
LOGE("Invalid percentage value %d", *percentage);
return false;
}
return true;
}
// Macro used in a switch statement to convert a PlayerCommand code into its
// string representation.
#ifdef CONSIDER_CODE
#error "CONSIDER_CODE already defined!!"
#endif
#define CONSIDER_CODE(val) case ::PlayerCommand::val: return #val
// Convert a command code into a string for logging purposes.
// @param code Of the command.
// @return a string representation of the command type.
const char *PlayerCommandCodeToString(PlayerCommand::Code code) {
switch (code) {
CONSIDER_CODE(PLAYER_QUIT);
CONSIDER_CODE(PLAYER_SETUP);
CONSIDER_CODE(PLAYER_SET_DATA_SOURCE);
CONSIDER_CODE(PLAYER_SET_VIDEO_SURFACE);
CONSIDER_CODE(PLAYER_SET_AUDIO_SINK);
CONSIDER_CODE(PLAYER_INIT);
CONSIDER_CODE(PLAYER_PREPARE);
CONSIDER_CODE(PLAYER_START);
CONSIDER_CODE(PLAYER_STOP);
CONSIDER_CODE(PLAYER_PAUSE);
CONSIDER_CODE(PLAYER_RESET);
CONSIDER_CODE(PLAYER_SET_LOOP);
CONSIDER_CODE(PLAYER_SEEK);
CONSIDER_CODE(PLAYER_GET_POSITION);
CONSIDER_CODE(PLAYER_GET_DURATION);
CONSIDER_CODE(PLAYER_GET_STATUS);
CONSIDER_CODE(PLAYER_REMOVE_DATA_SOURCE);
CONSIDER_CODE(PLAYER_CANCEL_ALL_COMMANDS);
default: return "UNKNOWN PlayerCommand code";
}
}
#undef CONSIDER_CODE
// Map a PV status code to a message type (error/info/nop)
// @param status PacketVideo status code as defined in pvmf_return_codes.h
// @return the corresponding android message type. MEDIA_NOP is used as a default.
::android::media_event_type MapStatusToEventType(const PVMFStatus status) {
if (status <= PVMFErrFirst) {
return ::android::MEDIA_ERROR;
} else if (status >= PVMFInfoFirst) {
return ::android::MEDIA_INFO;
} else {
return ::android::MEDIA_NOP;
}
}
// Map a PV status to an error/info code.
// @param status PacketVideo status code as defined in pvmf_return_codes.h
// @return the corresponding android error/info code.
int MapStatusToEventCode(const PVMFStatus status) {
switch (status) {
case PVMFErrContentInvalidForProgressivePlayback:
return ::android::MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK;
default:
// Takes advantage that both error and info codes are mapped to the
// same value.
assert(::android::MEDIA_ERROR_UNKNOWN == ::android::MEDIA_INFO_UNKNOWN);
return ::android::MEDIA_ERROR_UNKNOWN;
}
}
} // anonymous namespace
class PlayerDriver :
public OsclActiveObject,
public PVCommandStatusObserver,
public PVInformationalEventObserver,
public PVErrorEventObserver
{
public:
PlayerDriver(PVPlayer* pvPlayer);
~PlayerDriver();
PlayerCommand* dequeueCommand();
status_t enqueueCommand(PlayerCommand* code);
// Dequeues a code from MediaPlayer and gives it to PVPlayer.
void Run();
// Handlers for the various commands we can accept.
void commandFailed(PlayerCommand* command);
void handleSetup(PlayerSetup* command);
void handleSetDataSource(PlayerSetDataSource* command);
void handleSetVideoSurface(PlayerSetVideoSurface* command);
void handleSetAudioSink(PlayerSetAudioSink* command);
void handleInit(PlayerInit* command);
void handlePrepare(PlayerPrepare* command);
void handleStart(PlayerStart* command);
void handleStop(PlayerStop* command);
void handlePause(PlayerPause* command);
void handleSeek(PlayerSeek* command);
void handleCancelAllCommands(PlayerCancelAllCommands* command);
void handleRemoveDataSource(PlayerRemoveDataSource* command);
void handleReset(PlayerReset* command);
void handleQuit(PlayerQuit* command);
void handleGetPosition(PlayerGetPosition* command);
void handleGetDuration(PlayerGetDuration* command);
void handleGetStatus(PlayerGetStatus* command);
void handleCheckLiveStreaming(PlayerCheckLiveStreaming* cmd);
//void endOfData();
PVMFFormatType getFormatType();
void CommandCompleted(const PVCmdResponse& aResponse);
void HandleErrorEvent(const PVAsyncErrorEvent& aEvent);
void HandleInformationalEvent(const PVAsyncInformationalEvent& aEvent);
bool prepareDone() const { return mPrepareDone; }
bool isLiveStreaming() const { return mIsLiveStreaming; }
private:
// Finish up a non-async code in such a way that
// the event loop will keep running.
void FinishSyncCommand(PlayerCommand* command);
void handleGetDurationComplete(PlayerGetDuration* cmd);
void handleCheckLiveStreamingComplete(PlayerCheckLiveStreaming* cmd);
int setupHttpStreamPre();
int setupHttpStreamPost();
// Starts the PV scheduler thread.
static int startPlayerThread(void *cookie);
int playerThread();
// Callback for synchronous commands.
static void syncCompletion(status_t s, void *cookie, bool cancelled);
PVPlayer *mPvPlayer;
PVPlayerInterface *mPlayer;
PVPlayerDataSourceURL *mDataSource;
PVPlayerDataSink *mAudioSink;
PVMFNodeInterface *mAudioNode;
AndroidAudioMIO *mAudioOutputMIO;
PVPlayerDataSink *mVideoSink;
PVMFNodeInterface *mVideoNode;
AndroidSurfaceOutput *mVideoOutputMIO;
PvmiCapabilityAndConfig *mPlayerCapConfig;
OSCL_wHeapString<OsclMemAllocator> mDownloadFilename;
OSCL_HeapString<OsclMemAllocator> mDownloadProxy;
OSCL_wHeapString<OsclMemAllocator> mDownloadConfigFilename;
PVMFSourceContextData *mDownloadContextData;
PVPMetadataList mMetaKeyList;
Oscl_Vector<PvmiKvp,OsclMemAllocator> mMetaValueList;
int mNumMetaValues;
PVPMetadataList mCheckLiveKey;
Oscl_Vector<PvmiKvp,OsclMemAllocator> mCheckLiveValue;
int mCheckLiveMetaValues;
// Semaphore used for synchronous commands.
OsclSemaphore *mSyncSem;
// Status cached by syncCompletion for synchronous commands.
status_t mSyncStatus;
// Command queue and its lock.
List<PlayerCommand*> mCommandQueue;
Mutex mQueueLock;
bool mIsLooping;
bool mDoLoop;
bool mDataReadyReceived;
bool mPrepareDone;
bool mEndOfData;
int mRecentSeek;
bool mSeekComp;
bool mSeekPending;
bool mIsLiveStreaming;
bool mEmulation;
bool mContentLengthKnown;
void* mLibHandle;
nsecs_t mLastBufferingLog;
// video display surface
android::sp<android::ISurface> mSurface;
};
PlayerDriver::PlayerDriver(PVPlayer* pvPlayer) :
OsclActiveObject(OsclActiveObject::EPriorityNominal, "PVPlayerPlayer"),
mPvPlayer(pvPlayer),
mIsLooping(false),
mDoLoop(false),
mDataReadyReceived(false),
mPrepareDone(false),
mEndOfData(false),
mRecentSeek(0),
mSeekComp(true),
mSeekPending(false),
mIsLiveStreaming(false),
mEmulation(false),
mContentLengthKnown(false),
mLastBufferingLog(0)
{
LOGV("constructor");
mSyncSem = new OsclSemaphore();
mSyncSem->Create();
mDataSource = NULL;
mAudioSink = NULL;
mAudioNode = NULL;
mAudioOutputMIO = NULL;
mVideoSink = NULL;
mVideoNode = NULL;
mVideoOutputMIO = NULL;
mSurface = NULL;
mPlayerCapConfig = NULL;
mDownloadContextData = NULL;
// running in emulation?
mLibHandle = NULL;
char value[PROPERTY_VALUE_MAX];
if (property_get("ro.kernel.qemu", value, 0)) {
mEmulation = true;
LOGV("Emulation mode - using software codecs");
} else {
// attempt to open h/w specific library
mLibHandle = ::dlopen(MIO_LIBRARY_NAME, RTLD_NOW);
if (mLibHandle != NULL) {
LOGV("OpenCore hardware module loaded");
} else {
LOGV("OpenCore hardware module not found");
}
}
// start player thread
LOGV("start player thread");
createThreadEtc(PlayerDriver::startPlayerThread, this, "PV player");
// mSyncSem will be signaled when the scheduler has started
mSyncSem->Wait();
}
PlayerDriver::~PlayerDriver()
{
LOGV("destructor");
if (mLibHandle != NULL) {
::dlclose(mLibHandle);
}
}
PlayerCommand* PlayerDriver::dequeueCommand()
{
PlayerCommand* command;
mQueueLock.lock();
// XXX should we assert here?
if (mCommandQueue.empty()) {
PendForExec();
mQueueLock.unlock();
return NULL;
}
command = *(--mCommandQueue.end());
mCommandQueue.erase(--mCommandQueue.end());
if (mCommandQueue.size() > 0 )
{
RunIfNotReady();
}
else
{
PendForExec();
}
mQueueLock.unlock();
return command;
}
status_t PlayerDriver::enqueueCommand(PlayerCommand* command)
{
if (mPlayer == NULL) {
// Only commands which can come in this use-case is PLAYER_SETUP and PLAYER_QUIT
// The calling function should take responsibility to delete the command and cleanup
return NO_INIT;
}
OsclSemaphore *syncsemcopy = NULL;
// If the user didn't specify a completion callback, we
// are running in synchronous mode.
if (!command->hasCallback()) {
command->set(PlayerDriver::syncCompletion, this);
// make a copy of this semaphore for special handling of the PLAYER_QUIT code
syncsemcopy = mSyncSem;
}
// Add the code to the queue.
mQueueLock.lock();
mCommandQueue.push_front(command);
// save code, since command will be deleted by the standard completion function
int code = command->code();
// AO needs to be scheduled only if this is the first cmd after queue was empty
if (mCommandQueue.size() == 1)
{
PendComplete(OSCL_REQUEST_ERR_NONE);
}
mQueueLock.unlock();
// If we are in synchronous mode, wait for completion.
if (syncsemcopy) {
syncsemcopy->Wait();
if (code == PlayerCommand::PLAYER_QUIT) {
syncsemcopy->Close();
delete syncsemcopy;
return 0;
}
return mSyncStatus;
}
return OK;
}
void PlayerDriver::FinishSyncCommand(PlayerCommand* command)
{
command->complete(NO_ERROR, false);
delete command;
}
// The OSCL scheduler calls this when we get to run (this should happen only
// when a code has been enqueued for us).
void PlayerDriver::Run()
{
if (mDoLoop) {
mEndOfData = false;
mContentLengthKnown = false;
PVPPlaybackPosition begin, end;
begin.iIndeterminate = false;
begin.iPosUnit = PVPPBPOSUNIT_SEC;
begin.iPosValue.sec_value = 0;
begin.iMode = PVPPBPOS_MODE_NOW;
end.iIndeterminate = true;
mPlayer->SetPlaybackRange(begin, end, false, NULL);
mPlayer->Resume();
return;
}
PVPlayerState state = PVP_STATE_ERROR;
if ((mPlayer->GetPVPlayerStateSync(state) == PVMFSuccess))
{
if (state == PVP_STATE_ERROR)
{
return;
}
}
PlayerCommand* command;
command = dequeueCommand();
if (command) {
LOGV("Send player code: %d", command->code());
switch (command->code()) {
case PlayerCommand::PLAYER_SETUP:
handleSetup(static_cast<PlayerSetup*>(command));
break;
case PlayerCommand::PLAYER_SET_DATA_SOURCE:
handleSetDataSource(static_cast<PlayerSetDataSource*>(command));
break;
case PlayerCommand::PLAYER_SET_VIDEO_SURFACE:
handleSetVideoSurface(static_cast<PlayerSetVideoSurface*>(command));
break;
case PlayerCommand::PLAYER_SET_AUDIO_SINK:
handleSetAudioSink(static_cast<PlayerSetAudioSink*>(command));
break;
case PlayerCommand::PLAYER_INIT:
handleInit(static_cast<PlayerInit*>(command));
break;
case PlayerCommand::PLAYER_PREPARE:
handlePrepare(static_cast<PlayerPrepare*>(command));
break;
case PlayerCommand::PLAYER_START:
handleStart(static_cast<PlayerStart*>(command));
break;
case PlayerCommand::PLAYER_STOP:
handleStop(static_cast<PlayerStop*>(command));
break;
case PlayerCommand::PLAYER_PAUSE:
{
if(mIsLiveStreaming) {
LOGW("Pause denied");
FinishSyncCommand(command);
return;
}
handlePause(static_cast<PlayerPause*>(command));
}
break;
case PlayerCommand::PLAYER_SEEK:
{
if(mIsLiveStreaming) {
LOGW("Seek denied");
mPvPlayer->sendEvent(MEDIA_SEEK_COMPLETE);
FinishSyncCommand(command);
return;
}
handleSeek(static_cast<PlayerSeek*>(command));
}
break;
case PlayerCommand::PLAYER_GET_POSITION:
handleGetPosition(static_cast<PlayerGetPosition*>(command));
FinishSyncCommand(command);
return;
case PlayerCommand::PLAYER_GET_STATUS:
handleGetStatus(static_cast<PlayerGetStatus*>(command));
FinishSyncCommand(command);
return;
case PlayerCommand::PLAYER_CHECK_LIVE_STREAMING:
handleCheckLiveStreaming(static_cast<PlayerCheckLiveStreaming*>(command));
break;
case PlayerCommand::PLAYER_GET_DURATION:
handleGetDuration(static_cast<PlayerGetDuration*>(command));
break;
case PlayerCommand::PLAYER_REMOVE_DATA_SOURCE:
handleRemoveDataSource(static_cast<PlayerRemoveDataSource*>(command));
break;
case PlayerCommand::PLAYER_CANCEL_ALL_COMMANDS:
handleCancelAllCommands(static_cast<PlayerCancelAllCommands*>(command));
break;
case PlayerCommand::PLAYER_RESET:
handleReset(static_cast<PlayerReset*>(command));
break;
case PlayerCommand::PLAYER_QUIT:
handleQuit(static_cast<PlayerQuit*>(command));
return;
case PlayerCommand::PLAYER_SET_LOOP:
mIsLooping = static_cast<PlayerSetLoop*>(command)->loop();
FinishSyncCommand(command);
return;
default:
LOGE("Unexpected code %d", command->code());
break;
}
}
}
void PlayerDriver::commandFailed(PlayerCommand* command)
{
if (command == NULL) {
LOGV("async code failed");
return;
}
LOGV("Command failed: %d", command->code());
command->complete(UNKNOWN_ERROR, false);
delete command;
}
void PlayerDriver::handleSetup(PlayerSetup* command)
{
int error = 0;
// Make sure we have the capabilities and config interface first.
OSCL_TRY(error, mPlayer->QueryInterface(PVMI_CAPABILITY_AND_CONFIG_PVUUID,
(PVInterface *&)mPlayerCapConfig, command));
OSCL_FIRST_CATCH_ANY(error, commandFailed(command));
}
int PlayerDriver::setupHttpStreamPre()
{
mDataSource->SetDataSourceFormatType((char*)PVMF_MIME_DATA_SOURCE_HTTP_URL);
delete mDownloadContextData;
mDownloadContextData = NULL;
mDownloadContextData = new PVMFSourceContextData();
mDownloadContextData->EnableCommonSourceContext();
mDownloadContextData->EnableDownloadHTTPSourceContext();
// FIXME:
// This mDownloadConfigFilename at /tmp/http-stream-cfg
// should not exist. We need to clean it up later.
mDownloadConfigFilename = _STRLIT_WCHAR("/tmp/http-stream-cfg");
mDownloadFilename = NULL;
mDownloadProxy = _STRLIT_CHAR("");
mDownloadContextData->DownloadHTTPData()->iMaxFileSize = 0xFFFFFFFF;
mDownloadContextData->DownloadHTTPData()->iPlaybackControl = PVMFSourceContextDataDownloadHTTP::ENoSaveToFile;
mDownloadContextData->DownloadHTTPData()->iConfigFileName = mDownloadConfigFilename;
mDownloadContextData->DownloadHTTPData()->iDownloadFileName = mDownloadFilename;
mDownloadContextData->DownloadHTTPData()->iProxyName = mDownloadProxy;
mDownloadContextData->DownloadHTTPData()->iProxyPort = 0;
mDownloadContextData->DownloadHTTPData()->bIsNewSession = true;
mDataSource->SetDataSourceContextData(mDownloadContextData);
return 0;
}
int PlayerDriver::setupHttpStreamPost()
{
PvmiKvp iKVPSetAsync;
OSCL_StackString<64> iKeyStringSetAsync;
PvmiKvp *iErrorKVP = NULL;
int error = 0;
iKeyStringSetAsync=_STRLIT_CHAR("x-pvmf/net/http-timeout;valtype=uint32");
iKVPSetAsync.key=iKeyStringSetAsync.get_str();
iKVPSetAsync.value.uint32_value=20;
iErrorKVP=NULL;
OSCL_TRY(error, mPlayerCapConfig->setParametersSync(NULL, &iKVPSetAsync, 1, iErrorKVP));
OSCL_FIRST_CATCH_ANY(error, return -1);
iKeyStringSetAsync=_STRLIT_CHAR("x-pvmf/net/num-redirect-attempts;valtype=uint32");
iKVPSetAsync.key=iKeyStringSetAsync.get_str();
iKVPSetAsync.value.uint32_value=4;
iErrorKVP=NULL;
OSCL_TRY(error, mPlayerCapConfig->setParametersSync(NULL, &iKVPSetAsync, 1, iErrorKVP));
OSCL_FIRST_CATCH_ANY(error, return -1);
iKeyStringSetAsync=_STRLIT_CHAR("x-pvmf/net/max-tcp-recv-buffer-size-download;valtype=uint32");
iKVPSetAsync.key=iKeyStringSetAsync.get_str();
iKVPSetAsync.value.uint32_value=64000;
iErrorKVP=NULL;
OSCL_TRY(error, mPlayerCapConfig->setParametersSync(NULL, &iKVPSetAsync, 1, iErrorKVP));
OSCL_FIRST_CATCH_ANY(error, return -1);
return 0;
}
void PlayerDriver::handleSetDataSource(PlayerSetDataSource* command)
{
LOGV("handleSetDataSource");
int error = 0;
const char* url = command->url();
int lengthofurl = strlen(url);
oscl_wchar output[lengthofurl + 1];
OSCL_wHeapString<OsclMemAllocator> wFileName;
if (mDataSource) {
delete mDataSource;
mDataSource = NULL;
}
// Create a URL datasource to feed PVPlayer
mDataSource = new PVPlayerDataSourceURL();
oscl_UTF8ToUnicode(url, strlen(url), output, lengthofurl+1);
wFileName.set(output, oscl_strlen(output));
mDataSource->SetDataSourceURL(wFileName);
LOGV("handleSetDataSource- scanning for extension");
if (strncmp(url, "rtsp:", strlen("rtsp:")) == 0) {
mDataSource->SetDataSourceFormatType((const char*)PVMF_MIME_DATA_SOURCE_RTSP_URL);
} else if (strncmp(url, "http:", strlen("http:")) == 0) {
if (0!=setupHttpStreamPre())
{
commandFailed(command);
return;
}
} else {
const char* ext = strrchr(url, '.');
if (ext && ( strcasecmp(ext, ".sdp") == 0) ) {
// For SDP files, currently there is no recognizer. So, to play from such files,
// there is a need to set the format type.
mDataSource->SetDataSourceFormatType((const char*)PVMF_MIME_DATA_SOURCE_SDP_FILE);
} else {
mDataSource->SetDataSourceFormatType((const char*)PVMF_MIME_FORMAT_UNKNOWN); // Let PV figure it out
}
}
OSCL_TRY(error, mPlayer->AddDataSource(*mDataSource, command));
OSCL_FIRST_CATCH_ANY(error, commandFailed(command));
}
void PlayerDriver::handleInit(PlayerInit* command)
{
int error = 0;
if (mDownloadContextData) {
setupHttpStreamPost();
}
{
PvmiKvp iKVPSetAsync;
PvmiKvp *iErrorKVP = NULL;
int error = 0;
iKVPSetAsync.key = _STRLIT_CHAR("x-pvmf/net/user-agent;valtype=wchar*");
OSCL_wHeapString<OsclMemAllocator> userAgent = _STRLIT_WCHAR("CORE/6.506.4.1 OpenCORE/2.02 (Linux;Android ");
#if (PROPERTY_VALUE_MAX < 8)
#error "PROPERTY_VALUE_MAX must be at least 8"
#endif
char value[PROPERTY_VALUE_MAX];
int len = property_get("ro.build.version.release", value, "Unknown");
if (len) {
LOGV("release string is %s len %d", value, len);
oscl_wchar output[len+ 1];
oscl_UTF8ToUnicode(value, len, output, len+1);
userAgent += output;
}
userAgent += _STRLIT_WCHAR(")");
iKVPSetAsync.value.pWChar_value=userAgent.get_str();
iErrorKVP=NULL;
OSCL_TRY(error, mPlayerCapConfig->setParametersSync(NULL, &iKVPSetAsync, 1, iErrorKVP));
OSCL_FIRST_CATCH_ANY(error,
LOGE("handleInit- setParametersSync ERROR setting useragent");
);
}
OSCL_TRY(error, mPlayer->Init(command));
OSCL_FIRST_CATCH_ANY(error, commandFailed(command));
}
void PlayerDriver::handleSetVideoSurface(PlayerSetVideoSurface* command)
{
// create video MIO if needed
if (mVideoOutputMIO == NULL) {
int error = 0;
AndroidSurfaceOutput* mio = NULL;
// attempt to load device-specific video MIO
if (mLibHandle != NULL) {
VideoMioFactory f = (VideoMioFactory) ::dlsym(mLibHandle, VIDEO_MIO_FACTORY_NAME);
if (f != NULL) {
mio = f();
}
}
// if no device-specific MIO was created, use the generic one
if (mio == NULL) {
LOGW("Using generic video MIO");
mio = new AndroidSurfaceOutput();
}
// initialize the MIO parameters
status_t ret = mio->set(mPvPlayer, command->surface(), mEmulation);
if (ret != NO_ERROR) {
LOGE("Video MIO set failed");
commandFailed(command);
delete mio;
return;
}
mVideoOutputMIO = mio;
mVideoNode = PVMediaOutputNodeFactory::CreateMediaOutputNode(mVideoOutputMIO);
mVideoSink = new PVPlayerDataSinkPVMFNode;
((PVPlayerDataSinkPVMFNode *)mVideoSink)->SetDataSinkNode(mVideoNode);
((PVPlayerDataSinkPVMFNode *)mVideoSink)->SetDataSinkFormatType((char*)PVMF_MIME_YUV420);
OSCL_TRY(error, mPlayer->AddDataSink(*mVideoSink, command));
OSCL_FIRST_CATCH_ANY(error, commandFailed(command));
} else {
// change display surface
if (mVideoOutputMIO->setVideoSurface(command->surface()) == NO_ERROR) {
FinishSyncCommand(command);
} else {
LOGE("Video MIO set failed");
commandFailed(command);
}
}
}
void PlayerDriver::handleSetAudioSink(PlayerSetAudioSink* command)
{
int error = 0;
if (command->audioSink()->realtime()) {
LOGV("Create realtime output");
mAudioOutputMIO = new AndroidAudioOutput();
} else {
LOGV("Create stream output");
mAudioOutputMIO = new AndroidAudioStream();
}
mAudioOutputMIO->setAudioSink(command->audioSink());
mAudioNode = PVMediaOutputNodeFactory::CreateMediaOutputNode(mAudioOutputMIO);
mAudioSink = new PVPlayerDataSinkPVMFNode;
((PVPlayerDataSinkPVMFNode *)mAudioSink)->SetDataSinkNode(mAudioNode);
((PVPlayerDataSinkPVMFNode *)mAudioSink)->SetDataSinkFormatType((char*)PVMF_MIME_PCM16);
OSCL_TRY(error, mPlayer->AddDataSink(*mAudioSink, command));
OSCL_FIRST_CATCH_ANY(error, commandFailed(command));
}
void PlayerDriver::handlePrepare(PlayerPrepare* command)
{
//Keep alive is sent during the play to prevent the firewall from closing ports while
//streaming long clip
PvmiKvp iKVPSetAsync;
OSCL_StackString<64> iKeyStringSetAsync;
PvmiKvp *iErrorKVP = NULL;
int error=0;
iKeyStringSetAsync=_STRLIT_CHAR("x-pvmf/net/keep-alive-during-play;valtype=bool");
iKVPSetAsync.key=iKeyStringSetAsync.get_str();
iKVPSetAsync.value.bool_value=true;
iErrorKVP=NULL;
OSCL_TRY(error, mPlayerCapConfig->setParametersSync(NULL, &iKVPSetAsync, 1, iErrorKVP));
OSCL_TRY(error, mPlayer->Prepare(command));
OSCL_FIRST_CATCH_ANY(error, commandFailed(command));
char value[PROPERTY_VALUE_MAX] = {"0"};
property_get("ro.com.android.disable_rtsp_nat", value, "0");
LOGV("disable natpkt - %s",value);
if (1 == atoi(value))
{
//disable firewall packet
iKeyStringSetAsync=_STRLIT_CHAR("x-pvmf/net/disable-firewall-packets;valtype=bool");
iKVPSetAsync.key=iKeyStringSetAsync.get_str();
iKVPSetAsync.value.bool_value = 1; //1 - disable
iErrorKVP=NULL;
OSCL_TRY(error,mPlayerCapConfig->setParametersSync(NULL, &iKVPSetAsync, 1, iErrorKVP));
}
}
void PlayerDriver::handleStart(PlayerStart* command)
{
int error = 0;
// reset logging
mLastBufferingLog = 0;
// for video, set thread priority so we don't hog CPU
if (mVideoOutputMIO) {
int ret = setpriority(PRIO_PROCESS, 0, ANDROID_PRIORITY_DISPLAY);
}
// for audio, set thread priority so audio isn't choppy
else {
int ret = setpriority(PRIO_PROCESS, 0, ANDROID_PRIORITY_AUDIO);
}
// Signalling seek complete to continue obtaining the current position
// from PVPlayer Engine
mSeekComp = true;
// if we are paused, just resume
PVPlayerState state;
if (mPlayer->GetPVPlayerStateSync(state) == PVMFSuccess
&& (state == PVP_STATE_PAUSED)) {
if (mEndOfData) {
// if we are at the end, seek to the beginning first
mEndOfData = false;
PVPPlaybackPosition begin, end;
begin.iIndeterminate = false;
begin.iPosUnit = PVPPBPOSUNIT_SEC;
begin.iPosValue.sec_value = 0;
begin.iMode = PVPPBPOS_MODE_NOW;
end.iIndeterminate = true;
mPlayer->SetPlaybackRange(begin, end, false, NULL);
}
OSCL_TRY(error, mPlayer->Resume(command));
OSCL_FIRST_CATCH_ANY(error, commandFailed(command));
} else {
OSCL_TRY(error, mPlayer->Start(command));
OSCL_FIRST_CATCH_ANY(error, commandFailed(command));
}
}
void PlayerDriver::handleSeek(PlayerSeek* command)
{
int error = 0;
LOGV("handleSeek");
// Cache the most recent seek request
mRecentSeek = command->msec();
// Seeking in the pause state
PVPlayerState state;
if (mPlayer->GetPVPlayerStateSync(state) == PVMFSuccess
&& (state == PVP_STATE_PAUSED)) {
mSeekComp = false;
}
PVPPlaybackPosition begin, end;
begin.iIndeterminate = false;
begin.iPosUnit = PVPPBPOSUNIT_MILLISEC;
begin.iPosValue.millisec_value = command->msec();
begin.iMode = PVPPBPOS_MODE_NOW;
end.iIndeterminate = true;
mSeekPending = true;
OSCL_TRY(error, mPlayer->SetPlaybackRange(begin, end, false, command));
OSCL_FIRST_CATCH_ANY(error, commandFailed(command));
mEndOfData = false;
}
void PlayerDriver::handleGetPosition(PlayerGetPosition* command)
{
PVPPlaybackPosition pos;
pos.iPosUnit = PVPPBPOSUNIT_MILLISEC;
PVPlayerState state;
// In the pause state, get the progress bar position from the recent seek value
// instead of GetCurrentPosition() from PVPlayer Engine.
if (mPlayer->GetPVPlayerStateSync(state) == PVMFSuccess
&& (state == PVP_STATE_PAUSED)
&& (mSeekComp == false)) {
command->set(mRecentSeek);
}
else {
if (mPlayer->GetCurrentPositionSync(pos) != PVMFSuccess) {
command->set(-1);
} else {
LOGV("position=%d", pos.iPosValue.millisec_value);
command->set((int)pos.iPosValue.millisec_value);
}
}
}
void PlayerDriver::handleGetStatus(PlayerGetStatus* command)
{
PVPlayerState state;
if (mPlayer->GetPVPlayerStateSync(state) != PVMFSuccess) {
command->set(0);
} else {
command->set(state);
LOGV("status=%d", state);
}
}
void PlayerDriver::handleCheckLiveStreaming(PlayerCheckLiveStreaming* command)
{
LOGV("handleCheckLiveStreaming ...");
mCheckLiveKey.clear();
mCheckLiveKey.push_back(OSCL_HeapString<OsclMemAllocator>("pause-denied"));
mCheckLiveValue.clear();
int error = 0;
OSCL_TRY(error, mPlayer->GetMetadataValues(mCheckLiveKey, 0, 1, mCheckLiveMetaValues, mCheckLiveValue, command));
OSCL_FIRST_CATCH_ANY(error, commandFailed(command));
}
void PlayerDriver::handleGetDuration(PlayerGetDuration* command)
{
command->set(-1);
mMetaKeyList.clear();
mMetaKeyList.push_back(OSCL_HeapString<OsclMemAllocator>("duration"));
mMetaValueList.clear();
mNumMetaValues=0;
int error = 0;
OSCL_TRY(error, mPlayer->GetMetadataValues(mMetaKeyList,0,-1,mNumMetaValues,mMetaValueList, command));
OSCL_FIRST_CATCH_ANY(error, commandFailed(command));
}
void PlayerDriver::handleStop(PlayerStop* command)
{
int error = 0;
// setting the looping boolean to false. MediaPlayer app takes care of setting the loop again before the start.
mIsLooping = false;
mDoLoop = false;
PVPlayerState state;
if ((mPlayer->GetPVPlayerStateSync(state) == PVMFSuccess)
&& ( (state == PVP_STATE_PAUSED) ||
(state == PVP_STATE_PREPARED) ||
(state == PVP_STATE_STARTED) ))
{
OSCL_TRY(error, mPlayer->Stop(command));
OSCL_FIRST_CATCH_ANY(error, commandFailed(command));
}
else
{
LOGV("handleStop - Player State = %d - Sending Reset instead of Stop\n",state);
// TODO: Previously this called CancelAllCommands and RemoveDataSource
handleReset(new PlayerReset(command->callback(), command->cookie()));
delete command;
}
}
void PlayerDriver::handlePause(PlayerPause* command)
{
LOGV("call pause");
int error = 0;
OSCL_TRY(error, mPlayer->Pause(command));
OSCL_FIRST_CATCH_ANY(error, commandFailed(command));
}
void PlayerDriver::handleRemoveDataSource(PlayerRemoveDataSource* command)
{
LOGV("handleRemoveDataSource");
int error = 0;
OSCL_TRY(error, mPlayer->RemoveDataSource(*mDataSource, command));
OSCL_FIRST_CATCH_ANY(error, commandFailed(command));
}
void PlayerDriver::handleCancelAllCommands(PlayerCancelAllCommands* command)
{
LOGV("handleCancelAllCommands");
int error = 0;
OSCL_TRY(error, mPlayer->CancelAllCommands(command));
OSCL_FIRST_CATCH_ANY(error, commandFailed(command));
}
void PlayerDriver::handleReset(PlayerReset* command)
{
LOGV("handleReset");
int error = 0;
// setting the looping boolean to false. MediaPlayer app takes care of setting the loop again before the start.
mIsLooping = false;
mDoLoop = false;
mEndOfData = false;
mContentLengthKnown = false;
OSCL_TRY(error, mPlayer->Reset(command));
OSCL_FIRST_CATCH_ANY(error, commandFailed(command));
}
void PlayerDriver::handleQuit(PlayerQuit* command)
{
OsclExecScheduler *sched = OsclExecScheduler::Current();
sched->StopScheduler();
}
PVMFFormatType PlayerDriver::getFormatType()
{
return mDataSource->GetDataSourceFormatType();
}
/*static*/ int PlayerDriver::startPlayerThread(void *cookie)
{
LOGV("startPlayerThread");
PlayerDriver *ed = (PlayerDriver *)cookie;
return ed->playerThread();
}
int PlayerDriver::playerThread()
{
int error;
LOGV("InitializeForThread");
if (!InitializeForThread())
{
LOGV("InitializeForThread fail");
mPlayer = NULL;
mSyncSem->Signal();
return -1;
}
LOGV("OMX_MasterInit");
OMX_MasterInit();
LOGV("OsclScheduler::Init");
OsclScheduler::Init("AndroidPVWrapper");
LOGV("CreatePlayer");
OSCL_TRY(error, mPlayer = PVPlayerFactory::CreatePlayer(this, this, this));
if (error) {
// Just crash the first time someone tries to use it for now?
mPlayer = NULL;
mSyncSem->Signal();
return -1;
}
LOGV("AddToScheduler");
AddToScheduler();
LOGV("PendForExec");
PendForExec();
LOGV("OsclActiveScheduler::Current");
OsclExecScheduler *sched = OsclExecScheduler::Current();
LOGV("StartScheduler");
error = OsclErrNone;
OSCL_TRY(error, sched->StartScheduler(mSyncSem));
OSCL_FIRST_CATCH_ANY(error,
// Some AO did a leave, log it
LOGE("Player Engine AO did a leave, error=%d", error)
);
LOGV("DeletePlayer");
PVPlayerFactory::DeletePlayer(mPlayer);
delete mDownloadContextData;
mDownloadContextData = NULL;
delete mDataSource;
mDataSource = NULL;
delete mAudioSink;
PVMediaOutputNodeFactory::DeleteMediaOutputNode(mAudioNode);
delete mAudioOutputMIO;
delete mVideoSink;
if (mVideoNode) {
PVMediaOutputNodeFactory::DeleteMediaOutputNode(mVideoNode);
delete mVideoOutputMIO;
}
mSyncStatus = OK;
mSyncSem->Signal();
// note that we only signal mSyncSem. Deleting it is handled
// in enqueueCommand(). This is done because waiting for an
// already-deleted OsclSemaphore doesn't work (it blocks),
// and it's entirely possible for this thread to exit before
// enqueueCommand() gets around to waiting for the semaphore.
// do some of destructor's work here
// goodbye cruel world
delete this;
//Moved after the delete this, as Oscl cleanup should be done in the end.
//delete this was cleaning up OsclSemaphore objects, eventually causing a crash
OsclScheduler::Cleanup();
LOGV("OsclScheduler::Cleanup");
OMX_MasterDeinit();
UninitializeForThread();
return 0;
}
/*static*/ void PlayerDriver::syncCompletion(status_t s, void *cookie, bool cancelled)
{
PlayerDriver *ed = static_cast<PlayerDriver*>(cookie);
ed->mSyncStatus = s;
ed->mSyncSem->Signal();
}
void PlayerDriver::handleCheckLiveStreamingComplete(PlayerCheckLiveStreaming* cmd)
{
if (mCheckLiveValue.empty())
return;
const char* substr = oscl_strstr((char*)(mCheckLiveValue[0].key), _STRLIT_CHAR("pause-denied;valtype=bool"));
if (substr!=NULL) {
if ( mCheckLiveValue[0].value.bool_value == true ) {
LOGI("Live Streaming ... \n");
mIsLiveStreaming = true;
}
}
}
void PlayerDriver::handleGetDurationComplete(PlayerGetDuration* cmd)
{
cmd->set(-1);
if (mMetaValueList.empty())
return;
MediaClockConverter mcc;
for (uint32 i = 0; i < mMetaValueList.size(); ++i) {
// Search for the duration
const char* substr=oscl_strstr(mMetaValueList[i].key, _STRLIT_CHAR("duration;valtype=uint32;timescale="));
if (substr!=NULL) {
uint32 timescale=1000;
if (PV_atoi((substr+34), 'd', timescale) == false) {
// Retrieving timescale failed so default to 1000
timescale=1000;
}
uint32 duration = mMetaValueList[i].value.uint32_value;
if (duration > 0 && timescale > 0) {
//set the timescale
mcc.set_timescale(timescale);
//set the clock to the duration as per the timescale
mcc.set_clock(duration,0);
//convert to millisec
cmd->set(mcc.get_converted_ts(1000));
}
}
}
}
void PlayerDriver::CommandCompleted(const PVCmdResponse& aResponse)
{
LOGV("CommandCompleted");
PVMFStatus status = aResponse.GetCmdStatus();
if (mDoLoop) {
mDoLoop = false;
RunIfNotReady();
return;
}
PlayerCommand* command = static_cast<PlayerCommand*>(aResponse.GetContext());
LOGV("Completed command %s status=%s", command ? command->toString(): "<null>", PVMFStatusToString(status));
if (command == NULL) return;
// FIXME: Ignore non-fatal seek errors because pvPlayerEngine returns these errors and retains it's state.
if (mSeekPending) {
mSeekPending = false;
if ( ( (status == PVMFErrArgument) || (status == PVMFErrInvalidState) || (status == PVMFErrNotSupported) ) ) {
LOGV("Ignoring error during seek");
status = PVMFSuccess;
}
}
if (status == PVMFSuccess) {
switch (command->code()) {
case PlayerCommand::PLAYER_PREPARE:
LOGV("PLAYER_PREPARE complete mDownloadContextData=%p, mDataReadyReceived=%d", mDownloadContextData, mDataReadyReceived);
mPrepareDone = true;
// If we are streaming from the network, we
// have to wait until the first PVMFInfoDataReady
// is sent to notify the user that it is okay to
// begin playback. If it is a local file, just
// send it now at the completion of Prepare().
if ((mDownloadContextData == NULL) || mDataReadyReceived) {
mPvPlayer->sendEvent(MEDIA_PREPARED);
}
break;
case PlayerCommand::PLAYER_GET_DURATION:
handleGetDurationComplete(static_cast<PlayerGetDuration*>(command));
break;
case PlayerCommand::PLAYER_CHECK_LIVE_STREAMING:
handleCheckLiveStreamingComplete(static_cast<PlayerCheckLiveStreaming*>(command));
break;
case PlayerCommand::PLAYER_PAUSE:
LOGV("pause complete");
break;
case PlayerCommand::PLAYER_SEEK:
mPvPlayer->sendEvent(MEDIA_SEEK_COMPLETE);
break;
default: /* shut up gcc */
break;
}
// Call the user's requested completion function
command->complete(NO_ERROR, false);
} else if (status == PVMFErrCancelled) {
// Ignore cancelled code return status (PVMFErrCancelled), since it is not an error.
LOGE("Command (%d) was cancelled", command->code());
status = PVMFSuccess;
command->complete(NO_ERROR, true);
} else {
// Try to map the PV error code to an Android one.
LOGE("Command %s completed with an error or info %s", command->toString(), PVMFStatusToString(status));
const media_event_type event_type = MapStatusToEventType(status);
if (MEDIA_NOP != event_type) {
mPvPlayer->sendEvent(event_type, MapStatusToEventCode(status), status);
} else {
LOGE("Ignoring: %d", status);
}
command->complete(UNKNOWN_ERROR, false);
}
delete command;
}
void PlayerDriver::HandleErrorEvent(const PVAsyncErrorEvent& aEvent)
{
PVMFStatus status = aEvent.GetEventType();
// Errors use negative codes (see pvmi/pvmf/include/pvmf_return_codes.h)
if (status > PVMFErrFirst) {
LOGE("HandleErrorEvent called with an non-error event [%d]!!", status);
}
LOGE("HandleErrorEvent: %s", PVMFStatusToString(status));
// TODO: Map more of the PV error code into the Android Media Player ones.
mPvPlayer->sendEvent(MEDIA_ERROR, ::android::MEDIA_ERROR_UNKNOWN, status);
}
void PlayerDriver::HandleInformationalEvent(const PVAsyncInformationalEvent& aEvent)
{
PVMFStatus status = aEvent.GetEventType();
// Errors use negative codes (see pvmi/pvmf/include/pvmf_return_codes.h)
if (status <= PVMFErrFirst) {
// Errors should go to the HandleErrorEvent handler, not the
// informational one.
LOGE("HandleInformationalEvent called with an error event [%d]!!", status);
}
LOGV("HandleInformationalEvent: %s", PVMFStatusToString(status));
switch (status) {
case PVMFInfoEndOfData:
mEndOfData = true;
if (mIsLooping) {
mDoLoop = true;
Cancel();
RunIfNotReady();
} else {
mPvPlayer->sendEvent(MEDIA_PLAYBACK_COMPLETE);
}
break;
case PVMFInfoErrorHandlingComplete:
LOGW("PVMFInfoErrorHandlingComplete");
RunIfNotReady();
break;
case PVMFInfoBufferingStart:
mPvPlayer->sendEvent(MEDIA_BUFFERING_UPDATE, 0);
break;
case PVMFInfoBufferingStatus:
{
const void *buffer = aEvent.GetLocalBuffer();
const size_t size = aEvent.GetLocalBufferSize();
int percentage;
// For HTTP sessions, if PVMFInfoContentLength has been
// received, only then the buffering status is a percentage
// of content length. Otherwise, it is the total number of
// bytes downloaded.
// For RTSP session, the buffering status is a percentage
// of the data that needs to be downloaded to start/resume
// playback.
if ( (mContentLengthKnown || (getFormatType() == PVMF_MIME_DATA_SOURCE_RTSP_URL) ) &&
(GetBufferingPercentage(buffer, size, &percentage)))
{
nsecs_t now = systemTime();
if (now - mLastBufferingLog > kBufferingUpdatePeriod) {
LOGI("buffering (%d)", percentage);
mLastBufferingLog = now;
}
mPvPlayer->sendEvent(MEDIA_BUFFERING_UPDATE, percentage);
}
}
break;
case PVMFInfoDurationAvailable:
{
PVUuid infomsguuid = PVMFDurationInfoMessageInterfaceUUID;
PVMFDurationInfoMessageInterface* eventMsg = NULL;
PVInterface* infoExtInterface = aEvent.GetEventExtensionInterface();
if (infoExtInterface &&
infoExtInterface->queryInterface(infomsguuid, (PVInterface*&)eventMsg))
{
PVUuid eventuuid;
int32 infoCode;
eventMsg->GetCodeUUID(infoCode, eventuuid);
if (eventuuid == infomsguuid)
{
uint32 SourceDurationInMS = eventMsg->GetDuration();
LOGV(".... with duration = %u ms",SourceDurationInMS);
}
}
}
break;
case PVMFInfoDataReady:
if (mDataReadyReceived)
break;
mDataReadyReceived = true;
// If this is a network stream, we are now ready to play.
if (mDownloadContextData && mPrepareDone) {
mPvPlayer->sendEvent(MEDIA_PREPARED);
}
break;
case PVMFInfoVideoTrackFallingBehind:
// FIXME:
// When this happens, sometimes, we only have audio but no video and it
// is not recoverable. We use the same approach as we did in previous
// releases, and send an error event instead of an informational event
// when this happens.
LOGW("Video track fell behind");
mPvPlayer->sendEvent(MEDIA_ERROR, ::android::MEDIA_ERROR_UNKNOWN,
PVMFInfoVideoTrackFallingBehind);
break;
case PVMFInfoPoorlyInterleavedContent:
// TODO: This event should not be passed to the user in the ERROR channel.
LOGW("Poorly interleaved content.");
mPvPlayer->sendEvent(MEDIA_INFO, ::android::MEDIA_INFO_BAD_INTERLEAVING,
PVMFInfoPoorlyInterleavedContent);
break;
case PVMFInfoContentTruncated:
LOGE("Content is truncated.");
// FIXME:
// While streaming YouTube videos, we receive PVMFInfoContentTruncated event
// after some seek operation. PV is still looking into OpenCore to see whether
// there is any bug associated with it; Meanwhile, lets treat this as an error
// since after playerdriver receives this event, playback session cannot be
// recovered.
mPvPlayer->sendEvent(MEDIA_ERROR, ::android::MEDIA_ERROR_UNKNOWN,
PVMFInfoContentTruncated);
break;
case PVMFInfoContentLength:
mContentLengthKnown = true;
break;
/* Certain events we don't really care about, but don't
* want log spewage, so just no-op them here.
*/
case PVMFInfoPositionStatus:
case PVMFInfoBufferingComplete:
case PVMFInfoContentType:
case PVMFInfoUnderflow:
case PVMFInfoDataDiscarded:
break;
default:
LOGV("HandleInformationalEvent: type=%d UNHANDLED", status);
mPvPlayer->sendEvent(MEDIA_INFO, ::android::MEDIA_INFO_UNKNOWN, status);
break;
}
}
// ----------------------------------------------------------------------------
// PlayerCommand implementation
// ----------------------------------------------------------------------------
const char* PlayerCommand::toString() const {
return PlayerCommandCodeToString(code());
}
namespace android {
#undef LOG_TAG
#define LOG_TAG "PVPlayer"
#ifdef MAX_OPENCORE_INSTANCES
/*static*/ volatile int32_t PVPlayer::sNumInstances = 0;
#endif
// ----------------------------------------------------------------------------
// implement the Packet Video player
// ----------------------------------------------------------------------------
PVPlayer::PVPlayer()
{
LOGV("PVPlayer constructor");
mDataSourcePath = NULL;
mSharedFd = -1;
mIsDataSourceSet = false;
mDuration = -1;
mPlayerDriver = NULL;
#ifdef MAX_OPENCORE_INSTANCES
if (android_atomic_inc(&sNumInstances) >= MAX_OPENCORE_INSTANCES) {
LOGW("Exceeds maximum number of OpenCore instances");
mInit = -EBUSY;
return;
}
#endif
LOGV("construct PlayerDriver");
mPlayerDriver = new PlayerDriver(this);
LOGV("send PLAYER_SETUP");
PlayerSetup* setup = new PlayerSetup(0,0);
mInit = mPlayerDriver->enqueueCommand(setup);
if (mInit == NO_INIT) {
delete setup;
}
}
status_t PVPlayer::initCheck()
{
return mInit;
}
PVPlayer::~PVPlayer()
{
LOGV("PVPlayer destructor");
if (mPlayerDriver != NULL) {
PlayerQuit quit = PlayerQuit(0,0);
mPlayerDriver->enqueueCommand(&quit); // will wait on mSyncSem, signaled by player thread
}
free(mDataSourcePath);
if (mSharedFd >= 0) {
close(mSharedFd);
}
#ifdef MAX_OPENCORE_INSTANCES
android_atomic_dec(&sNumInstances);
#endif
}
status_t PVPlayer::setDataSource(
const char *url, const KeyedVector<String8, String8> *)
{
LOGV("setDataSource(%s)", url);
if (mSharedFd >= 0) {
close(mSharedFd);
mSharedFd = -1;
}
free(mDataSourcePath);
mDataSourcePath = NULL;
// Don't let somebody trick us in to reading some random block of memory
if (strncmp("sharedfd://", url, 11) == 0)
return android::UNKNOWN_ERROR;
mDataSourcePath = strdup(url);
return OK;
}
status_t PVPlayer::setDataSource(int fd, int64_t offset, int64_t length) {
// This is all a big hack to allow PV to play from a file descriptor.
// Eventually we'll fix PV to use a file descriptor directly instead
// of using mmap().
LOGV("setDataSource(%d, %lld, %lld)", fd, offset, length);
if (mSharedFd >= 0) {
close(mSharedFd);
mSharedFd = -1;
}
free(mDataSourcePath);
mDataSourcePath = NULL;
char buf[80];
mSharedFd = dup(fd);
sprintf(buf, "sharedfd://%d:%lld:%lld", mSharedFd, offset, length);
mDataSourcePath = strdup(buf);
return OK;
}
status_t PVPlayer::setVideoSurface(const sp<ISurface>& surface)
{
LOGV("setVideoSurface(%p)", surface.get());
mSurface = surface;
return OK;
}
status_t PVPlayer::prepare()
{
status_t ret;
// We need to differentiate the two valid use cases for prepare():
// 1. new PVPlayer/reset()->setDataSource()->prepare()
// 2. new PVPlayer/reset()->setDataSource()->prepare()/prepareAsync()
// ->start()->...->stop()->prepare()
// If data source has already been set previously, no need to run
// a sequence of commands and only the PLAYER_PREPARE code needs
// to be run.
if (!mIsDataSourceSet) {
// set data source
LOGV("prepare");
LOGV(" data source = %s", mDataSourcePath);
ret = mPlayerDriver->enqueueCommand(new PlayerSetDataSource(mDataSourcePath,0,0));
if (ret != OK)
return ret;
// init
LOGV(" init");
ret = mPlayerDriver->enqueueCommand(new PlayerInit(0,0));
if (ret != OK)
return ret;
// set video surface, if there is one
if (mSurface != NULL) {
LOGV(" set video surface");
ret = mPlayerDriver->enqueueCommand(new PlayerSetVideoSurface(mSurface,0,0));
if (ret != OK)
return ret;
}
// set audio output
// If we ever need to expose selectable audio output setup, this can be broken
// out. In the meantime, however, system audio routing APIs should suffice.
LOGV(" set audio sink");
ret = mPlayerDriver->enqueueCommand(new PlayerSetAudioSink(mAudioSink,0,0));
if (ret != OK)
return ret;
// New data source has been set successfully.
mIsDataSourceSet = true;
}
// prepare
LOGV(" prepare");
return mPlayerDriver->enqueueCommand(new PlayerPrepare(check_for_live_streaming, this));
}
void PVPlayer::check_for_live_streaming(status_t s, void *cookie, bool cancelled)
{
LOGV("check_for_live_streaming s=%d, cancelled=%d", s, cancelled);
if (s == NO_ERROR && !cancelled) {
PVPlayer *p = (PVPlayer*)cookie;
if ( (p->mPlayerDriver->getFormatType() == PVMF_MIME_DATA_SOURCE_RTSP_URL) ||
(p->mPlayerDriver->getFormatType() == PVMF_MIME_DATA_SOURCE_MS_HTTP_STREAMING_URL) ) {
p->mPlayerDriver->enqueueCommand(new PlayerCheckLiveStreaming( do_nothing, NULL));
}
}
}
void PVPlayer::run_init(status_t s, void *cookie, bool cancelled)
{
LOGV("run_init s=%d, cancelled=%d", s, cancelled);
if (s == NO_ERROR && !cancelled) {
PVPlayer *p = (PVPlayer*)cookie;
p->mPlayerDriver->enqueueCommand(new PlayerInit(run_set_video_surface, cookie));
}
}
void PVPlayer::run_set_video_surface(status_t s, void *cookie, bool cancelled)
{
LOGV("run_set_video_surface s=%d, cancelled=%d", s, cancelled);
if (s == NO_ERROR && !cancelled) {
// If we don't have a video surface, just skip to the next step.
PVPlayer *p = (PVPlayer*)cookie;
if (p->mSurface == NULL) {
run_set_audio_output(s, cookie, false);
} else {
p->mPlayerDriver->enqueueCommand(new PlayerSetVideoSurface(p->mSurface, run_set_audio_output, cookie));
}
}
}
void PVPlayer::run_set_audio_output(status_t s, void *cookie, bool cancelled)
{
LOGV("run_set_audio_output s=%d, cancelled=%d", s, cancelled);
if (s == NO_ERROR && !cancelled) {
PVPlayer *p = (PVPlayer*)cookie;
p->mPlayerDriver->enqueueCommand(new PlayerSetAudioSink(p->mAudioSink, run_prepare, cookie));
}
}
void PVPlayer::run_prepare(status_t s, void *cookie, bool cancelled)
{
LOGV("run_prepare s=%d, cancelled=%d", s, cancelled);
if (s == NO_ERROR && !cancelled) {
PVPlayer *p = (PVPlayer*)cookie;
p->mPlayerDriver->enqueueCommand(new PlayerPrepare(check_for_live_streaming, cookie));
}
}
status_t PVPlayer::prepareAsync()
{
LOGV("prepareAsync");
status_t ret = OK;
if (!mIsDataSourceSet) { // If data source has NOT been set.
// Set our data source as cached in setDataSource() above.
LOGV(" data source = %s", mDataSourcePath);
ret = mPlayerDriver->enqueueCommand(new PlayerSetDataSource(mDataSourcePath,run_init,this));
mIsDataSourceSet = true;
} else { // If data source has been already set.
// No need to run a sequence of commands.
// The only code needed to run is PLAYER_PREPARE.
ret = mPlayerDriver->enqueueCommand(new PlayerPrepare(check_for_live_streaming, this));
}
return ret;
}
status_t PVPlayer::start()
{
LOGV("start");
return mPlayerDriver->enqueueCommand(new PlayerStart(0,0));
}
status_t PVPlayer::stop()
{
LOGV("stop");
return mPlayerDriver->enqueueCommand(new PlayerStop(0,0));
}
status_t PVPlayer::pause()
{
LOGV("pause");
return mPlayerDriver->enqueueCommand(new PlayerPause(0,0));
}
bool PVPlayer::isPlaying()
{
int status = 0;
if (mPlayerDriver->enqueueCommand(new PlayerGetStatus(&status,0,0)) == NO_ERROR) {
return (status == PVP_STATE_STARTED);
}
return false;
}
status_t PVPlayer::getCurrentPosition(int *msec)
{
return mPlayerDriver->enqueueCommand(new PlayerGetPosition(msec,0,0));
}
status_t PVPlayer::getDuration(int *msec)
{
status_t ret = mPlayerDriver->enqueueCommand(new PlayerGetDuration(msec,0,0));
if (ret == NO_ERROR) mDuration = *msec;
return ret;
}
status_t PVPlayer::seekTo(int msec)
{
LOGV("seekTo(%d)", msec);
// can't always seek to end of streams - so we fudge a little
if ((msec == mDuration) && (mDuration > 0)) {
msec--;
LOGV("Seek adjusted 1 msec from end");
}
return mPlayerDriver->enqueueCommand(new PlayerSeek(msec,do_nothing,0));
}
status_t PVPlayer::reset()
{
LOGV("reset");
status_t ret = mPlayerDriver->enqueueCommand(new PlayerCancelAllCommands(0,0));
// Log failure from CancelAllCommands() and call Reset() regardless.
if (ret != NO_ERROR) {
LOGE("failed to cancel all exiting PV player engine commands with error code (%d)", ret);
}
ret = mPlayerDriver->enqueueCommand(new PlayerReset(0,0));
// We should never fail in Reset(), but logs the failure just in case.
if (ret != NO_ERROR) {
LOGE("failed to reset PV player engine with error code (%d)", ret);
} else {
ret = mPlayerDriver->enqueueCommand(new PlayerRemoveDataSource(0,0));
}
mSurface.clear();
LOGV("unmap file");
if (mSharedFd >= 0) {
close(mSharedFd);
mSharedFd = -1;
}
mIsDataSourceSet = false;
return ret;
}
status_t PVPlayer::setLooping(int loop)
{
LOGV("setLooping(%d)", loop);
return mPlayerDriver->enqueueCommand(new PlayerSetLoop(loop,0,0));
}
// This is a stub for the direct invocation API.
// From include/media/MediaPlayerInterface.h where the abstract method
// is declared:
//
// Invoke a generic method on the player by using opaque parcels
// for the request and reply.
// @param request Parcel that must start with the media player
// interface token.
// @param[out] reply Parcel to hold the reply data. Cannot be null.
// @return OK if the invocation was made successfully.
//
// This stub should be replaced with a concrete implementation.
//
// Typically the request parcel will contain an opcode to identify an
// operation to be executed. There might also be a handle used to
// create a session between the client and the player.
//
// The concrete implementation can then dispatch the request
// internally based on the double (opcode, handle).
status_t PVPlayer::invoke(const Parcel& request, Parcel *reply)
{
return INVALID_OPERATION;
}
// Called by the MediaPlayerService::Client to retrieve a set or all
// the metadata if ids is empty.
status_t PVPlayer::getMetadata(const media::Metadata::Filter& ids,
Parcel *records) {
using media::Metadata;
if (!mPlayerDriver || !mPlayerDriver->prepareDone()) {
return INVALID_OPERATION;
}
if (ids.size() != 0) {
LOGW("Metadata filtering not implemented, ignoring.");
}
Metadata metadata(records);
bool ok = true;
// Right now, we only communicate info about the liveness of the
// stream to enable/disable pause and seek in the UI.
const bool live = mPlayerDriver->isLiveStreaming();
ok = ok && metadata.appendBool(Metadata::kPauseAvailable, !live);
ok = ok && metadata.appendBool(Metadata::kSeekBackwardAvailable, !live);
ok = ok && metadata.appendBool(Metadata::kSeekForwardAvailable, !live);
return ok ? OK : UNKNOWN_ERROR;
}
} // namespace android