blob: 7e045236c9f402a672132673bde8d2543901599d [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/* 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>
Glenn Kasten6af763b2011-05-04 17:58:57 -070032#include <unistd.h>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080033
Dima Zavin34bb4192011-05-11 14:15:23 -070034#include <system/audio.h>
Dima Zavin24fc2fb2011-04-19 22:30:36 -070035
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080036#include "MidiFile.h"
37
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080038// ----------------------------------------------------------------------------
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),
Dima Zavin24fc2fb2011-04-19 22:30:36 -070058 mStreamType(AUDIO_STREAM_MUSIC), mLoop(false), mExit(false),
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080059 mPaused(false), mRender(false), mTid(-1)
60{
61 LOGV("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 LOGE("EAS library/header mismatch");
73 goto Failed;
74 }
75
76 // initialize EAS library
77 if (EAS_Init(&mEasData) != EAS_SUCCESS) {
78 LOGE("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);
Dave Sparksc0827692009-04-29 12:59:33 -070089 createThreadEtc(renderThread, this, "midithread", ANDROID_PRIORITY_AUDIO);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080090 mCondition.wait(mMutex);
91 LOGV("thread started");
92 }
93
94 // indicate success
95 if (mTid > 0) {
96 LOGV(" render thread(%d) started", mTid);
97 mState = EAS_STATE_READY;
98 }
99
100Failed:
101 return;
102}
103
104status_t MidiFile::initCheck()
105{
106 if (mState == EAS_STATE_ERROR) return ERROR_EAS_FAILURE;
107 return NO_ERROR;
108}
109
110MidiFile::~MidiFile() {
111 LOGV("MidiFile destructor");
112 release();
113}
114
Andreas Huber25643002010-01-28 11:19:57 -0800115status_t MidiFile::setDataSource(
116 const char* path, const KeyedVector<String8, String8> *) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800117 LOGV("MidiFile::setDataSource url=%s", path);
118 Mutex::Autolock lock(mMutex);
119
120 // file still open?
121 if (mEasHandle) {
122 reset_nosync();
123 }
124
125 // open file and set paused state
126 mFileLocator.path = strdup(path);
127 mFileLocator.fd = -1;
128 mFileLocator.offset = 0;
129 mFileLocator.length = 0;
130 EAS_RESULT result = EAS_OpenFile(mEasData, &mFileLocator, &mEasHandle);
131 if (result == EAS_SUCCESS) {
132 updateState();
133 }
134
135 if (result != EAS_SUCCESS) {
136 LOGE("EAS_OpenFile failed: [%d]", (int)result);
137 mState = EAS_STATE_ERROR;
138 return ERROR_OPEN_FAILED;
139 }
140
141 mState = EAS_STATE_OPEN;
142 mPlayTime = 0;
143 return NO_ERROR;
144}
145
146status_t MidiFile::setDataSource(int fd, int64_t offset, int64_t length)
147{
148 LOGV("MidiFile::setDataSource fd=%d", fd);
149 Mutex::Autolock lock(mMutex);
150
151 // file still open?
152 if (mEasHandle) {
153 reset_nosync();
154 }
155
156 // open file and set paused state
157 mFileLocator.fd = dup(fd);
158 mFileLocator.offset = offset;
159 mFileLocator.length = length;
160 EAS_RESULT result = EAS_OpenFile(mEasData, &mFileLocator, &mEasHandle);
161 updateState();
162
163 if (result != EAS_SUCCESS) {
164 LOGE("EAS_OpenFile failed: [%d]", (int)result);
165 mState = EAS_STATE_ERROR;
166 return ERROR_OPEN_FAILED;
167 }
168
169 mState = EAS_STATE_OPEN;
170 mPlayTime = 0;
171 return NO_ERROR;
172}
173
174status_t MidiFile::prepare()
175{
176 LOGV("MidiFile::prepare");
177 Mutex::Autolock lock(mMutex);
178 if (!mEasHandle) {
179 return ERROR_NOT_OPEN;
180 }
181 EAS_RESULT result;
182 if ((result = EAS_Prepare(mEasData, mEasHandle)) != EAS_SUCCESS) {
183 LOGE("EAS_Prepare failed: [%ld]", result);
184 return ERROR_EAS_FAILURE;
185 }
186 updateState();
187 return NO_ERROR;
188}
189
190status_t MidiFile::prepareAsync()
191{
192 LOGV("MidiFile::prepareAsync");
193 status_t ret = prepare();
194
195 // don't hold lock during callback
196 if (ret == NO_ERROR) {
197 sendEvent(MEDIA_PREPARED);
198 } else {
199 sendEvent(MEDIA_ERROR, MEDIA_ERROR_UNKNOWN, ret);
200 }
201 return ret;
202}
203
204status_t MidiFile::start()
205{
206 LOGV("MidiFile::start");
207 Mutex::Autolock lock(mMutex);
208 if (!mEasHandle) {
209 return ERROR_NOT_OPEN;
210 }
211
212 // resuming after pause?
213 if (mPaused) {
214 if (EAS_Resume(mEasData, mEasHandle) != EAS_SUCCESS) {
215 return ERROR_EAS_FAILURE;
216 }
217 mPaused = false;
218 updateState();
219 }
220
221 mRender = true;
222
223 // wake up render thread
224 LOGV(" wakeup render thread");
225 mCondition.signal();
226 return NO_ERROR;
227}
228
229status_t MidiFile::stop()
230{
231 LOGV("MidiFile::stop");
232 Mutex::Autolock lock(mMutex);
233 if (!mEasHandle) {
234 return ERROR_NOT_OPEN;
235 }
236 if (!mPaused && (mState != EAS_STATE_STOPPED)) {
237 EAS_RESULT result = EAS_Pause(mEasData, mEasHandle);
238 if (result != EAS_SUCCESS) {
239 LOGE("EAS_Pause returned error %ld", result);
240 return ERROR_EAS_FAILURE;
241 }
242 }
243 mPaused = false;
244 return NO_ERROR;
245}
246
247status_t MidiFile::seekTo(int position)
248{
249 LOGV("MidiFile::seekTo %d", position);
250 // hold lock during EAS calls
251 {
252 Mutex::Autolock lock(mMutex);
253 if (!mEasHandle) {
254 return ERROR_NOT_OPEN;
255 }
256 EAS_RESULT result;
257 if ((result = EAS_Locate(mEasData, mEasHandle, position, false))
258 != EAS_SUCCESS)
259 {
260 LOGE("EAS_Locate returned %ld", result);
261 return ERROR_EAS_FAILURE;
262 }
263 EAS_GetLocation(mEasData, mEasHandle, &mPlayTime);
264 }
265 sendEvent(MEDIA_SEEK_COMPLETE);
266 return NO_ERROR;
267}
268
269status_t MidiFile::pause()
270{
271 LOGV("MidiFile::pause");
272 Mutex::Autolock lock(mMutex);
273 if (!mEasHandle) {
274 return ERROR_NOT_OPEN;
275 }
276 if ((mState == EAS_STATE_PAUSING) || (mState == EAS_STATE_PAUSED)) return NO_ERROR;
277 if (EAS_Pause(mEasData, mEasHandle) != EAS_SUCCESS) {
278 return ERROR_EAS_FAILURE;
279 }
280 mPaused = true;
281 return NO_ERROR;
282}
283
284bool MidiFile::isPlaying()
285{
286 LOGV("MidiFile::isPlaying, mState=%d", int(mState));
287 if (!mEasHandle || mPaused) return false;
288 return (mState == EAS_STATE_PLAY);
289}
290
291status_t MidiFile::getCurrentPosition(int* position)
292{
293 LOGV("MidiFile::getCurrentPosition");
294 if (!mEasHandle) {
295 LOGE("getCurrentPosition(): file not open");
296 return ERROR_NOT_OPEN;
297 }
298 if (mPlayTime < 0) {
299 LOGE("getCurrentPosition(): mPlayTime = %ld", mPlayTime);
300 return ERROR_EAS_FAILURE;
301 }
302 *position = mPlayTime;
303 return NO_ERROR;
304}
305
306status_t MidiFile::getDuration(int* duration)
307{
308
309 LOGV("MidiFile::getDuration");
310 {
311 Mutex::Autolock lock(mMutex);
312 if (!mEasHandle) return ERROR_NOT_OPEN;
313 *duration = mDuration;
314 }
315
316 // if no duration cached, get the duration
317 // don't need a lock here because we spin up a new engine
318 if (*duration < 0) {
319 EAS_I32 temp;
320 EAS_DATA_HANDLE easData = NULL;
321 EAS_HANDLE easHandle = NULL;
322 EAS_RESULT result = EAS_Init(&easData);
323 if (result == EAS_SUCCESS) {
324 result = EAS_OpenFile(easData, &mFileLocator, &easHandle);
325 }
326 if (result == EAS_SUCCESS) {
327 result = EAS_Prepare(easData, easHandle);
328 }
329 if (result == EAS_SUCCESS) {
330 result = EAS_ParseMetaData(easData, easHandle, &temp);
331 }
332 if (easHandle) {
333 EAS_CloseFile(easData, easHandle);
334 }
335 if (easData) {
336 EAS_Shutdown(easData);
337 }
338
339 if (result != EAS_SUCCESS) {
340 return ERROR_EAS_FAILURE;
341 }
342
343 // cache successful result
344 mDuration = *duration = int(temp);
345 }
346
347 return NO_ERROR;
348}
349
350status_t MidiFile::release()
351{
352 LOGV("MidiFile::release");
353 Mutex::Autolock l(mMutex);
354 reset_nosync();
355
356 // wait for render thread to exit
357 mExit = true;
358 mCondition.signal();
359
360 // wait for thread to exit
361 if (mAudioBuffer) {
362 mCondition.wait(mMutex);
363 }
364
365 // release resources
366 if (mEasData) {
367 EAS_Shutdown(mEasData);
368 mEasData = NULL;
369 }
370 return NO_ERROR;
371}
372
373status_t MidiFile::reset()
374{
375 LOGV("MidiFile::reset");
376 Mutex::Autolock lock(mMutex);
377 return reset_nosync();
378}
379
380// call only with mutex held
381status_t MidiFile::reset_nosync()
382{
383 LOGV("MidiFile::reset_nosync");
384 // close file
385 if (mEasHandle) {
386 EAS_CloseFile(mEasData, mEasHandle);
387 mEasHandle = NULL;
388 }
389 if (mFileLocator.path) {
390 free((void*)mFileLocator.path);
391 mFileLocator.path = NULL;
392 }
393 if (mFileLocator.fd >= 0) {
394 close(mFileLocator.fd);
395 }
396 mFileLocator.fd = -1;
397 mFileLocator.offset = 0;
398 mFileLocator.length = 0;
399
400 mPlayTime = -1;
401 mDuration = -1;
402 mLoop = false;
403 mPaused = false;
404 mRender = false;
405 return NO_ERROR;
406}
407
408status_t MidiFile::setLooping(int loop)
409{
410 LOGV("MidiFile::setLooping");
411 Mutex::Autolock lock(mMutex);
412 if (!mEasHandle) {
413 return ERROR_NOT_OPEN;
414 }
415 loop = loop ? -1 : 0;
416 if (EAS_SetRepeat(mEasData, mEasHandle, loop) != EAS_SUCCESS) {
417 return ERROR_EAS_FAILURE;
418 }
419 return NO_ERROR;
420}
421
422status_t MidiFile::createOutputTrack() {
Dima Zavin24fc2fb2011-04-19 22:30:36 -0700423 if (mAudioSink->open(pLibConfig->sampleRate, pLibConfig->numChannels, AUDIO_FORMAT_PCM_16_BIT, 2) != NO_ERROR) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800424 LOGE("mAudioSink open failed");
425 return ERROR_OPEN_FAILED;
426 }
427 return NO_ERROR;
428}
429
430int MidiFile::renderThread(void* p) {
431
432 return ((MidiFile*)p)->render();
433}
434
435int MidiFile::render() {
436 EAS_RESULT result = EAS_FAILURE;
437 EAS_I32 count;
438 int temp;
439 bool audioStarted = false;
440
441 LOGV("MidiFile::render");
442
443 // allocate render buffer
444 mAudioBuffer = new EAS_PCM[pLibConfig->mixBufferSize * pLibConfig->numChannels * NUM_BUFFERS];
445 if (!mAudioBuffer) {
446 LOGE("mAudioBuffer allocate failed");
447 goto threadExit;
448 }
449
450 // signal main thread that we started
451 {
452 Mutex::Autolock l(mMutex);
Glenn Kasten6af763b2011-05-04 17:58:57 -0700453 mTid = gettid();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800454 LOGV("render thread(%d) signal", mTid);
455 mCondition.signal();
456 }
457
458 while (1) {
459 mMutex.lock();
460
461 // nothing to render, wait for client thread to wake us up
462 while (!mRender && !mExit)
463 {
464 LOGV("MidiFile::render - signal wait");
465 mCondition.wait(mMutex);
466 LOGV("MidiFile::render - signal rx'd");
467 }
468 if (mExit) {
469 mMutex.unlock();
470 break;
471 }
472
473 // render midi data into the input buffer
474 //LOGV("MidiFile::render - rendering audio");
475 int num_output = 0;
476 EAS_PCM* p = mAudioBuffer;
477 for (int i = 0; i < NUM_BUFFERS; i++) {
478 result = EAS_Render(mEasData, p, pLibConfig->mixBufferSize, &count);
479 if (result != EAS_SUCCESS) {
480 LOGE("EAS_Render returned %ld", result);
481 }
482 p += count * pLibConfig->numChannels;
483 num_output += count * pLibConfig->numChannels * sizeof(EAS_PCM);
484 }
485
486 // update playback state and position
487 // LOGV("MidiFile::render - updating state");
488 EAS_GetLocation(mEasData, mEasHandle, &mPlayTime);
489 EAS_State(mEasData, mEasHandle, &mState);
490 mMutex.unlock();
491
492 // create audio output track if necessary
493 if (!mAudioSink->ready()) {
494 LOGV("MidiFile::render - create output track");
495 if (createOutputTrack() != NO_ERROR)
496 goto threadExit;
497 }
498
499 // Write data to the audio hardware
500 // LOGV("MidiFile::render - writing to audio output");
501 if ((temp = mAudioSink->write(mAudioBuffer, num_output)) < 0) {
502 LOGE("Error in writing:%d",temp);
503 return temp;
504 }
505
506 // start audio output if necessary
507 if (!audioStarted) {
508 //LOGV("MidiFile::render - starting audio");
509 mAudioSink->start();
510 audioStarted = true;
511 }
512
513 // still playing?
514 if ((mState == EAS_STATE_STOPPED) || (mState == EAS_STATE_ERROR) ||
515 (mState == EAS_STATE_PAUSED))
516 {
517 switch(mState) {
518 case EAS_STATE_STOPPED:
519 {
520 LOGV("MidiFile::render - stopped");
521 sendEvent(MEDIA_PLAYBACK_COMPLETE);
522 break;
523 }
524 case EAS_STATE_ERROR:
525 {
526 LOGE("MidiFile::render - error");
527 sendEvent(MEDIA_ERROR, MEDIA_ERROR_UNKNOWN);
528 break;
529 }
530 case EAS_STATE_PAUSED:
531 LOGV("MidiFile::render - paused");
532 break;
533 default:
534 break;
535 }
536 mAudioSink->stop();
537 audioStarted = false;
538 mRender = false;
539 }
540 }
541
542threadExit:
543 mAudioSink.clear();
544 if (mAudioBuffer) {
545 delete [] mAudioBuffer;
546 mAudioBuffer = NULL;
547 }
548 mMutex.lock();
549 mTid = -1;
550 mCondition.signal();
551 mMutex.unlock();
552 return result;
553}
554
555} // end namespace android