MidiFile.cpp revision cbaffcffee6418d678806e63097c19fe26d48fe0
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    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
101Failed:
102    return;
103}
104
105status_t MidiFile::initCheck()
106{
107    if (mState == EAS_STATE_ERROR) return ERROR_EAS_FAILURE;
108    return NO_ERROR;
109}
110
111MidiFile::~MidiFile() {
112    ALOGV("MidiFile destructor");
113    release();
114}
115
116status_t MidiFile::setDataSource(
117        const char* path, const KeyedVector<String8, String8> *) {
118    ALOGV("MidiFile::setDataSource url=%s", path);
119    Mutex::Autolock lock(mMutex);
120
121    // file still open?
122    if (mEasHandle) {
123        reset_nosync();
124    }
125
126    // open file and set paused state
127    mFileLocator.path = strdup(path);
128    mFileLocator.fd = -1;
129    mFileLocator.offset = 0;
130    mFileLocator.length = 0;
131    EAS_RESULT result = EAS_OpenFile(mEasData, &mFileLocator, &mEasHandle);
132    if (result == EAS_SUCCESS) {
133        updateState();
134    }
135
136    if (result != EAS_SUCCESS) {
137        ALOGE("EAS_OpenFile failed: [%d]", (int)result);
138        mState = EAS_STATE_ERROR;
139        return ERROR_OPEN_FAILED;
140    }
141
142    mState = EAS_STATE_OPEN;
143    mPlayTime = 0;
144    return NO_ERROR;
145}
146
147status_t MidiFile::setDataSource(int fd, int64_t offset, int64_t length)
148{
149    ALOGV("MidiFile::setDataSource fd=%d", fd);
150    Mutex::Autolock lock(mMutex);
151
152    // file still open?
153    if (mEasHandle) {
154        reset_nosync();
155    }
156
157    // open file and set paused state
158    mFileLocator.fd = dup(fd);
159    mFileLocator.offset = offset;
160    mFileLocator.length = length;
161    EAS_RESULT result = EAS_OpenFile(mEasData, &mFileLocator, &mEasHandle);
162    updateState();
163
164    if (result != EAS_SUCCESS) {
165        ALOGE("EAS_OpenFile failed: [%d]", (int)result);
166        mState = EAS_STATE_ERROR;
167        return ERROR_OPEN_FAILED;
168    }
169
170    mState = EAS_STATE_OPEN;
171    mPlayTime = 0;
172    return NO_ERROR;
173}
174
175status_t MidiFile::prepare()
176{
177    ALOGV("MidiFile::prepare");
178    Mutex::Autolock lock(mMutex);
179    if (!mEasHandle) {
180        return ERROR_NOT_OPEN;
181    }
182    EAS_RESULT result;
183    if ((result = EAS_Prepare(mEasData, mEasHandle)) != EAS_SUCCESS) {
184        ALOGE("EAS_Prepare failed: [%ld]", result);
185        return ERROR_EAS_FAILURE;
186    }
187    updateState();
188    return NO_ERROR;
189}
190
191status_t MidiFile::prepareAsync()
192{
193    ALOGV("MidiFile::prepareAsync");
194    status_t ret = prepare();
195
196    // don't hold lock during callback
197    if (ret == NO_ERROR) {
198        sendEvent(MEDIA_PREPARED);
199    } else {
200        sendEvent(MEDIA_ERROR, MEDIA_ERROR_UNKNOWN, ret);
201    }
202    return ret;
203}
204
205status_t MidiFile::start()
206{
207    ALOGV("MidiFile::start");
208    Mutex::Autolock lock(mMutex);
209    if (!mEasHandle) {
210        return ERROR_NOT_OPEN;
211    }
212
213    // resuming after pause?
214    if (mPaused) {
215        if (EAS_Resume(mEasData, mEasHandle) != EAS_SUCCESS) {
216            return ERROR_EAS_FAILURE;
217        }
218        mPaused = false;
219        updateState();
220    }
221
222    mRender = true;
223    if (mState == EAS_STATE_PLAY) {
224        sendEvent(MEDIA_STARTED);
225    }
226
227    // wake up render thread
228    ALOGV("  wakeup render thread");
229    mCondition.signal();
230    return NO_ERROR;
231}
232
233status_t MidiFile::stop()
234{
235    ALOGV("MidiFile::stop");
236    Mutex::Autolock lock(mMutex);
237    if (!mEasHandle) {
238        return ERROR_NOT_OPEN;
239    }
240    if (!mPaused && (mState != EAS_STATE_STOPPED)) {
241        EAS_RESULT result = EAS_Pause(mEasData, mEasHandle);
242        if (result != EAS_SUCCESS) {
243            ALOGE("EAS_Pause returned error %ld", result);
244            return ERROR_EAS_FAILURE;
245        }
246    }
247    mPaused = false;
248    sendEvent(MEDIA_STOPPED);
249    return NO_ERROR;
250}
251
252status_t MidiFile::seekTo(int position)
253{
254    ALOGV("MidiFile::seekTo %d", position);
255    // hold lock during EAS calls
256    {
257        Mutex::Autolock lock(mMutex);
258        if (!mEasHandle) {
259            return ERROR_NOT_OPEN;
260        }
261        EAS_RESULT result;
262        if ((result = EAS_Locate(mEasData, mEasHandle, position, false))
263                != EAS_SUCCESS)
264        {
265            ALOGE("EAS_Locate returned %ld", result);
266            return ERROR_EAS_FAILURE;
267        }
268        EAS_GetLocation(mEasData, mEasHandle, &mPlayTime);
269    }
270    sendEvent(MEDIA_SEEK_COMPLETE);
271    return NO_ERROR;
272}
273
274status_t MidiFile::pause()
275{
276    ALOGV("MidiFile::pause");
277    Mutex::Autolock lock(mMutex);
278    if (!mEasHandle) {
279        return ERROR_NOT_OPEN;
280    }
281    if ((mState == EAS_STATE_PAUSING) || (mState == EAS_STATE_PAUSED)) return NO_ERROR;
282    if (EAS_Pause(mEasData, mEasHandle) != EAS_SUCCESS) {
283        return ERROR_EAS_FAILURE;
284    }
285    mPaused = true;
286    sendEvent(MEDIA_PAUSED);
287    return NO_ERROR;
288}
289
290bool MidiFile::isPlaying()
291{
292    ALOGV("MidiFile::isPlaying, mState=%d", int(mState));
293    if (!mEasHandle || mPaused) return false;
294    return (mState == EAS_STATE_PLAY);
295}
296
297status_t MidiFile::getCurrentPosition(int* position)
298{
299    ALOGV("MidiFile::getCurrentPosition");
300    if (!mEasHandle) {
301        ALOGE("getCurrentPosition(): file not open");
302        return ERROR_NOT_OPEN;
303    }
304    if (mPlayTime < 0) {
305        ALOGE("getCurrentPosition(): mPlayTime = %ld", mPlayTime);
306        return ERROR_EAS_FAILURE;
307    }
308    *position = mPlayTime;
309    return NO_ERROR;
310}
311
312status_t MidiFile::getDuration(int* duration)
313{
314
315    ALOGV("MidiFile::getDuration");
316    {
317        Mutex::Autolock lock(mMutex);
318        if (!mEasHandle) return ERROR_NOT_OPEN;
319        *duration = mDuration;
320    }
321
322    // if no duration cached, get the duration
323    // don't need a lock here because we spin up a new engine
324    if (*duration < 0) {
325        EAS_I32 temp;
326        EAS_DATA_HANDLE easData = NULL;
327        EAS_HANDLE easHandle = NULL;
328        EAS_RESULT result = EAS_Init(&easData);
329        if (result == EAS_SUCCESS) {
330            result = EAS_OpenFile(easData, &mFileLocator, &easHandle);
331        }
332        if (result == EAS_SUCCESS) {
333            result = EAS_Prepare(easData, easHandle);
334        }
335        if (result == EAS_SUCCESS) {
336            result = EAS_ParseMetaData(easData, easHandle, &temp);
337        }
338        if (easHandle) {
339            EAS_CloseFile(easData, easHandle);
340        }
341        if (easData) {
342            EAS_Shutdown(easData);
343        }
344
345        if (result != EAS_SUCCESS) {
346            return ERROR_EAS_FAILURE;
347        }
348
349        // cache successful result
350        mDuration = *duration = int(temp);
351    }
352
353    return NO_ERROR;
354}
355
356status_t MidiFile::release()
357{
358    ALOGV("MidiFile::release");
359    Mutex::Autolock l(mMutex);
360    reset_nosync();
361
362    // wait for render thread to exit
363    mExit = true;
364    mCondition.signal();
365
366    // wait for thread to exit
367    if (mAudioBuffer) {
368        mCondition.wait(mMutex);
369    }
370
371    // release resources
372    if (mEasData) {
373        EAS_Shutdown(mEasData);
374        mEasData = NULL;
375    }
376    return NO_ERROR;
377}
378
379status_t MidiFile::reset()
380{
381    ALOGV("MidiFile::reset");
382    Mutex::Autolock lock(mMutex);
383    return reset_nosync();
384}
385
386// call only with mutex held
387status_t MidiFile::reset_nosync()
388{
389    ALOGV("MidiFile::reset_nosync");
390    sendEvent(MEDIA_STOPPED);
391    // close file
392    if (mEasHandle) {
393        EAS_CloseFile(mEasData, mEasHandle);
394        mEasHandle = NULL;
395    }
396    if (mFileLocator.path) {
397        free((void*)mFileLocator.path);
398        mFileLocator.path = NULL;
399    }
400    if (mFileLocator.fd >= 0) {
401        close(mFileLocator.fd);
402    }
403    mFileLocator.fd = -1;
404    mFileLocator.offset = 0;
405    mFileLocator.length = 0;
406
407    mPlayTime = -1;
408    mDuration = -1;
409    mLoop = false;
410    mPaused = false;
411    mRender = false;
412    return NO_ERROR;
413}
414
415status_t MidiFile::setLooping(int loop)
416{
417    ALOGV("MidiFile::setLooping");
418    Mutex::Autolock lock(mMutex);
419    if (!mEasHandle) {
420        return ERROR_NOT_OPEN;
421    }
422    loop = loop ? -1 : 0;
423    if (EAS_SetRepeat(mEasData, mEasHandle, loop) != EAS_SUCCESS) {
424        return ERROR_EAS_FAILURE;
425    }
426    return NO_ERROR;
427}
428
429status_t MidiFile::createOutputTrack() {
430    if (mAudioSink->open(pLibConfig->sampleRate, pLibConfig->numChannels,
431            CHANNEL_MASK_USE_CHANNEL_ORDER, AUDIO_FORMAT_PCM_16_BIT, 2 /*bufferCount*/) != NO_ERROR) {
432        ALOGE("mAudioSink open failed");
433        return ERROR_OPEN_FAILED;
434    }
435    return NO_ERROR;
436}
437
438int MidiFile::render() {
439    EAS_RESULT result = EAS_FAILURE;
440    EAS_I32 count;
441    int temp;
442    bool audioStarted = false;
443
444    ALOGV("MidiFile::render");
445
446    // allocate render buffer
447    mAudioBuffer = new EAS_PCM[pLibConfig->mixBufferSize * pLibConfig->numChannels * NUM_BUFFERS];
448    if (!mAudioBuffer) {
449        ALOGE("mAudioBuffer allocate failed");
450        goto threadExit;
451    }
452
453    // signal main thread that we started
454    {
455        Mutex::Autolock l(mMutex);
456        mTid = gettid();
457        ALOGV("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            ALOGV("MidiFile::render - signal wait");
468            mCondition.wait(mMutex);
469            ALOGV("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        //ALOGV("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                ALOGE("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        // ALOGV("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            ALOGV("MidiFile::render - create output track");
498            if (createOutputTrack() != NO_ERROR)
499                goto threadExit;
500        }
501
502        // Write data to the audio hardware
503        // ALOGV("MidiFile::render - writing to audio output");
504        if ((temp = mAudioSink->write(mAudioBuffer, num_output)) < 0) {
505            ALOGE("Error in writing:%d",temp);
506            return temp;
507        }
508
509        // start audio output if necessary
510        if (!audioStarted) {
511            //ALOGV("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                ALOGV("MidiFile::render - stopped");
524                sendEvent(MEDIA_PLAYBACK_COMPLETE);
525                break;
526            }
527            case EAS_STATE_ERROR:
528            {
529                ALOGE("MidiFile::render - error");
530                sendEvent(MEDIA_ERROR, MEDIA_ERROR_UNKNOWN);
531                break;
532            }
533            case EAS_STATE_PAUSED:
534                ALOGV("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