Home | History | Annotate | Download | only in libmediaplayerservice
      1 /* MidiFile.cpp
      2 **
      3 ** Copyright 2007, The Android Open Source Project
      4 **
      5 ** Licensed under the Apache License, Version 2.0 (the "License");
      6 ** you may not use this file except in compliance with the License.
      7 ** You may obtain a copy of the License at
      8 **
      9 **     http://www.apache.org/licenses/LICENSE-2.0
     10 **
     11 ** Unless required by applicable law or agreed to in writing, software
     12 ** distributed under the License is distributed on an "AS IS" BASIS,
     13 ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14 ** See the License for the specific language governing permissions and
     15 ** limitations under the License.
     16 */
     17 
     18 //#define LOG_NDEBUG 0
     19 #define LOG_TAG "MidiFile"
     20 #include "utils/Log.h"
     21 
     22 #include <stdio.h>
     23 #include <assert.h>
     24 #include <limits.h>
     25 #include <unistd.h>
     26 #include <fcntl.h>
     27 #include <sched.h>
     28 #include <utils/threads.h>
     29 #include <libsonivox/eas_reverb.h>
     30 #include <sys/types.h>
     31 #include <sys/stat.h>
     32 #include <unistd.h>
     33 
     34 #include <system/audio.h>
     35 
     36 #include "MidiFile.h"
     37 
     38 // ----------------------------------------------------------------------------
     39 
     40 namespace android {
     41 
     42 // ----------------------------------------------------------------------------
     43 
     44 // The midi engine buffers are a bit small (128 frames), so we batch them up
     45 static const int NUM_BUFFERS = 4;
     46 
     47 // TODO: Determine appropriate return codes
     48 static status_t ERROR_NOT_OPEN = -1;
     49 static status_t ERROR_OPEN_FAILED = -2;
     50 static status_t ERROR_EAS_FAILURE = -3;
     51 static status_t ERROR_ALLOCATE_FAILED = -4;
     52 
     53 static const S_EAS_LIB_CONFIG* pLibConfig = NULL;
     54 
     55 MidiFile::MidiFile() :
     56     mEasData(NULL), mEasHandle(NULL), mAudioBuffer(NULL),
     57     mPlayTime(-1), mDuration(-1), mState(EAS_STATE_ERROR),
     58     mStreamType(AUDIO_STREAM_MUSIC), mLoop(false), mExit(false),
     59     mPaused(false), mRender(false), mTid(-1)
     60 {
     61     ALOGV("constructor");
     62 
     63     mFileLocator.path = NULL;
     64     mFileLocator.fd = -1;
     65     mFileLocator.offset = 0;
     66     mFileLocator.length = 0;
     67 
     68     // get the library configuration and do sanity check
     69     if (pLibConfig == NULL)
     70         pLibConfig = EAS_Config();
     71     if ((pLibConfig == NULL) || (LIB_VERSION != pLibConfig->libVersion)) {
     72         ALOGE("EAS library/header mismatch");
     73         goto Failed;
     74     }
     75 
     76     // initialize EAS library
     77     if (EAS_Init(&mEasData) != EAS_SUCCESS) {
     78         ALOGE("EAS_Init failed");
     79         goto Failed;
     80     }
     81 
     82     // select reverb preset and enable
     83     EAS_SetParameter(mEasData, EAS_MODULE_REVERB, EAS_PARAM_REVERB_PRESET, EAS_PARAM_REVERB_CHAMBER);
     84     EAS_SetParameter(mEasData, EAS_MODULE_REVERB, EAS_PARAM_REVERB_BYPASS, EAS_FALSE);
     85 
     86     // create playback thread
     87     {
     88         Mutex::Autolock l(mMutex);
     89         mThread = new MidiFileThread(this);
     90         mThread->run("midithread", ANDROID_PRIORITY_AUDIO);
     91         mCondition.wait(mMutex);
     92         ALOGV("thread started");
     93     }
     94 
     95     // indicate success
     96     if (mTid > 0) {
     97         ALOGV(" render thread(%d) started", mTid);
     98         mState = EAS_STATE_READY;
     99     }
    100 
    101 Failed:
    102     return;
    103 }
    104 
    105 status_t MidiFile::initCheck()
    106 {
    107     if (mState == EAS_STATE_ERROR) return ERROR_EAS_FAILURE;
    108     return NO_ERROR;
    109 }
    110 
    111 MidiFile::~MidiFile() {
    112     ALOGV("MidiFile destructor");
    113     release();
    114 }
    115 
    116 status_t MidiFile::setDataSource(
    117         const sp<IMediaHTTPService> & /*httpService*/,
    118         const char* path,
    119         const KeyedVector<String8, String8> *) {
    120     ALOGV("MidiFile::setDataSource url=%s", path);
    121     Mutex::Autolock lock(mMutex);
    122 
    123     // file still open?
    124     if (mEasHandle) {
    125         reset_nosync();
    126     }
    127 
    128     // open file and set paused state
    129     mFileLocator.path = strdup(path);
    130     mFileLocator.fd = -1;
    131     mFileLocator.offset = 0;
    132     mFileLocator.length = 0;
    133     EAS_RESULT result = EAS_OpenFile(mEasData, &mFileLocator, &mEasHandle);
    134     if (result == EAS_SUCCESS) {
    135         updateState();
    136     }
    137 
    138     if (result != EAS_SUCCESS) {
    139         ALOGE("EAS_OpenFile failed: [%d]", (int)result);
    140         mState = EAS_STATE_ERROR;
    141         return ERROR_OPEN_FAILED;
    142     }
    143 
    144     mState = EAS_STATE_OPEN;
    145     mPlayTime = 0;
    146     return NO_ERROR;
    147 }
    148 
    149 status_t MidiFile::setDataSource(int fd, int64_t offset, int64_t length)
    150 {
    151     ALOGV("MidiFile::setDataSource fd=%d", fd);
    152     Mutex::Autolock lock(mMutex);
    153 
    154     // file still open?
    155     if (mEasHandle) {
    156         reset_nosync();
    157     }
    158 
    159     // open file and set paused state
    160     mFileLocator.fd = dup(fd);
    161     mFileLocator.offset = offset;
    162     mFileLocator.length = length;
    163     EAS_RESULT result = EAS_OpenFile(mEasData, &mFileLocator, &mEasHandle);
    164     updateState();
    165 
    166     if (result != EAS_SUCCESS) {
    167         ALOGE("EAS_OpenFile failed: [%d]", (int)result);
    168         mState = EAS_STATE_ERROR;
    169         return ERROR_OPEN_FAILED;
    170     }
    171 
    172     mState = EAS_STATE_OPEN;
    173     mPlayTime = 0;
    174     return NO_ERROR;
    175 }
    176 
    177 status_t MidiFile::prepare()
    178 {
    179     ALOGV("MidiFile::prepare");
    180     Mutex::Autolock lock(mMutex);
    181     if (!mEasHandle) {
    182         return ERROR_NOT_OPEN;
    183     }
    184     EAS_RESULT result;
    185     if ((result = EAS_Prepare(mEasData, mEasHandle)) != EAS_SUCCESS) {
    186         ALOGE("EAS_Prepare failed: [%ld]", result);
    187         return ERROR_EAS_FAILURE;
    188     }
    189     updateState();
    190     return NO_ERROR;
    191 }
    192 
    193 status_t MidiFile::prepareAsync()
    194 {
    195     ALOGV("MidiFile::prepareAsync");
    196     status_t ret = prepare();
    197 
    198     // don't hold lock during callback
    199     if (ret == NO_ERROR) {
    200         sendEvent(MEDIA_PREPARED);
    201     } else {
    202         sendEvent(MEDIA_ERROR, MEDIA_ERROR_UNKNOWN, ret);
    203     }
    204     return ret;
    205 }
    206 
    207 status_t MidiFile::start()
    208 {
    209     ALOGV("MidiFile::start");
    210     Mutex::Autolock lock(mMutex);
    211     if (!mEasHandle) {
    212         return ERROR_NOT_OPEN;
    213     }
    214 
    215     // resuming after pause?
    216     if (mPaused) {
    217         if (EAS_Resume(mEasData, mEasHandle) != EAS_SUCCESS) {
    218             return ERROR_EAS_FAILURE;
    219         }
    220         mPaused = false;
    221         updateState();
    222     }
    223 
    224     mRender = true;
    225     if (mState == EAS_STATE_PLAY) {
    226         sendEvent(MEDIA_STARTED);
    227     }
    228 
    229     // wake up render thread
    230     ALOGV("  wakeup render thread");
    231     mCondition.signal();
    232     return NO_ERROR;
    233 }
    234 
    235 status_t MidiFile::stop()
    236 {
    237     ALOGV("MidiFile::stop");
    238     Mutex::Autolock lock(mMutex);
    239     if (!mEasHandle) {
    240         return ERROR_NOT_OPEN;
    241     }
    242     if (!mPaused && (mState != EAS_STATE_STOPPED)) {
    243         EAS_RESULT result = EAS_Pause(mEasData, mEasHandle);
    244         if (result != EAS_SUCCESS) {
    245             ALOGE("EAS_Pause returned error %ld", result);
    246             return ERROR_EAS_FAILURE;
    247         }
    248     }
    249     mPaused = false;
    250     sendEvent(MEDIA_STOPPED);
    251     return NO_ERROR;
    252 }
    253 
    254 status_t MidiFile::seekTo(int position)
    255 {
    256     ALOGV("MidiFile::seekTo %d", position);
    257     // hold lock during EAS calls
    258     {
    259         Mutex::Autolock lock(mMutex);
    260         if (!mEasHandle) {
    261             return ERROR_NOT_OPEN;
    262         }
    263         EAS_RESULT result;
    264         if ((result = EAS_Locate(mEasData, mEasHandle, position, false))
    265                 != EAS_SUCCESS)
    266         {
    267             ALOGE("EAS_Locate returned %ld", result);
    268             return ERROR_EAS_FAILURE;
    269         }
    270         EAS_GetLocation(mEasData, mEasHandle, &mPlayTime);
    271     }
    272     sendEvent(MEDIA_SEEK_COMPLETE);
    273     return NO_ERROR;
    274 }
    275 
    276 status_t MidiFile::pause()
    277 {
    278     ALOGV("MidiFile::pause");
    279     Mutex::Autolock lock(mMutex);
    280     if (!mEasHandle) {
    281         return ERROR_NOT_OPEN;
    282     }
    283     if ((mState == EAS_STATE_PAUSING) || (mState == EAS_STATE_PAUSED)) return NO_ERROR;
    284     if (EAS_Pause(mEasData, mEasHandle) != EAS_SUCCESS) {
    285         return ERROR_EAS_FAILURE;
    286     }
    287     mPaused = true;
    288     sendEvent(MEDIA_PAUSED);
    289     return NO_ERROR;
    290 }
    291 
    292 bool MidiFile::isPlaying()
    293 {
    294     ALOGV("MidiFile::isPlaying, mState=%d", int(mState));
    295     if (!mEasHandle || mPaused) return false;
    296     return (mState == EAS_STATE_PLAY);
    297 }
    298 
    299 status_t MidiFile::getCurrentPosition(int* position)
    300 {
    301     ALOGV("MidiFile::getCurrentPosition");
    302     if (!mEasHandle) {
    303         ALOGE("getCurrentPosition(): file not open");
    304         return ERROR_NOT_OPEN;
    305     }
    306     if (mPlayTime < 0) {
    307         ALOGE("getCurrentPosition(): mPlayTime = %ld", mPlayTime);
    308         return ERROR_EAS_FAILURE;
    309     }
    310     *position = mPlayTime;
    311     return NO_ERROR;
    312 }
    313 
    314 status_t MidiFile::getDuration(int* duration)
    315 {
    316 
    317     ALOGV("MidiFile::getDuration");
    318     {
    319         Mutex::Autolock lock(mMutex);
    320         if (!mEasHandle) return ERROR_NOT_OPEN;
    321         *duration = mDuration;
    322     }
    323 
    324     // if no duration cached, get the duration
    325     // don't need a lock here because we spin up a new engine
    326     if (*duration < 0) {
    327         EAS_I32 temp;
    328         EAS_DATA_HANDLE easData = NULL;
    329         EAS_HANDLE easHandle = NULL;
    330         EAS_RESULT result = EAS_Init(&easData);
    331         if (result == EAS_SUCCESS) {
    332             result = EAS_OpenFile(easData, &mFileLocator, &easHandle);
    333         }
    334         if (result == EAS_SUCCESS) {
    335             result = EAS_Prepare(easData, easHandle);
    336         }
    337         if (result == EAS_SUCCESS) {
    338             result = EAS_ParseMetaData(easData, easHandle, &temp);
    339         }
    340         if (easHandle) {
    341             EAS_CloseFile(easData, easHandle);
    342         }
    343         if (easData) {
    344             EAS_Shutdown(easData);
    345         }
    346 
    347         if (result != EAS_SUCCESS) {
    348             return ERROR_EAS_FAILURE;
    349         }
    350 
    351         // cache successful result
    352         mDuration = *duration = int(temp);
    353     }
    354 
    355     return NO_ERROR;
    356 }
    357 
    358 status_t MidiFile::release()
    359 {
    360     ALOGV("MidiFile::release");
    361     Mutex::Autolock l(mMutex);
    362     reset_nosync();
    363 
    364     // wait for render thread to exit
    365     mExit = true;
    366     mCondition.signal();
    367 
    368     // wait for thread to exit
    369     if (mAudioBuffer) {
    370         mCondition.wait(mMutex);
    371     }
    372 
    373     // release resources
    374     if (mEasData) {
    375         EAS_Shutdown(mEasData);
    376         mEasData = NULL;
    377     }
    378     return NO_ERROR;
    379 }
    380 
    381 status_t MidiFile::reset()
    382 {
    383     ALOGV("MidiFile::reset");
    384     Mutex::Autolock lock(mMutex);
    385     return reset_nosync();
    386 }
    387 
    388 // call only with mutex held
    389 status_t MidiFile::reset_nosync()
    390 {
    391     ALOGV("MidiFile::reset_nosync");
    392     sendEvent(MEDIA_STOPPED);
    393     // close file
    394     if (mEasHandle) {
    395         EAS_CloseFile(mEasData, mEasHandle);
    396         mEasHandle = NULL;
    397     }
    398     if (mFileLocator.path) {
    399         free((void*)mFileLocator.path);
    400         mFileLocator.path = NULL;
    401     }
    402     if (mFileLocator.fd >= 0) {
    403         close(mFileLocator.fd);
    404     }
    405     mFileLocator.fd = -1;
    406     mFileLocator.offset = 0;
    407     mFileLocator.length = 0;
    408 
    409     mPlayTime = -1;
    410     mDuration = -1;
    411     mLoop = false;
    412     mPaused = false;
    413     mRender = false;
    414     return NO_ERROR;
    415 }
    416 
    417 status_t MidiFile::setLooping(int loop)
    418 {
    419     ALOGV("MidiFile::setLooping");
    420     Mutex::Autolock lock(mMutex);
    421     if (!mEasHandle) {
    422         return ERROR_NOT_OPEN;
    423     }
    424     loop = loop ? -1 : 0;
    425     if (EAS_SetRepeat(mEasData, mEasHandle, loop) != EAS_SUCCESS) {
    426         return ERROR_EAS_FAILURE;
    427     }
    428     return NO_ERROR;
    429 }
    430 
    431 status_t MidiFile::createOutputTrack() {
    432     if (mAudioSink->open(pLibConfig->sampleRate, pLibConfig->numChannels,
    433             CHANNEL_MASK_USE_CHANNEL_ORDER, AUDIO_FORMAT_PCM_16_BIT, 2 /*bufferCount*/) != NO_ERROR) {
    434         ALOGE("mAudioSink open failed");
    435         return ERROR_OPEN_FAILED;
    436     }
    437     return NO_ERROR;
    438 }
    439 
    440 int MidiFile::render() {
    441     EAS_RESULT result = EAS_FAILURE;
    442     EAS_I32 count;
    443     int temp;
    444     bool audioStarted = false;
    445 
    446     ALOGV("MidiFile::render");
    447 
    448     // allocate render buffer
    449     mAudioBuffer = new EAS_PCM[pLibConfig->mixBufferSize * pLibConfig->numChannels * NUM_BUFFERS];
    450     if (!mAudioBuffer) {
    451         ALOGE("mAudioBuffer allocate failed");
    452         goto threadExit;
    453     }
    454 
    455     // signal main thread that we started
    456     {
    457         Mutex::Autolock l(mMutex);
    458         mTid = gettid();
    459         ALOGV("render thread(%d) signal", mTid);
    460         mCondition.signal();
    461     }
    462 
    463     while (1) {
    464         mMutex.lock();
    465 
    466         // nothing to render, wait for client thread to wake us up
    467         while (!mRender && !mExit)
    468         {
    469             ALOGV("MidiFile::render - signal wait");
    470             mCondition.wait(mMutex);
    471             ALOGV("MidiFile::render - signal rx'd");
    472         }
    473         if (mExit) {
    474             mMutex.unlock();
    475             break;
    476         }
    477 
    478         // render midi data into the input buffer
    479         //ALOGV("MidiFile::render - rendering audio");
    480         int num_output = 0;
    481         EAS_PCM* p = mAudioBuffer;
    482         for (int i = 0; i < NUM_BUFFERS; i++) {
    483             result = EAS_Render(mEasData, p, pLibConfig->mixBufferSize, &count);
    484             if (result != EAS_SUCCESS) {
    485                 ALOGE("EAS_Render returned %ld", result);
    486             }
    487             p += count * pLibConfig->numChannels;
    488             num_output += count * pLibConfig->numChannels * sizeof(EAS_PCM);
    489         }
    490 
    491         // update playback state and position
    492         // ALOGV("MidiFile::render - updating state");
    493         EAS_GetLocation(mEasData, mEasHandle, &mPlayTime);
    494         EAS_State(mEasData, mEasHandle, &mState);
    495         mMutex.unlock();
    496 
    497         // create audio output track if necessary
    498         if (!mAudioSink->ready()) {
    499             ALOGV("MidiFile::render - create output track");
    500             if (createOutputTrack() != NO_ERROR)
    501                 goto threadExit;
    502         }
    503 
    504         // Write data to the audio hardware
    505         // ALOGV("MidiFile::render - writing to audio output");
    506         if ((temp = mAudioSink->write(mAudioBuffer, num_output)) < 0) {
    507             ALOGE("Error in writing:%d",temp);
    508             return temp;
    509         }
    510 
    511         // start audio output if necessary
    512         if (!audioStarted) {
    513             //ALOGV("MidiFile::render - starting audio");
    514             mAudioSink->start();
    515             audioStarted = true;
    516         }
    517 
    518         // still playing?
    519         if ((mState == EAS_STATE_STOPPED) || (mState == EAS_STATE_ERROR) ||
    520                 (mState == EAS_STATE_PAUSED))
    521         {
    522             switch(mState) {
    523             case EAS_STATE_STOPPED:
    524             {
    525                 ALOGV("MidiFile::render - stopped");
    526                 sendEvent(MEDIA_PLAYBACK_COMPLETE);
    527                 break;
    528             }
    529             case EAS_STATE_ERROR:
    530             {
    531                 ALOGE("MidiFile::render - error");
    532                 sendEvent(MEDIA_ERROR, MEDIA_ERROR_UNKNOWN);
    533                 break;
    534             }
    535             case EAS_STATE_PAUSED:
    536                 ALOGV("MidiFile::render - paused");
    537                 break;
    538             default:
    539                 break;
    540             }
    541             mAudioSink->stop();
    542             audioStarted = false;
    543             mRender = false;
    544         }
    545     }
    546 
    547 threadExit:
    548     mAudioSink.clear();
    549     if (mAudioBuffer) {
    550         delete [] mAudioBuffer;
    551         mAudioBuffer = NULL;
    552     }
    553     mMutex.lock();
    554     mTid = -1;
    555     mCondition.signal();
    556     mMutex.unlock();
    557     return result;
    558 }
    559 
    560 } // end namespace android
    561