/*
** 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 "VorbisPlayer"
#include "utils/Log.h"
#include <stdio.h>
#include <assert.h>
#include <limits.h>
#include <unistd.h>
#include <fcntl.h>
#include <sched.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "VorbisPlayer.h"
#ifdef HAVE_GETTID
static pid_t myTid() { return gettid(); }
#else
static pid_t myTid() { return getpid(); }
#endif
// ----------------------------------------------------------------------------
namespace android {
// ----------------------------------------------------------------------------
// TODO: Determine appropriate return codes
static status_t ERROR_NOT_OPEN = -1;
static status_t ERROR_OPEN_FAILED = -2;
static status_t ERROR_ALLOCATE_FAILED = -4;
static status_t ERROR_NOT_SUPPORTED = -8;
static status_t ERROR_NOT_READY = -16;
static status_t STATE_INIT = 0;
static status_t STATE_ERROR = 1;
static status_t STATE_OPEN = 2;
VorbisPlayer::VorbisPlayer() :
mAudioBuffer(NULL), mPlayTime(-1), mDuration(-1), mState(STATE_ERROR),
mStreamType(AudioSystem::MUSIC), mLoop(false), mAndroidLoop(false),
mExit(false), mPaused(false), mRender(false), mRenderTid(-1)
{
LOGV("constructor\n");
memset(&mVorbisFile, 0, sizeof mVorbisFile);
}
void VorbisPlayer::onFirstRef()
{
LOGV("onFirstRef");
// create playback thread
Mutex::Autolock l(mMutex);
createThreadEtc(renderThread, this, "vorbis decoder", ANDROID_PRIORITY_AUDIO);
mCondition.wait(mMutex);
if (mRenderTid > 0) {
LOGV("render thread(%d) started", mRenderTid);
mState = STATE_INIT;
}
}
status_t VorbisPlayer::initCheck()
{
if (mState != STATE_ERROR) return NO_ERROR;
return ERROR_NOT_READY;
}
VorbisPlayer::~VorbisPlayer() {
LOGV("VorbisPlayer destructor\n");
release();
}
status_t VorbisPlayer::setDataSource(
const char *uri, const KeyedVector<String8, String8> *headers) {
return setdatasource(uri, -1, 0, 0x7ffffffffffffffLL); // intentionally less than LONG_MAX
}
status_t VorbisPlayer::setDataSource(int fd, int64_t offset, int64_t length)
{
return setdatasource(NULL, fd, offset, length);
}
size_t VorbisPlayer::vp_fread(void *buf, size_t size, size_t nmemb, void *me) {
VorbisPlayer *self = (VorbisPlayer*) me;
long curpos = vp_ftell(me);
while (nmemb != 0 && (curpos + size * nmemb) > self->mLength) {
nmemb--;
}
return fread(buf, size, nmemb, self->mFile);
}
int VorbisPlayer::vp_fseek(void *me, ogg_int64_t off, int whence) {
VorbisPlayer *self = (VorbisPlayer*) me;
if (whence == SEEK_SET)
return fseek(self->mFile, off + self->mOffset, whence);
else if (whence == SEEK_CUR)
return fseek(self->mFile, off, whence);
else if (whence == SEEK_END)
return fseek(self->mFile, self->mOffset + self->mLength + off, SEEK_SET);
return -1;
}
int VorbisPlayer::vp_fclose(void *me) {
LOGV("vp_fclose");
VorbisPlayer *self = (VorbisPlayer*) me;
int ret = fclose (self->mFile);
self->mFile = NULL;
return ret;
}
long VorbisPlayer::vp_ftell(void *me) {
VorbisPlayer *self = (VorbisPlayer*) me;
return ftell(self->mFile) - self->mOffset;
}
status_t VorbisPlayer::setdatasource(const char *path, int fd, int64_t offset, int64_t length)
{
LOGV("setDataSource url=%s, fd=%d\n", path, fd);
// file still open?
Mutex::Autolock l(mMutex);
if (mState == STATE_OPEN) {
reset_nosync();
}
// open file and set paused state
if (path) {
mFile = fopen(path, "r");
} else {
mFile = fdopen(dup(fd), "r");
}
if (mFile == NULL) {
return ERROR_OPEN_FAILED;
}
struct stat sb;
int ret;
if (path) {
ret = stat(path, &sb);
} else {
ret = fstat(fd, &sb);
}
if (ret != 0) {
mState = STATE_ERROR;
fclose(mFile);
return ERROR_OPEN_FAILED;
}
if (sb.st_size > (length + offset)) {
mLength = length;
} else {
mLength = sb.st_size - offset;
}
ov_callbacks callbacks = {
(size_t (*)(void *, size_t, size_t, void *)) vp_fread,
(int (*)(void *, ogg_int64_t, int)) vp_fseek,
(int (*)(void *)) vp_fclose,
(long (*)(void *)) vp_ftell
};
mOffset = offset;
fseek(mFile, offset, SEEK_SET);
int result = ov_open_callbacks(this, &mVorbisFile, NULL, 0, callbacks);
if (result < 0) {
LOGE("ov_open() failed: [%d]\n", (int)result);
mState = STATE_ERROR;
fclose(mFile);
return ERROR_OPEN_FAILED;
}
// look for the android loop tag (for ringtones)
char **ptr = ov_comment(&mVorbisFile,-1)->user_comments;
while(*ptr) {
// does the comment start with ANDROID_LOOP_TAG
if(strncmp(*ptr, ANDROID_LOOP_TAG, strlen(ANDROID_LOOP_TAG)) == 0) {
// read the value of the tag
char *val = *ptr + strlen(ANDROID_LOOP_TAG) + 1;
mAndroidLoop = (strncmp(val, "true", 4) == 0);
}
// we keep parsing even after finding one occurence of ANDROID_LOOP_TAG,
// as we could find another one (the tag might have been appended more than once).
++ptr;
}
LOGV_IF(mAndroidLoop, "looped sound");
mState = STATE_OPEN;
return NO_ERROR;
}
status_t VorbisPlayer::prepare()
{
LOGV("prepare\n");
if (mState != STATE_OPEN ) {
return ERROR_NOT_OPEN;
}
return NO_ERROR;
}
status_t VorbisPlayer::prepareAsync() {
LOGV("prepareAsync\n");
// can't hold the lock here because of the callback
// it's safe because we don't change state
if (mState != STATE_OPEN ) {
sendEvent(MEDIA_ERROR);
return NO_ERROR;
}
sendEvent(MEDIA_PREPARED);
return NO_ERROR;
}
status_t VorbisPlayer::start()
{
LOGV("start\n");
Mutex::Autolock l(mMutex);
if (mState != STATE_OPEN) {
return ERROR_NOT_OPEN;
}
mPaused = false;
mRender = true;
// wake up render thread
LOGV(" wakeup render thread\n");
mCondition.signal();
return NO_ERROR;
}
status_t VorbisPlayer::stop()
{
LOGV("stop\n");
Mutex::Autolock l(mMutex);
if (mState != STATE_OPEN) {
return ERROR_NOT_OPEN;
}
mPaused = true;
mRender = false;
return NO_ERROR;
}
status_t VorbisPlayer::seekTo(int position)
{
LOGV("seekTo %d\n", position);
Mutex::Autolock l(mMutex);
if (mState != STATE_OPEN) {
return ERROR_NOT_OPEN;
}
int result = ov_time_seek(&mVorbisFile, position);
if (result != 0) {
LOGE("ov_time_seek() returned %d\n", result);
return result;
}
sendEvent(MEDIA_SEEK_COMPLETE);
return NO_ERROR;
}
status_t VorbisPlayer::pause()
{
LOGV("pause\n");
Mutex::Autolock l(mMutex);
if (mState != STATE_OPEN) {
return ERROR_NOT_OPEN;
}
mPaused = true;
return NO_ERROR;
}
bool VorbisPlayer::isPlaying()
{
LOGV("isPlaying\n");
if (mState == STATE_OPEN) {
return mRender;
}
return false;
}
status_t VorbisPlayer::getCurrentPosition(int* position)
{
LOGV("getCurrentPosition\n");
Mutex::Autolock l(mMutex);
if (mState != STATE_OPEN) {
LOGE("getCurrentPosition(): file not open");
return ERROR_NOT_OPEN;
}
*position = ov_time_tell(&mVorbisFile);
if (*position < 0) {
LOGE("getCurrentPosition(): ov_time_tell returned %d", *position);
return *position;
}
return NO_ERROR;
}
status_t VorbisPlayer::getDuration(int* duration)
{
LOGV("getDuration\n");
Mutex::Autolock l(mMutex);
if (mState != STATE_OPEN) {
return ERROR_NOT_OPEN;
}
int ret = ov_time_total(&mVorbisFile, -1);
if (ret == OV_EINVAL) {
return -1;
}
*duration = ret;
return NO_ERROR;
}
status_t VorbisPlayer::release()
{
LOGV("release\n");
Mutex::Autolock l(mMutex);
reset_nosync();
// TODO: timeout when thread won't exit
// wait for render thread to exit
if (mRenderTid > 0) {
mExit = true;
mCondition.signal();
mCondition.wait(mMutex);
}
return NO_ERROR;
}
status_t VorbisPlayer::reset()
{
LOGV("reset\n");
Mutex::Autolock l(mMutex);
return reset_nosync();
}
// always call with lock held
status_t VorbisPlayer::reset_nosync()
{
// close file
if (mFile != NULL) {
ov_clear(&mVorbisFile); // this also closes the FILE
if (mFile != NULL) {
LOGV("OOPS! Vorbis didn't close the file");
fclose(mFile);
mFile = NULL;
}
}
mState = STATE_ERROR;
mPlayTime = -1;
mDuration = -1;
mLoop = false;
mAndroidLoop = false;
mPaused = false;
mRender = false;
return NO_ERROR;
}
status_t VorbisPlayer::setLooping(int loop)
{
LOGV("setLooping\n");
Mutex::Autolock l(mMutex);
mLoop = (loop != 0);
return NO_ERROR;
}
status_t VorbisPlayer::createOutputTrack() {
// open audio track
vorbis_info *vi = ov_info(&mVorbisFile, -1);
LOGV("Create AudioTrack object: rate=%ld, channels=%d\n",
vi->rate, vi->channels);
if (mAudioSink->open(vi->rate, vi->channels, AudioSystem::PCM_16_BIT, DEFAULT_AUDIOSINK_BUFFERCOUNT) != NO_ERROR) {
LOGE("mAudioSink open failed");
return ERROR_OPEN_FAILED;
}
return NO_ERROR;
}
int VorbisPlayer::renderThread(void* p) {
return ((VorbisPlayer*)p)->render();
}
#define AUDIOBUFFER_SIZE 4096
int VorbisPlayer::render() {
int result = -1;
int temp;
int current_section = 0;
bool audioStarted = false;
LOGV("render\n");
// allocate render buffer
mAudioBuffer = new char[AUDIOBUFFER_SIZE];
if (!mAudioBuffer) {
LOGE("mAudioBuffer allocate failed\n");
goto threadExit;
}
// let main thread know we're ready
{
Mutex::Autolock l(mMutex);
mRenderTid = myTid();
mCondition.signal();
}
while (1) {
long numread = 0;
{
Mutex::Autolock l(mMutex);
// pausing?
if (mPaused) {
if (mAudioSink->ready()) mAudioSink->pause();
mRender = false;
audioStarted = false;
}
// nothing to render, wait for client thread to wake us up
if (!mExit && !mRender) {
LOGV("render - signal wait\n");
mCondition.wait(mMutex);
LOGV("render - signal rx'd\n");
}
if (mExit) break;
// We could end up here if start() is called, and before we get a
// chance to run, the app calls stop() or reset(). Re-check render
// flag so we don't try to render in stop or reset state.
if (!mRender) continue;
// render vorbis data into the input buffer
numread = ov_read(&mVorbisFile, mAudioBuffer, AUDIOBUFFER_SIZE, ¤t_section);
if (numread == 0) {
// end of file, do we need to loop?
// ...
if (mLoop || mAndroidLoop) {
ov_time_seek(&mVorbisFile, 0);
current_section = 0;
numread = ov_read(&mVorbisFile, mAudioBuffer, AUDIOBUFFER_SIZE, ¤t_section);
} else {
mAudioSink->stop();
audioStarted = false;
mRender = false;
mPaused = true;
int endpos = ov_time_tell(&mVorbisFile);
LOGV("send MEDIA_PLAYBACK_COMPLETE");
sendEvent(MEDIA_PLAYBACK_COMPLETE);
// wait until we're started again
LOGV("playback complete - wait for signal");
mCondition.wait(mMutex);
LOGV("playback complete - signal rx'd");
if (mExit) break;
// if we're still at the end, restart from the beginning
if (mState == STATE_OPEN) {
int curpos = ov_time_tell(&mVorbisFile);
if (curpos == endpos) {
ov_time_seek(&mVorbisFile, 0);
}
current_section = 0;
numread = ov_read(&mVorbisFile, mAudioBuffer, AUDIOBUFFER_SIZE, ¤t_section);
}
}
}
}
// codec returns negative number on error
if (numread < 0) {
LOGE("Error in Vorbis decoder");
sendEvent(MEDIA_ERROR);
break;
}
// create audio output track if necessary
if (!mAudioSink->ready()) {
LOGV("render - create output track\n");
if (createOutputTrack() != NO_ERROR)
break;
}
// Write data to the audio hardware
if ((temp = mAudioSink->write(mAudioBuffer, numread)) < 0) {
LOGE("Error in writing:%d",temp);
result = temp;
break;
}
// start audio output if necessary
if (!audioStarted && !mPaused && !mExit) {
LOGV("render - starting audio\n");
mAudioSink->start();
audioStarted = true;
}
}
threadExit:
mAudioSink.clear();
if (mAudioBuffer) {
delete [] mAudioBuffer;
mAudioBuffer = NULL;
}
// tell main thread goodbye
Mutex::Autolock l(mMutex);
mRenderTid = -1;
mCondition.signal();
return result;
}
} // end namespace android