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