1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17//#define LOG_NDEBUG 0
18#define LOG_TAG "MidiExtractor"
19#include <utils/Log.h>
20
21#include "include/MidiExtractor.h"
22
23#include <media/MidiIoWrapper.h>
24#include <media/stagefright/foundation/ADebug.h>
25#include <media/stagefright/MediaBufferGroup.h>
26#include <media/stagefright/MediaDefs.h>
27#include <media/stagefright/MetaData.h>
28#include <media/stagefright/MediaSource.h>
29#include <libsonivox/eas_reverb.h>
30
31namespace android {
32
33// how many Sonivox output buffers to aggregate into one MediaBuffer
34static const int NUM_COMBINE_BUFFERS = 4;
35
36class MidiSource : public MediaSource {
37
38public:
39    MidiSource(
40            const sp<MidiEngine> &engine,
41            const sp<MetaData> &trackMetadata);
42
43    virtual status_t start(MetaData *params);
44    virtual status_t stop();
45    virtual sp<MetaData> getFormat();
46
47    virtual status_t read(
48            MediaBuffer **buffer, const ReadOptions *options = NULL);
49
50protected:
51    virtual ~MidiSource();
52
53private:
54    sp<MidiEngine> mEngine;
55    sp<MetaData> mTrackMetadata;
56    bool mInitCheck;
57    bool mStarted;
58
59    status_t init();
60
61    // no copy constructor or assignment
62    MidiSource(const MidiSource &);
63    MidiSource &operator=(const MidiSource &);
64
65};
66
67
68// Midisource
69
70MidiSource::MidiSource(
71        const sp<MidiEngine> &engine,
72        const sp<MetaData> &trackMetadata)
73    : mEngine(engine),
74      mTrackMetadata(trackMetadata),
75      mInitCheck(false),
76      mStarted(false)
77{
78    ALOGV("MidiSource ctor");
79    mInitCheck = init();
80}
81
82MidiSource::~MidiSource()
83{
84    ALOGV("MidiSource dtor");
85    if (mStarted) {
86        stop();
87    }
88}
89
90status_t MidiSource::start(MetaData * /* params */)
91{
92    ALOGV("MidiSource::start");
93
94    CHECK(!mStarted);
95    mStarted = true;
96    mEngine->allocateBuffers();
97    return OK;
98}
99
100status_t MidiSource::stop()
101{
102    ALOGV("MidiSource::stop");
103
104    CHECK(mStarted);
105    mStarted = false;
106    mEngine->releaseBuffers();
107
108    return OK;
109}
110
111sp<MetaData> MidiSource::getFormat()
112{
113    return mTrackMetadata;
114}
115
116status_t MidiSource::read(
117        MediaBuffer **outBuffer, const ReadOptions *options)
118{
119    ALOGV("MidiSource::read");
120    MediaBuffer *buffer;
121    // process an optional seek request
122    int64_t seekTimeUs;
123    ReadOptions::SeekMode mode;
124    if ((NULL != options) && options->getSeekTo(&seekTimeUs, &mode)) {
125        if (seekTimeUs <= 0LL) {
126            seekTimeUs = 0LL;
127        }
128        mEngine->seekTo(seekTimeUs);
129    }
130    buffer = mEngine->readBuffer();
131    *outBuffer = buffer;
132    ALOGV("MidiSource::read %p done", this);
133    return buffer != NULL ? (status_t) OK : (status_t) ERROR_END_OF_STREAM;
134}
135
136status_t MidiSource::init()
137{
138    ALOGV("MidiSource::init");
139    return OK;
140}
141
142// MidiEngine
143
144MidiEngine::MidiEngine(const sp<DataSource> &dataSource,
145        const sp<MetaData> &fileMetadata,
146        const sp<MetaData> &trackMetadata) :
147            mGroup(NULL),
148            mEasData(NULL),
149            mEasHandle(NULL),
150            mEasConfig(NULL),
151            mIsInitialized(false) {
152    mIoWrapper = new MidiIoWrapper(dataSource);
153    // spin up a new EAS engine
154    EAS_I32 temp;
155    EAS_RESULT result = EAS_Init(&mEasData);
156
157    if (result == EAS_SUCCESS) {
158        result = EAS_OpenFile(mEasData, mIoWrapper->getLocator(), &mEasHandle);
159    }
160    if (result == EAS_SUCCESS) {
161        result = EAS_Prepare(mEasData, mEasHandle);
162    }
163    if (result == EAS_SUCCESS) {
164        result = EAS_ParseMetaData(mEasData, mEasHandle, &temp);
165    }
166
167    if (result != EAS_SUCCESS) {
168        return;
169    }
170
171    if (fileMetadata != NULL) {
172        fileMetadata->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_MIDI);
173    }
174
175    if (trackMetadata != NULL) {
176        trackMetadata->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_RAW);
177        trackMetadata->setInt64(kKeyDuration, 1000ll * temp); // milli->micro
178        mEasConfig = EAS_Config();
179        trackMetadata->setInt32(kKeySampleRate, mEasConfig->sampleRate);
180        trackMetadata->setInt32(kKeyChannelCount, mEasConfig->numChannels);
181        trackMetadata->setInt32(kKeyPcmEncoding, kAudioEncodingPcm16bit);
182    }
183    mIsInitialized = true;
184}
185
186MidiEngine::~MidiEngine() {
187    if (mEasHandle) {
188        EAS_CloseFile(mEasData, mEasHandle);
189    }
190    if (mEasData) {
191        EAS_Shutdown(mEasData);
192    }
193    delete mGroup;
194
195}
196
197status_t MidiEngine::initCheck() {
198    return mIsInitialized ? OK : UNKNOWN_ERROR;
199}
200
201status_t MidiEngine::allocateBuffers() {
202    // select reverb preset and enable
203    EAS_SetParameter(mEasData, EAS_MODULE_REVERB, EAS_PARAM_REVERB_PRESET, EAS_PARAM_REVERB_CHAMBER);
204    EAS_SetParameter(mEasData, EAS_MODULE_REVERB, EAS_PARAM_REVERB_BYPASS, EAS_FALSE);
205
206    mGroup = new MediaBufferGroup;
207    int bufsize = sizeof(EAS_PCM)
208            * mEasConfig->mixBufferSize * mEasConfig->numChannels * NUM_COMBINE_BUFFERS;
209    ALOGV("using %d byte buffer", bufsize);
210    mGroup->add_buffer(new MediaBuffer(bufsize));
211    return OK;
212}
213
214status_t MidiEngine::releaseBuffers() {
215    delete mGroup;
216    mGroup = NULL;
217    return OK;
218}
219
220status_t MidiEngine::seekTo(int64_t positionUs) {
221    ALOGV("seekTo %lld", (long long)positionUs);
222    EAS_RESULT result = EAS_Locate(mEasData, mEasHandle, positionUs / 1000, false);
223    return result == EAS_SUCCESS ? OK : UNKNOWN_ERROR;
224}
225
226MediaBuffer* MidiEngine::readBuffer() {
227    EAS_STATE state;
228    EAS_State(mEasData, mEasHandle, &state);
229    if ((state == EAS_STATE_STOPPED) || (state == EAS_STATE_ERROR)) {
230        return NULL;
231    }
232    MediaBuffer *buffer;
233    status_t err = mGroup->acquire_buffer(&buffer);
234    if (err != OK) {
235        ALOGE("readBuffer: no buffer");
236        return NULL;
237    }
238    EAS_I32 timeMs;
239    EAS_GetLocation(mEasData, mEasHandle, &timeMs);
240    int64_t timeUs = 1000ll * timeMs;
241    buffer->meta_data()->setInt64(kKeyTime, timeUs);
242
243    EAS_PCM* p = (EAS_PCM*) buffer->data();
244    int numBytesOutput = 0;
245    for (int i = 0; i < NUM_COMBINE_BUFFERS; i++) {
246        EAS_I32 numRendered;
247        EAS_RESULT result = EAS_Render(mEasData, p, mEasConfig->mixBufferSize, &numRendered);
248        if (result != EAS_SUCCESS) {
249            ALOGE("EAS_Render returned %ld", result);
250            break;
251        }
252        p += numRendered * mEasConfig->numChannels;
253        numBytesOutput += numRendered * mEasConfig->numChannels * sizeof(EAS_PCM);
254    }
255    buffer->set_range(0, numBytesOutput);
256    ALOGV("readBuffer: returning %zd in buffer %p", buffer->range_length(), buffer);
257    return buffer;
258}
259
260
261// MidiExtractor
262
263MidiExtractor::MidiExtractor(
264        const sp<DataSource> &dataSource)
265    : mDataSource(dataSource),
266      mInitCheck(false)
267{
268    ALOGV("MidiExtractor ctor");
269    mFileMetadata = new MetaData;
270    mTrackMetadata = new MetaData;
271    mEngine = new MidiEngine(mDataSource, mFileMetadata, mTrackMetadata);
272    mInitCheck = mEngine->initCheck();
273}
274
275MidiExtractor::~MidiExtractor()
276{
277    ALOGV("MidiExtractor dtor");
278}
279
280size_t MidiExtractor::countTracks()
281{
282    return mInitCheck == OK ? 1 : 0;
283}
284
285sp<IMediaSource> MidiExtractor::getTrack(size_t index)
286{
287    if (mInitCheck != OK || index > 0) {
288        return NULL;
289    }
290    return new MidiSource(mEngine, mTrackMetadata);
291}
292
293sp<MetaData> MidiExtractor::getTrackMetaData(
294        size_t index, uint32_t /* flags */) {
295    ALOGV("MidiExtractor::getTrackMetaData");
296    if (mInitCheck != OK || index > 0) {
297        return NULL;
298    }
299    return mTrackMetadata;
300}
301
302sp<MetaData> MidiExtractor::getMetaData()
303{
304    ALOGV("MidiExtractor::getMetaData");
305    return mFileMetadata;
306}
307
308// Sniffer
309
310bool SniffMidi(
311        const sp<DataSource> &source, String8 *mimeType, float *confidence,
312        sp<AMessage> *)
313{
314    sp<MidiEngine> p = new MidiEngine(source, NULL, NULL);
315    if (p->initCheck() == OK) {
316        *mimeType = MEDIA_MIMETYPE_AUDIO_MIDI;
317        *confidence = 0.8;
318        ALOGV("SniffMidi: yes");
319        return true;
320    }
321    ALOGV("SniffMidi: no");
322    return false;
323
324}
325
326}  // namespace android
327