MidiFile.cpp revision 0512ab559d4670c2204078470d7ef5d376811c57
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
40namespace android {
41
42// ----------------------------------------------------------------------------
43
44// The midi engine buffers are a bit small (128 frames), so we batch them up
45static const int NUM_BUFFERS = 4;
46
47// TODO: Determine appropriate return codes
48static status_t ERROR_NOT_OPEN = -1;
49static status_t ERROR_OPEN_FAILED = -2;
50static status_t ERROR_EAS_FAILURE = -3;
51static status_t ERROR_ALLOCATE_FAILED = -4;
52
53static const S_EAS_LIB_CONFIG* pLibConfig = NULL;
54
55MidiFile::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    LOGV("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        LOGE("EAS library/header mismatch");
73        goto Failed;
74    }
75
76    // initialize EAS library
77    if (EAS_Init(&mEasData) != EAS_SUCCESS) {
78        LOGE("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        createThreadEtc(renderThread, this, "midithread", ANDROID_PRIORITY_AUDIO);
90        mCondition.wait(mMutex);
91        LOGV("thread started");
92    }
93
94    // indicate success
95    if (mTid > 0) {
96        LOGV(" render thread(%d) started", mTid);
97        mState = EAS_STATE_READY;
98    }
99
100Failed:
101    return;
102}
103
104status_t MidiFile::initCheck()
105{
106    if (mState == EAS_STATE_ERROR) return ERROR_EAS_FAILURE;
107    return NO_ERROR;
108}
109
110MidiFile::~MidiFile() {
111    LOGV("MidiFile destructor");
112    release();
113}
114
115status_t MidiFile::setDataSource(
116        const char* path, const KeyedVector<String8, String8> *) {
117    LOGV("MidiFile::setDataSource url=%s", path);
118    Mutex::Autolock lock(mMutex);
119
120    // file still open?
121    if (mEasHandle) {
122        reset_nosync();
123    }
124
125    // open file and set paused state
126    mFileLocator.path = strdup(path);
127    mFileLocator.fd = -1;
128    mFileLocator.offset = 0;
129    mFileLocator.length = 0;
130    EAS_RESULT result = EAS_OpenFile(mEasData, &mFileLocator, &mEasHandle);
131    if (result == EAS_SUCCESS) {
132        updateState();
133    }
134
135    if (result != EAS_SUCCESS) {
136        LOGE("EAS_OpenFile failed: [%d]", (int)result);
137        mState = EAS_STATE_ERROR;
138        return ERROR_OPEN_FAILED;
139    }
140
141    mState = EAS_STATE_OPEN;
142    mPlayTime = 0;
143    return NO_ERROR;
144}
145
146status_t MidiFile::setDataSource(int fd, int64_t offset, int64_t length)
147{
148    LOGV("MidiFile::setDataSource fd=%d", fd);
149    Mutex::Autolock lock(mMutex);
150
151    // file still open?
152    if (mEasHandle) {
153        reset_nosync();
154    }
155
156    // open file and set paused state
157    mFileLocator.fd = dup(fd);
158    mFileLocator.offset = offset;
159    mFileLocator.length = length;
160    EAS_RESULT result = EAS_OpenFile(mEasData, &mFileLocator, &mEasHandle);
161    updateState();
162
163    if (result != EAS_SUCCESS) {
164        LOGE("EAS_OpenFile failed: [%d]", (int)result);
165        mState = EAS_STATE_ERROR;
166        return ERROR_OPEN_FAILED;
167    }
168
169    mState = EAS_STATE_OPEN;
170    mPlayTime = 0;
171    return NO_ERROR;
172}
173
174status_t MidiFile::prepare()
175{
176    LOGV("MidiFile::prepare");
177    Mutex::Autolock lock(mMutex);
178    if (!mEasHandle) {
179        return ERROR_NOT_OPEN;
180    }
181    EAS_RESULT result;
182    if ((result = EAS_Prepare(mEasData, mEasHandle)) != EAS_SUCCESS) {
183        LOGE("EAS_Prepare failed: [%ld]", result);
184        return ERROR_EAS_FAILURE;
185    }
186    updateState();
187    return NO_ERROR;
188}
189
190status_t MidiFile::prepareAsync()
191{
192    LOGV("MidiFile::prepareAsync");
193    status_t ret = prepare();
194
195    // don't hold lock during callback
196    if (ret == NO_ERROR) {
197        sendEvent(MEDIA_PREPARED);
198    } else {
199        sendEvent(MEDIA_ERROR, MEDIA_ERROR_UNKNOWN, ret);
200    }
201    return ret;
202}
203
204status_t MidiFile::start()
205{
206    LOGV("MidiFile::start");
207    Mutex::Autolock lock(mMutex);
208    if (!mEasHandle) {
209        return ERROR_NOT_OPEN;
210    }
211
212    // resuming after pause?
213    if (mPaused) {
214        if (EAS_Resume(mEasData, mEasHandle) != EAS_SUCCESS) {
215            return ERROR_EAS_FAILURE;
216        }
217        mPaused = false;
218        updateState();
219    }
220
221    mRender = true;
222
223    // wake up render thread
224    LOGV("  wakeup render thread");
225    mCondition.signal();
226    return NO_ERROR;
227}
228
229status_t MidiFile::stop()
230{
231    LOGV("MidiFile::stop");
232    Mutex::Autolock lock(mMutex);
233    if (!mEasHandle) {
234        return ERROR_NOT_OPEN;
235    }
236    if (!mPaused && (mState != EAS_STATE_STOPPED)) {
237        EAS_RESULT result = EAS_Pause(mEasData, mEasHandle);
238        if (result != EAS_SUCCESS) {
239            LOGE("EAS_Pause returned error %ld", result);
240            return ERROR_EAS_FAILURE;
241        }
242    }
243    mPaused = false;
244    return NO_ERROR;
245}
246
247status_t MidiFile::seekTo(int position)
248{
249    LOGV("MidiFile::seekTo %d", position);
250    // hold lock during EAS calls
251    {
252        Mutex::Autolock lock(mMutex);
253        if (!mEasHandle) {
254            return ERROR_NOT_OPEN;
255        }
256        EAS_RESULT result;
257        if ((result = EAS_Locate(mEasData, mEasHandle, position, false))
258                != EAS_SUCCESS)
259        {
260            LOGE("EAS_Locate returned %ld", result);
261            return ERROR_EAS_FAILURE;
262        }
263        EAS_GetLocation(mEasData, mEasHandle, &mPlayTime);
264    }
265    sendEvent(MEDIA_SEEK_COMPLETE);
266    return NO_ERROR;
267}
268
269status_t MidiFile::pause()
270{
271    LOGV("MidiFile::pause");
272    Mutex::Autolock lock(mMutex);
273    if (!mEasHandle) {
274        return ERROR_NOT_OPEN;
275    }
276    if ((mState == EAS_STATE_PAUSING) || (mState == EAS_STATE_PAUSED)) return NO_ERROR;
277    if (EAS_Pause(mEasData, mEasHandle) != EAS_SUCCESS) {
278        return ERROR_EAS_FAILURE;
279    }
280    mPaused = true;
281    return NO_ERROR;
282}
283
284bool MidiFile::isPlaying()
285{
286    LOGV("MidiFile::isPlaying, mState=%d", int(mState));
287    if (!mEasHandle || mPaused) return false;
288    return (mState == EAS_STATE_PLAY);
289}
290
291status_t MidiFile::getCurrentPosition(int* position)
292{
293    LOGV("MidiFile::getCurrentPosition");
294    if (!mEasHandle) {
295        LOGE("getCurrentPosition(): file not open");
296        return ERROR_NOT_OPEN;
297    }
298    if (mPlayTime < 0) {
299        LOGE("getCurrentPosition(): mPlayTime = %ld", mPlayTime);
300        return ERROR_EAS_FAILURE;
301    }
302    *position = mPlayTime;
303    return NO_ERROR;
304}
305
306status_t MidiFile::getDuration(int* duration)
307{
308
309    LOGV("MidiFile::getDuration");
310    {
311        Mutex::Autolock lock(mMutex);
312        if (!mEasHandle) return ERROR_NOT_OPEN;
313        *duration = mDuration;
314    }
315
316    // if no duration cached, get the duration
317    // don't need a lock here because we spin up a new engine
318    if (*duration < 0) {
319        EAS_I32 temp;
320        EAS_DATA_HANDLE easData = NULL;
321        EAS_HANDLE easHandle = NULL;
322        EAS_RESULT result = EAS_Init(&easData);
323        if (result == EAS_SUCCESS) {
324            result = EAS_OpenFile(easData, &mFileLocator, &easHandle);
325        }
326        if (result == EAS_SUCCESS) {
327            result = EAS_Prepare(easData, easHandle);
328        }
329        if (result == EAS_SUCCESS) {
330            result = EAS_ParseMetaData(easData, easHandle, &temp);
331        }
332        if (easHandle) {
333            EAS_CloseFile(easData, easHandle);
334        }
335        if (easData) {
336            EAS_Shutdown(easData);
337        }
338
339        if (result != EAS_SUCCESS) {
340            return ERROR_EAS_FAILURE;
341        }
342
343        // cache successful result
344        mDuration = *duration = int(temp);
345    }
346
347    return NO_ERROR;
348}
349
350status_t MidiFile::release()
351{
352    LOGV("MidiFile::release");
353    Mutex::Autolock l(mMutex);
354    reset_nosync();
355
356    // wait for render thread to exit
357    mExit = true;
358    mCondition.signal();
359
360    // wait for thread to exit
361    if (mAudioBuffer) {
362        mCondition.wait(mMutex);
363    }
364
365    // release resources
366    if (mEasData) {
367        EAS_Shutdown(mEasData);
368        mEasData = NULL;
369    }
370    return NO_ERROR;
371}
372
373status_t MidiFile::reset()
374{
375    LOGV("MidiFile::reset");
376    Mutex::Autolock lock(mMutex);
377    return reset_nosync();
378}
379
380// call only with mutex held
381status_t MidiFile::reset_nosync()
382{
383    LOGV("MidiFile::reset_nosync");
384    // close file
385    if (mEasHandle) {
386        EAS_CloseFile(mEasData, mEasHandle);
387        mEasHandle = NULL;
388    }
389    if (mFileLocator.path) {
390        free((void*)mFileLocator.path);
391        mFileLocator.path = NULL;
392    }
393    if (mFileLocator.fd >= 0) {
394        close(mFileLocator.fd);
395    }
396    mFileLocator.fd = -1;
397    mFileLocator.offset = 0;
398    mFileLocator.length = 0;
399
400    mPlayTime = -1;
401    mDuration = -1;
402    mLoop = false;
403    mPaused = false;
404    mRender = false;
405    return NO_ERROR;
406}
407
408status_t MidiFile::setLooping(int loop)
409{
410    LOGV("MidiFile::setLooping");
411    Mutex::Autolock lock(mMutex);
412    if (!mEasHandle) {
413        return ERROR_NOT_OPEN;
414    }
415    loop = loop ? -1 : 0;
416    if (EAS_SetRepeat(mEasData, mEasHandle, loop) != EAS_SUCCESS) {
417        return ERROR_EAS_FAILURE;
418    }
419    return NO_ERROR;
420}
421
422status_t MidiFile::createOutputTrack() {
423    if (mAudioSink->open(pLibConfig->sampleRate, pLibConfig->numChannels, AUDIO_FORMAT_PCM_16_BIT, 2) != NO_ERROR) {
424        LOGE("mAudioSink open failed");
425        return ERROR_OPEN_FAILED;
426    }
427    return NO_ERROR;
428}
429
430int MidiFile::renderThread(void* p) {
431
432    return ((MidiFile*)p)->render();
433}
434
435int MidiFile::render() {
436    EAS_RESULT result = EAS_FAILURE;
437    EAS_I32 count;
438    int temp;
439    bool audioStarted = false;
440
441    LOGV("MidiFile::render");
442
443    // allocate render buffer
444    mAudioBuffer = new EAS_PCM[pLibConfig->mixBufferSize * pLibConfig->numChannels * NUM_BUFFERS];
445    if (!mAudioBuffer) {
446        LOGE("mAudioBuffer allocate failed");
447        goto threadExit;
448    }
449
450    // signal main thread that we started
451    {
452        Mutex::Autolock l(mMutex);
453        mTid = gettid();
454        LOGV("render thread(%d) signal", mTid);
455        mCondition.signal();
456    }
457
458    while (1) {
459        mMutex.lock();
460
461        // nothing to render, wait for client thread to wake us up
462        while (!mRender && !mExit)
463        {
464            LOGV("MidiFile::render - signal wait");
465            mCondition.wait(mMutex);
466            LOGV("MidiFile::render - signal rx'd");
467        }
468        if (mExit) {
469            mMutex.unlock();
470            break;
471        }
472
473        // render midi data into the input buffer
474        //LOGV("MidiFile::render - rendering audio");
475        int num_output = 0;
476        EAS_PCM* p = mAudioBuffer;
477        for (int i = 0; i < NUM_BUFFERS; i++) {
478            result = EAS_Render(mEasData, p, pLibConfig->mixBufferSize, &count);
479            if (result != EAS_SUCCESS) {
480                LOGE("EAS_Render returned %ld", result);
481            }
482            p += count * pLibConfig->numChannels;
483            num_output += count * pLibConfig->numChannels * sizeof(EAS_PCM);
484        }
485
486        // update playback state and position
487        // LOGV("MidiFile::render - updating state");
488        EAS_GetLocation(mEasData, mEasHandle, &mPlayTime);
489        EAS_State(mEasData, mEasHandle, &mState);
490        mMutex.unlock();
491
492        // create audio output track if necessary
493        if (!mAudioSink->ready()) {
494            LOGV("MidiFile::render - create output track");
495            if (createOutputTrack() != NO_ERROR)
496                goto threadExit;
497        }
498
499        // Write data to the audio hardware
500        // LOGV("MidiFile::render - writing to audio output");
501        if ((temp = mAudioSink->write(mAudioBuffer, num_output)) < 0) {
502            LOGE("Error in writing:%d",temp);
503            return temp;
504        }
505
506        // start audio output if necessary
507        if (!audioStarted) {
508            //LOGV("MidiFile::render - starting audio");
509            mAudioSink->start();
510            audioStarted = true;
511        }
512
513        // still playing?
514        if ((mState == EAS_STATE_STOPPED) || (mState == EAS_STATE_ERROR) ||
515                (mState == EAS_STATE_PAUSED))
516        {
517            switch(mState) {
518            case EAS_STATE_STOPPED:
519            {
520                LOGV("MidiFile::render - stopped");
521                sendEvent(MEDIA_PLAYBACK_COMPLETE);
522                break;
523            }
524            case EAS_STATE_ERROR:
525            {
526                LOGE("MidiFile::render - error");
527                sendEvent(MEDIA_ERROR, MEDIA_ERROR_UNKNOWN);
528                break;
529            }
530            case EAS_STATE_PAUSED:
531                LOGV("MidiFile::render - paused");
532                break;
533            default:
534                break;
535            }
536            mAudioSink->stop();
537            audioStarted = false;
538            mRender = false;
539        }
540    }
541
542threadExit:
543    mAudioSink.clear();
544    if (mAudioBuffer) {
545        delete [] mAudioBuffer;
546        mAudioBuffer = NULL;
547    }
548    mMutex.lock();
549    mTid = -1;
550    mCondition.signal();
551    mMutex.unlock();
552    return result;
553}
554
555} // end namespace android
556