MidiFile.cpp revision 89fa4ad53f2f4d57adbc97ae1149fc00c9b6f3c5
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
33#include "MidiFile.h"
34
35#ifdef HAVE_GETTID
36static pid_t myTid() { return gettid(); }
37#else
38static pid_t myTid() { return getpid(); }
39#endif
40
41// ----------------------------------------------------------------------------
42
43namespace android {
44
45// ----------------------------------------------------------------------------
46
47// The midi engine buffers are a bit small (128 frames), so we batch them up
48static const int NUM_BUFFERS = 4;
49
50// TODO: Determine appropriate return codes
51static status_t ERROR_NOT_OPEN = -1;
52static status_t ERROR_OPEN_FAILED = -2;
53static status_t ERROR_EAS_FAILURE = -3;
54static status_t ERROR_ALLOCATE_FAILED = -4;
55
56static const S_EAS_LIB_CONFIG* pLibConfig = NULL;
57
58MidiFile::MidiFile() :
59    mEasData(NULL), mEasHandle(NULL), mAudioBuffer(NULL),
60    mPlayTime(-1), mDuration(-1), mState(EAS_STATE_ERROR),
61    mStreamType(AudioSystem::MUSIC), mLoop(false), mExit(false),
62    mPaused(false), mRender(false), mTid(-1)
63{
64    LOGV("constructor");
65
66    mFileLocator.path = NULL;
67    mFileLocator.fd = -1;
68    mFileLocator.offset = 0;
69    mFileLocator.length = 0;
70
71    // get the library configuration and do sanity check
72    if (pLibConfig == NULL)
73        pLibConfig = EAS_Config();
74    if ((pLibConfig == NULL) || (LIB_VERSION != pLibConfig->libVersion)) {
75        LOGE("EAS library/header mismatch");
76        goto Failed;
77    }
78
79    // initialize EAS library
80    if (EAS_Init(&mEasData) != EAS_SUCCESS) {
81        LOGE("EAS_Init failed");
82        goto Failed;
83    }
84
85    // select reverb preset and enable
86    EAS_SetParameter(mEasData, EAS_MODULE_REVERB, EAS_PARAM_REVERB_PRESET, EAS_PARAM_REVERB_CHAMBER);
87    EAS_SetParameter(mEasData, EAS_MODULE_REVERB, EAS_PARAM_REVERB_BYPASS, EAS_FALSE);
88
89    // create playback thread
90    {
91        Mutex::Autolock l(mMutex);
92        createThreadEtc(renderThread, this, "midithread");
93        mCondition.wait(mMutex);
94        LOGV("thread started");
95    }
96
97    // indicate success
98    if (mTid > 0) {
99        LOGV(" render thread(%d) started", mTid);
100        mState = EAS_STATE_READY;
101    }
102
103Failed:
104    return;
105}
106
107status_t MidiFile::initCheck()
108{
109    if (mState == EAS_STATE_ERROR) return ERROR_EAS_FAILURE;
110    return NO_ERROR;
111}
112
113MidiFile::~MidiFile() {
114    LOGV("MidiFile destructor");
115    release();
116}
117
118status_t MidiFile::setDataSource(const char* path)
119{
120    LOGV("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        LOGE("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
149status_t MidiFile::setDataSource(int fd, int64_t offset, int64_t length)
150{
151    LOGV("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        LOGE("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
177status_t MidiFile::prepare()
178{
179    LOGV("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        LOGE("EAS_Prepare failed: [%ld]", result);
187        return ERROR_EAS_FAILURE;
188    }
189    updateState();
190    return NO_ERROR;
191}
192
193status_t MidiFile::prepareAsync()
194{
195    LOGV("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
207status_t MidiFile::start()
208{
209    LOGV("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
226    // wake up render thread
227    LOGV("  wakeup render thread");
228    mCondition.signal();
229    return NO_ERROR;
230}
231
232status_t MidiFile::stop()
233{
234    LOGV("MidiFile::stop");
235    Mutex::Autolock lock(mMutex);
236    if (!mEasHandle) {
237        return ERROR_NOT_OPEN;
238    }
239    if (!mPaused && (mState != EAS_STATE_STOPPED)) {
240        EAS_RESULT result = EAS_Pause(mEasData, mEasHandle);
241        if (result != EAS_SUCCESS) {
242            LOGE("EAS_Pause returned error %ld", result);
243            return ERROR_EAS_FAILURE;
244        }
245    }
246    mPaused = false;
247    return NO_ERROR;
248}
249
250status_t MidiFile::seekTo(int position)
251{
252    LOGV("MidiFile::seekTo %d", position);
253    // hold lock during EAS calls
254    {
255        Mutex::Autolock lock(mMutex);
256        if (!mEasHandle) {
257            return ERROR_NOT_OPEN;
258        }
259        EAS_RESULT result;
260        if ((result = EAS_Locate(mEasData, mEasHandle, position, false))
261                != EAS_SUCCESS)
262        {
263            LOGE("EAS_Locate returned %ld", result);
264            return ERROR_EAS_FAILURE;
265        }
266        EAS_GetLocation(mEasData, mEasHandle, &mPlayTime);
267    }
268    sendEvent(MEDIA_SEEK_COMPLETE);
269    return NO_ERROR;
270}
271
272status_t MidiFile::pause()
273{
274    LOGV("MidiFile::pause");
275    Mutex::Autolock lock(mMutex);
276    if (!mEasHandle) {
277        return ERROR_NOT_OPEN;
278    }
279    if ((mState == EAS_STATE_PAUSING) || (mState == EAS_STATE_PAUSED)) return NO_ERROR;
280    if (EAS_Pause(mEasData, mEasHandle) != EAS_SUCCESS) {
281        return ERROR_EAS_FAILURE;
282    }
283    mPaused = true;
284    return NO_ERROR;
285}
286
287bool MidiFile::isPlaying()
288{
289    LOGV("MidiFile::isPlaying, mState=%d", int(mState));
290    if (!mEasHandle || mPaused) return false;
291    return (mState == EAS_STATE_PLAY);
292}
293
294status_t MidiFile::getCurrentPosition(int* position)
295{
296    LOGV("MidiFile::getCurrentPosition");
297    if (!mEasHandle) {
298        LOGE("getCurrentPosition(): file not open");
299        return ERROR_NOT_OPEN;
300    }
301    if (mPlayTime < 0) {
302        LOGE("getCurrentPosition(): mPlayTime = %ld", mPlayTime);
303        return ERROR_EAS_FAILURE;
304    }
305    *position = mPlayTime;
306    return NO_ERROR;
307}
308
309status_t MidiFile::getDuration(int* duration)
310{
311
312    LOGV("MidiFile::getDuration");
313    {
314        Mutex::Autolock lock(mMutex);
315        if (!mEasHandle) return ERROR_NOT_OPEN;
316        *duration = mDuration;
317    }
318
319    // if no duration cached, get the duration
320    // don't need a lock here because we spin up a new engine
321    if (*duration < 0) {
322        EAS_I32 temp;
323        EAS_DATA_HANDLE easData = NULL;
324        EAS_HANDLE easHandle = NULL;
325        EAS_RESULT result = EAS_Init(&easData);
326        if (result == EAS_SUCCESS) {
327            result = EAS_OpenFile(easData, &mFileLocator, &easHandle);
328        }
329        if (result == EAS_SUCCESS) {
330            result = EAS_Prepare(easData, easHandle);
331        }
332        if (result == EAS_SUCCESS) {
333            result = EAS_ParseMetaData(easData, easHandle, &temp);
334        }
335        if (easHandle) {
336            EAS_CloseFile(easData, easHandle);
337        }
338        if (easData) {
339            EAS_Shutdown(easData);
340        }
341
342        if (result != EAS_SUCCESS) {
343            return ERROR_EAS_FAILURE;
344        }
345
346        // cache successful result
347        mDuration = *duration = int(temp);
348    }
349
350    return NO_ERROR;
351}
352
353status_t MidiFile::release()
354{
355    LOGV("MidiFile::release");
356    Mutex::Autolock l(mMutex);
357    reset_nosync();
358
359    // wait for render thread to exit
360    mExit = true;
361    mCondition.signal();
362
363    // wait for thread to exit
364    if (mAudioBuffer) {
365        mCondition.wait(mMutex);
366    }
367
368    // release resources
369    if (mEasData) {
370        EAS_Shutdown(mEasData);
371        mEasData = NULL;
372    }
373    return NO_ERROR;
374}
375
376status_t MidiFile::reset()
377{
378    LOGV("MidiFile::reset");
379    Mutex::Autolock lock(mMutex);
380    return reset_nosync();
381}
382
383// call only with mutex held
384status_t MidiFile::reset_nosync()
385{
386    LOGV("MidiFile::reset_nosync");
387    // close file
388    if (mEasHandle) {
389        EAS_CloseFile(mEasData, mEasHandle);
390        mEasHandle = NULL;
391    }
392    if (mFileLocator.path) {
393        free((void*)mFileLocator.path);
394        mFileLocator.path = NULL;
395    }
396    if (mFileLocator.fd >= 0) {
397        close(mFileLocator.fd);
398    }
399    mFileLocator.fd = -1;
400    mFileLocator.offset = 0;
401    mFileLocator.length = 0;
402
403    mPlayTime = -1;
404    mDuration = -1;
405    mLoop = false;
406    mPaused = false;
407    mRender = false;
408    return NO_ERROR;
409}
410
411status_t MidiFile::setLooping(int loop)
412{
413    LOGV("MidiFile::setLooping");
414    Mutex::Autolock lock(mMutex);
415    if (!mEasHandle) {
416        return ERROR_NOT_OPEN;
417    }
418    loop = loop ? -1 : 0;
419    if (EAS_SetRepeat(mEasData, mEasHandle, loop) != EAS_SUCCESS) {
420        return ERROR_EAS_FAILURE;
421    }
422    return NO_ERROR;
423}
424
425status_t MidiFile::createOutputTrack() {
426    if (mAudioSink->open(pLibConfig->sampleRate, pLibConfig->numChannels, AudioSystem::PCM_16_BIT, 2) != NO_ERROR) {
427        LOGE("mAudioSink open failed");
428        return ERROR_OPEN_FAILED;
429    }
430    return NO_ERROR;
431}
432
433int MidiFile::renderThread(void* p) {
434
435    return ((MidiFile*)p)->render();
436}
437
438int MidiFile::render() {
439    EAS_RESULT result = EAS_FAILURE;
440    EAS_I32 count;
441    int temp;
442    bool audioStarted = false;
443
444    LOGV("MidiFile::render");
445
446    // allocate render buffer
447    mAudioBuffer = new EAS_PCM[pLibConfig->mixBufferSize * pLibConfig->numChannels * NUM_BUFFERS];
448    if (!mAudioBuffer) {
449        LOGE("mAudioBuffer allocate failed");
450        goto threadExit;
451    }
452
453    // signal main thread that we started
454    {
455        Mutex::Autolock l(mMutex);
456        mTid = myTid();
457        LOGV("render thread(%d) signal", mTid);
458        mCondition.signal();
459    }
460
461    while (1) {
462        mMutex.lock();
463
464        // nothing to render, wait for client thread to wake us up
465        while (!mRender && !mExit)
466        {
467            LOGV("MidiFile::render - signal wait");
468            mCondition.wait(mMutex);
469            LOGV("MidiFile::render - signal rx'd");
470        }
471        if (mExit) {
472            mMutex.unlock();
473            break;
474        }
475
476        // render midi data into the input buffer
477        //LOGV("MidiFile::render - rendering audio");
478        int num_output = 0;
479        EAS_PCM* p = mAudioBuffer;
480        for (int i = 0; i < NUM_BUFFERS; i++) {
481            result = EAS_Render(mEasData, p, pLibConfig->mixBufferSize, &count);
482            if (result != EAS_SUCCESS) {
483                LOGE("EAS_Render returned %ld", result);
484            }
485            p += count * pLibConfig->numChannels;
486            num_output += count * pLibConfig->numChannels * sizeof(EAS_PCM);
487        }
488
489        // update playback state and position
490        // LOGV("MidiFile::render - updating state");
491        EAS_GetLocation(mEasData, mEasHandle, &mPlayTime);
492        EAS_State(mEasData, mEasHandle, &mState);
493        mMutex.unlock();
494
495        // create audio output track if necessary
496        if (!mAudioSink->ready()) {
497            LOGV("MidiFile::render - create output track");
498            if (createOutputTrack() != NO_ERROR)
499                goto threadExit;
500        }
501
502        // Write data to the audio hardware
503        // LOGV("MidiFile::render - writing to audio output");
504        if ((temp = mAudioSink->write(mAudioBuffer, num_output)) < 0) {
505            LOGE("Error in writing:%d",temp);
506            return temp;
507        }
508
509        // start audio output if necessary
510        if (!audioStarted) {
511            //LOGV("MidiFile::render - starting audio");
512            mAudioSink->start();
513            audioStarted = true;
514        }
515
516        // still playing?
517        if ((mState == EAS_STATE_STOPPED) || (mState == EAS_STATE_ERROR) ||
518                (mState == EAS_STATE_PAUSED))
519        {
520            switch(mState) {
521            case EAS_STATE_STOPPED:
522            {
523                LOGV("MidiFile::render - stopped");
524                sendEvent(MEDIA_PLAYBACK_COMPLETE);
525                break;
526            }
527            case EAS_STATE_ERROR:
528            {
529                LOGE("MidiFile::render - error");
530                sendEvent(MEDIA_ERROR, MEDIA_ERROR_UNKNOWN);
531                break;
532            }
533            case EAS_STATE_PAUSED:
534                LOGV("MidiFile::render - paused");
535                break;
536            default:
537                break;
538            }
539            mAudioSink->stop();
540            audioStarted = false;
541            mRender = false;
542        }
543    }
544
545threadExit:
546    mAudioSink.clear();
547    if (mAudioBuffer) {
548        delete [] mAudioBuffer;
549        mAudioBuffer = NULL;
550    }
551    mMutex.lock();
552    mTid = -1;
553    mCondition.signal();
554    mMutex.unlock();
555    return result;
556}
557
558} // end namespace android
559