/* ** 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