• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
MidiFile()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 
initCheck()105 status_t MidiFile::initCheck()
106 {
107     if (mState == EAS_STATE_ERROR) return ERROR_EAS_FAILURE;
108     return NO_ERROR;
109 }
110 
~MidiFile()111 MidiFile::~MidiFile() {
112     ALOGV("MidiFile destructor");
113     release();
114 }
115 
setDataSource(const sp<IMediaHTTPService> &,const char * path,const KeyedVector<String8,String8> *)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 
setDataSource(int fd,int64_t offset,int64_t length)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 
prepare()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 
prepareAsync()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 
start()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 
stop()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 
seekTo(int position)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 
pause()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 
isPlaying()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 
getCurrentPosition(int * position)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 
getDuration(int * duration)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 
release()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 
reset()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
reset_nosync()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 
setLooping(int loop)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 
createOutputTrack()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 
render()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