• 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 
33 #include "MidiFile.h"
34 
35 #ifdef HAVE_GETTID
myTid()36 static pid_t myTid() { return gettid(); }
37 #else
myTid()38 static pid_t myTid() { return getpid(); }
39 #endif
40 
41 // ----------------------------------------------------------------------------
42 
43 namespace android {
44 
45 // ----------------------------------------------------------------------------
46 
47 // The midi engine buffers are a bit small (128 frames), so we batch them up
48 static const int NUM_BUFFERS = 4;
49 
50 // TODO: Determine appropriate return codes
51 static status_t ERROR_NOT_OPEN = -1;
52 static status_t ERROR_OPEN_FAILED = -2;
53 static status_t ERROR_EAS_FAILURE = -3;
54 static status_t ERROR_ALLOCATE_FAILED = -4;
55 
56 static const S_EAS_LIB_CONFIG* pLibConfig = NULL;
57 
MidiFile()58 MidiFile::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", ANDROID_PRIORITY_AUDIO);
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 
103 Failed:
104     return;
105 }
106 
initCheck()107 status_t MidiFile::initCheck()
108 {
109     if (mState == EAS_STATE_ERROR) return ERROR_EAS_FAILURE;
110     return NO_ERROR;
111 }
112 
~MidiFile()113 MidiFile::~MidiFile() {
114     LOGV("MidiFile destructor");
115     release();
116 }
117 
setDataSource(const char * path)118 status_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 
setDataSource(int fd,int64_t offset,int64_t length)149 status_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 
prepare()177 status_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 
prepareAsync()193 status_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 
start()207 status_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 
stop()232 status_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 
seekTo(int position)250 status_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 
pause()272 status_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 
isPlaying()287 bool 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 
getCurrentPosition(int * position)294 status_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 
getDuration(int * duration)309 status_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 
release()353 status_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 
reset()376 status_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
reset_nosync()384 status_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 
setLooping(int loop)411 status_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 
createOutputTrack()425 status_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 
renderThread(void * p)433 int MidiFile::renderThread(void* p) {
434 
435     return ((MidiFile*)p)->render();
436 }
437 
render()438 int 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 
545 threadExit:
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