• 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 char * path,const KeyedVector<String8,String8> *)116 status_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 
setDataSource(int fd,int64_t offset,int64_t length)147 status_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 
prepare()175 status_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 
prepareAsync()191 status_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 
start()205 status_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 
224     // wake up render thread
225     ALOGV("  wakeup render thread");
226     mCondition.signal();
227     return NO_ERROR;
228 }
229 
stop()230 status_t MidiFile::stop()
231 {
232     ALOGV("MidiFile::stop");
233     Mutex::Autolock lock(mMutex);
234     if (!mEasHandle) {
235         return ERROR_NOT_OPEN;
236     }
237     if (!mPaused && (mState != EAS_STATE_STOPPED)) {
238         EAS_RESULT result = EAS_Pause(mEasData, mEasHandle);
239         if (result != EAS_SUCCESS) {
240             ALOGE("EAS_Pause returned error %ld", result);
241             return ERROR_EAS_FAILURE;
242         }
243     }
244     mPaused = false;
245     return NO_ERROR;
246 }
247 
seekTo(int position)248 status_t MidiFile::seekTo(int position)
249 {
250     ALOGV("MidiFile::seekTo %d", position);
251     // hold lock during EAS calls
252     {
253         Mutex::Autolock lock(mMutex);
254         if (!mEasHandle) {
255             return ERROR_NOT_OPEN;
256         }
257         EAS_RESULT result;
258         if ((result = EAS_Locate(mEasData, mEasHandle, position, false))
259                 != EAS_SUCCESS)
260         {
261             ALOGE("EAS_Locate returned %ld", result);
262             return ERROR_EAS_FAILURE;
263         }
264         EAS_GetLocation(mEasData, mEasHandle, &mPlayTime);
265     }
266     sendEvent(MEDIA_SEEK_COMPLETE);
267     return NO_ERROR;
268 }
269 
pause()270 status_t MidiFile::pause()
271 {
272     ALOGV("MidiFile::pause");
273     Mutex::Autolock lock(mMutex);
274     if (!mEasHandle) {
275         return ERROR_NOT_OPEN;
276     }
277     if ((mState == EAS_STATE_PAUSING) || (mState == EAS_STATE_PAUSED)) return NO_ERROR;
278     if (EAS_Pause(mEasData, mEasHandle) != EAS_SUCCESS) {
279         return ERROR_EAS_FAILURE;
280     }
281     mPaused = true;
282     return NO_ERROR;
283 }
284 
isPlaying()285 bool MidiFile::isPlaying()
286 {
287     ALOGV("MidiFile::isPlaying, mState=%d", int(mState));
288     if (!mEasHandle || mPaused) return false;
289     return (mState == EAS_STATE_PLAY);
290 }
291 
getCurrentPosition(int * position)292 status_t MidiFile::getCurrentPosition(int* position)
293 {
294     ALOGV("MidiFile::getCurrentPosition");
295     if (!mEasHandle) {
296         ALOGE("getCurrentPosition(): file not open");
297         return ERROR_NOT_OPEN;
298     }
299     if (mPlayTime < 0) {
300         ALOGE("getCurrentPosition(): mPlayTime = %ld", mPlayTime);
301         return ERROR_EAS_FAILURE;
302     }
303     *position = mPlayTime;
304     return NO_ERROR;
305 }
306 
getDuration(int * duration)307 status_t MidiFile::getDuration(int* duration)
308 {
309 
310     ALOGV("MidiFile::getDuration");
311     {
312         Mutex::Autolock lock(mMutex);
313         if (!mEasHandle) return ERROR_NOT_OPEN;
314         *duration = mDuration;
315     }
316 
317     // if no duration cached, get the duration
318     // don't need a lock here because we spin up a new engine
319     if (*duration < 0) {
320         EAS_I32 temp;
321         EAS_DATA_HANDLE easData = NULL;
322         EAS_HANDLE easHandle = NULL;
323         EAS_RESULT result = EAS_Init(&easData);
324         if (result == EAS_SUCCESS) {
325             result = EAS_OpenFile(easData, &mFileLocator, &easHandle);
326         }
327         if (result == EAS_SUCCESS) {
328             result = EAS_Prepare(easData, easHandle);
329         }
330         if (result == EAS_SUCCESS) {
331             result = EAS_ParseMetaData(easData, easHandle, &temp);
332         }
333         if (easHandle) {
334             EAS_CloseFile(easData, easHandle);
335         }
336         if (easData) {
337             EAS_Shutdown(easData);
338         }
339 
340         if (result != EAS_SUCCESS) {
341             return ERROR_EAS_FAILURE;
342         }
343 
344         // cache successful result
345         mDuration = *duration = int(temp);
346     }
347 
348     return NO_ERROR;
349 }
350 
release()351 status_t MidiFile::release()
352 {
353     ALOGV("MidiFile::release");
354     Mutex::Autolock l(mMutex);
355     reset_nosync();
356 
357     // wait for render thread to exit
358     mExit = true;
359     mCondition.signal();
360 
361     // wait for thread to exit
362     if (mAudioBuffer) {
363         mCondition.wait(mMutex);
364     }
365 
366     // release resources
367     if (mEasData) {
368         EAS_Shutdown(mEasData);
369         mEasData = NULL;
370     }
371     return NO_ERROR;
372 }
373 
reset()374 status_t MidiFile::reset()
375 {
376     ALOGV("MidiFile::reset");
377     Mutex::Autolock lock(mMutex);
378     return reset_nosync();
379 }
380 
381 // call only with mutex held
reset_nosync()382 status_t MidiFile::reset_nosync()
383 {
384     ALOGV("MidiFile::reset_nosync");
385     // close file
386     if (mEasHandle) {
387         EAS_CloseFile(mEasData, mEasHandle);
388         mEasHandle = NULL;
389     }
390     if (mFileLocator.path) {
391         free((void*)mFileLocator.path);
392         mFileLocator.path = NULL;
393     }
394     if (mFileLocator.fd >= 0) {
395         close(mFileLocator.fd);
396     }
397     mFileLocator.fd = -1;
398     mFileLocator.offset = 0;
399     mFileLocator.length = 0;
400 
401     mPlayTime = -1;
402     mDuration = -1;
403     mLoop = false;
404     mPaused = false;
405     mRender = false;
406     return NO_ERROR;
407 }
408 
setLooping(int loop)409 status_t MidiFile::setLooping(int loop)
410 {
411     ALOGV("MidiFile::setLooping");
412     Mutex::Autolock lock(mMutex);
413     if (!mEasHandle) {
414         return ERROR_NOT_OPEN;
415     }
416     loop = loop ? -1 : 0;
417     if (EAS_SetRepeat(mEasData, mEasHandle, loop) != EAS_SUCCESS) {
418         return ERROR_EAS_FAILURE;
419     }
420     return NO_ERROR;
421 }
422 
createOutputTrack()423 status_t MidiFile::createOutputTrack() {
424     if (mAudioSink->open(pLibConfig->sampleRate, pLibConfig->numChannels,
425             CHANNEL_MASK_USE_CHANNEL_ORDER, AUDIO_FORMAT_PCM_16_BIT, 2) != NO_ERROR) {
426         ALOGE("mAudioSink open failed");
427         return ERROR_OPEN_FAILED;
428     }
429     return NO_ERROR;
430 }
431 
render()432 int MidiFile::render() {
433     EAS_RESULT result = EAS_FAILURE;
434     EAS_I32 count;
435     int temp;
436     bool audioStarted = false;
437 
438     ALOGV("MidiFile::render");
439 
440     // allocate render buffer
441     mAudioBuffer = new EAS_PCM[pLibConfig->mixBufferSize * pLibConfig->numChannels * NUM_BUFFERS];
442     if (!mAudioBuffer) {
443         ALOGE("mAudioBuffer allocate failed");
444         goto threadExit;
445     }
446 
447     // signal main thread that we started
448     {
449         Mutex::Autolock l(mMutex);
450         mTid = gettid();
451         ALOGV("render thread(%d) signal", mTid);
452         mCondition.signal();
453     }
454 
455     while (1) {
456         mMutex.lock();
457 
458         // nothing to render, wait for client thread to wake us up
459         while (!mRender && !mExit)
460         {
461             ALOGV("MidiFile::render - signal wait");
462             mCondition.wait(mMutex);
463             ALOGV("MidiFile::render - signal rx'd");
464         }
465         if (mExit) {
466             mMutex.unlock();
467             break;
468         }
469 
470         // render midi data into the input buffer
471         //ALOGV("MidiFile::render - rendering audio");
472         int num_output = 0;
473         EAS_PCM* p = mAudioBuffer;
474         for (int i = 0; i < NUM_BUFFERS; i++) {
475             result = EAS_Render(mEasData, p, pLibConfig->mixBufferSize, &count);
476             if (result != EAS_SUCCESS) {
477                 ALOGE("EAS_Render returned %ld", result);
478             }
479             p += count * pLibConfig->numChannels;
480             num_output += count * pLibConfig->numChannels * sizeof(EAS_PCM);
481         }
482 
483         // update playback state and position
484         // ALOGV("MidiFile::render - updating state");
485         EAS_GetLocation(mEasData, mEasHandle, &mPlayTime);
486         EAS_State(mEasData, mEasHandle, &mState);
487         mMutex.unlock();
488 
489         // create audio output track if necessary
490         if (!mAudioSink->ready()) {
491             ALOGV("MidiFile::render - create output track");
492             if (createOutputTrack() != NO_ERROR)
493                 goto threadExit;
494         }
495 
496         // Write data to the audio hardware
497         // ALOGV("MidiFile::render - writing to audio output");
498         if ((temp = mAudioSink->write(mAudioBuffer, num_output)) < 0) {
499             ALOGE("Error in writing:%d",temp);
500             return temp;
501         }
502 
503         // start audio output if necessary
504         if (!audioStarted) {
505             //ALOGV("MidiFile::render - starting audio");
506             mAudioSink->start();
507             audioStarted = true;
508         }
509 
510         // still playing?
511         if ((mState == EAS_STATE_STOPPED) || (mState == EAS_STATE_ERROR) ||
512                 (mState == EAS_STATE_PAUSED))
513         {
514             switch(mState) {
515             case EAS_STATE_STOPPED:
516             {
517                 ALOGV("MidiFile::render - stopped");
518                 sendEvent(MEDIA_PLAYBACK_COMPLETE);
519                 break;
520             }
521             case EAS_STATE_ERROR:
522             {
523                 ALOGE("MidiFile::render - error");
524                 sendEvent(MEDIA_ERROR, MEDIA_ERROR_UNKNOWN);
525                 break;
526             }
527             case EAS_STATE_PAUSED:
528                 ALOGV("MidiFile::render - paused");
529                 break;
530             default:
531                 break;
532             }
533             mAudioSink->stop();
534             audioStarted = false;
535             mRender = false;
536         }
537     }
538 
539 threadExit:
540     mAudioSink.clear();
541     if (mAudioBuffer) {
542         delete [] mAudioBuffer;
543         mAudioBuffer = NULL;
544     }
545     mMutex.lock();
546     mTid = -1;
547     mCondition.signal();
548     mMutex.unlock();
549     return result;
550 }
551 
552 } // end namespace android
553