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 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
149status_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
177status_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
193status_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
207status_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
235status_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
254status_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
276status_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
292bool 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
299status_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
314status_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
358status_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
381status_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
389status_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
417status_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
431status_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
440int 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
547threadExit:
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