Dave Sparks 978811960a We might try to close the Vorbis file twice under certain
circumstances. This fix nulls the mFile member so we don't
try to close it twice. Bug 1904783.
2009-06-26 17:24:22 -07:00

530 lines
14 KiB
C++

/*
** 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* path)
{
return setdatasource(path, -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, &current_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, &current_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, &current_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