| /* |
| * Copyright (C) 2014 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| //#define LOG_NDEBUG 0 |
| #define LOG_TAG "MidiExtractor" |
| #include <utils/Log.h> |
| |
| #include "MidiExtractor.h" |
| |
| #include <media/MidiIoWrapper.h> |
| #include <media/stagefright/foundation/ADebug.h> |
| #include <media/stagefright/MediaBufferGroup.h> |
| #include <media/stagefright/MediaDefs.h> |
| #include <media/stagefright/MediaErrors.h> |
| #include <libsonivox/eas_reverb.h> |
| |
| namespace android { |
| |
| // how many Sonivox output buffers to aggregate into one MediaBuffer |
| static const int NUM_COMBINE_BUFFERS = 4; |
| |
| class MidiSource : public MediaTrackHelper { |
| |
| public: |
| MidiSource( |
| MidiEngine &engine, |
| AMediaFormat *trackMetadata); |
| |
| virtual media_status_t start(); |
| virtual media_status_t stop(); |
| virtual media_status_t getFormat(AMediaFormat *); |
| |
| virtual media_status_t read( |
| MediaBufferHelper **buffer, const ReadOptions *options = NULL); |
| |
| protected: |
| virtual ~MidiSource(); |
| |
| private: |
| MidiEngine &mEngine; |
| AMediaFormat *mTrackMetadata; |
| bool mInitCheck; |
| bool mStarted; |
| |
| status_t init(); |
| |
| // no copy constructor or assignment |
| MidiSource(const MidiSource &); |
| MidiSource &operator=(const MidiSource &); |
| |
| }; |
| |
| |
| // Midisource |
| |
| MidiSource::MidiSource( |
| MidiEngine &engine, |
| AMediaFormat *trackMetadata) |
| : mEngine(engine), |
| mTrackMetadata(trackMetadata), |
| mInitCheck(false), |
| mStarted(false) |
| { |
| ALOGV("MidiSource ctor"); |
| mInitCheck = init(); |
| } |
| |
| MidiSource::~MidiSource() |
| { |
| ALOGV("MidiSource dtor"); |
| if (mStarted) { |
| stop(); |
| } |
| } |
| |
| media_status_t MidiSource::start() |
| { |
| ALOGV("MidiSource::start"); |
| |
| CHECK(!mStarted); |
| mStarted = true; |
| mEngine.allocateBuffers(mBufferGroup); |
| return AMEDIA_OK; |
| } |
| |
| media_status_t MidiSource::stop() |
| { |
| ALOGV("MidiSource::stop"); |
| |
| CHECK(mStarted); |
| mStarted = false; |
| mEngine.releaseBuffers(); |
| |
| return AMEDIA_OK; |
| } |
| |
| media_status_t MidiSource::getFormat(AMediaFormat *meta) |
| { |
| return AMediaFormat_copy(meta, mTrackMetadata); |
| } |
| |
| media_status_t MidiSource::read( |
| MediaBufferHelper **outBuffer, const ReadOptions *options) |
| { |
| ALOGV("MidiSource::read"); |
| MediaBufferHelper *buffer; |
| // process an optional seek request |
| int64_t seekTimeUs; |
| ReadOptions::SeekMode mode; |
| if ((NULL != options) && options->getSeekTo(&seekTimeUs, &mode)) { |
| if (seekTimeUs <= 0LL) { |
| seekTimeUs = 0LL; |
| } |
| mEngine.seekTo(seekTimeUs); |
| } |
| buffer = mEngine.readBuffer(); |
| *outBuffer = buffer; |
| ALOGV("MidiSource::read %p done", this); |
| return buffer != NULL ? AMEDIA_OK : AMEDIA_ERROR_END_OF_STREAM; |
| } |
| |
| status_t MidiSource::init() |
| { |
| ALOGV("MidiSource::init"); |
| return OK; |
| } |
| |
| // MidiEngine |
| |
| MidiEngine::MidiEngine(CDataSource *dataSource, |
| AMediaFormat *fileMetadata, |
| AMediaFormat *trackMetadata) : |
| mEasData(NULL), |
| mEasHandle(NULL), |
| mEasConfig(NULL), |
| mIsInitialized(false) { |
| mIoWrapper = new MidiIoWrapper(dataSource); |
| // spin up a new EAS engine |
| EAS_I32 temp; |
| EAS_RESULT result = EAS_Init(&mEasData); |
| |
| if (result == EAS_SUCCESS) { |
| result = EAS_OpenFile(mEasData, mIoWrapper->getLocator(), &mEasHandle); |
| } |
| if (result == EAS_SUCCESS) { |
| result = EAS_Prepare(mEasData, mEasHandle); |
| } |
| if (result == EAS_SUCCESS) { |
| result = EAS_ParseMetaData(mEasData, mEasHandle, &temp); |
| } |
| |
| if (result != EAS_SUCCESS) { |
| return; |
| } |
| |
| if (fileMetadata != NULL) { |
| AMediaFormat_setString(fileMetadata, AMEDIAFORMAT_KEY_MIME, MEDIA_MIMETYPE_AUDIO_MIDI); |
| } |
| |
| if (trackMetadata != NULL) { |
| AMediaFormat_setString(trackMetadata, AMEDIAFORMAT_KEY_MIME, MEDIA_MIMETYPE_AUDIO_RAW); |
| AMediaFormat_setInt64( |
| trackMetadata, AMEDIAFORMAT_KEY_DURATION, 1000ll * temp); // milli->micro |
| mEasConfig = EAS_Config(); |
| AMediaFormat_setInt32( |
| trackMetadata, AMEDIAFORMAT_KEY_SAMPLE_RATE, mEasConfig->sampleRate); |
| AMediaFormat_setInt32( |
| trackMetadata, AMEDIAFORMAT_KEY_CHANNEL_COUNT, mEasConfig->numChannels); |
| AMediaFormat_setInt32( |
| trackMetadata, AMEDIAFORMAT_KEY_PCM_ENCODING, kAudioEncodingPcm16bit); |
| } |
| mIsInitialized = true; |
| } |
| |
| MidiEngine::~MidiEngine() { |
| if (mEasHandle) { |
| EAS_CloseFile(mEasData, mEasHandle); |
| } |
| if (mEasData) { |
| EAS_Shutdown(mEasData); |
| } |
| delete mIoWrapper; |
| } |
| |
| status_t MidiEngine::initCheck() { |
| return mIsInitialized ? OK : UNKNOWN_ERROR; |
| } |
| |
| status_t MidiEngine::allocateBuffers(MediaBufferGroupHelper *group) { |
| // select reverb preset and enable |
| EAS_SetParameter(mEasData, EAS_MODULE_REVERB, EAS_PARAM_REVERB_PRESET, EAS_PARAM_REVERB_CHAMBER); |
| EAS_SetParameter(mEasData, EAS_MODULE_REVERB, EAS_PARAM_REVERB_BYPASS, EAS_FALSE); |
| |
| int bufsize = sizeof(EAS_PCM) |
| * mEasConfig->mixBufferSize * mEasConfig->numChannels * NUM_COMBINE_BUFFERS; |
| ALOGV("using %d byte buffer", bufsize); |
| mGroup = group; |
| mGroup->add_buffer(bufsize); |
| return OK; |
| } |
| |
| status_t MidiEngine::releaseBuffers() { |
| return OK; |
| } |
| |
| status_t MidiEngine::seekTo(int64_t positionUs) { |
| ALOGV("seekTo %lld", (long long)positionUs); |
| EAS_RESULT result = EAS_Locate(mEasData, mEasHandle, positionUs / 1000, false); |
| return result == EAS_SUCCESS ? OK : UNKNOWN_ERROR; |
| } |
| |
| MediaBufferHelper* MidiEngine::readBuffer() { |
| EAS_STATE state; |
| EAS_State(mEasData, mEasHandle, &state); |
| if ((state == EAS_STATE_STOPPED) || (state == EAS_STATE_ERROR)) { |
| return NULL; |
| } |
| MediaBufferHelper *buffer; |
| status_t err = mGroup->acquire_buffer(&buffer); |
| if (err != OK) { |
| ALOGE("readBuffer: no buffer"); |
| return NULL; |
| } |
| EAS_I32 timeMs; |
| EAS_GetLocation(mEasData, mEasHandle, &timeMs); |
| int64_t timeUs = 1000ll * timeMs; |
| AMediaFormat *meta = buffer->meta_data(); |
| AMediaFormat_setInt64(meta, AMEDIAFORMAT_KEY_TIME_US, timeUs); |
| AMediaFormat_setInt32(meta, AMEDIAFORMAT_KEY_IS_SYNC_FRAME, 1); |
| |
| EAS_PCM* p = (EAS_PCM*) buffer->data(); |
| int numBytesOutput = 0; |
| for (int i = 0; i < NUM_COMBINE_BUFFERS; i++) { |
| EAS_I32 numRendered; |
| EAS_RESULT result = EAS_Render(mEasData, p, mEasConfig->mixBufferSize, &numRendered); |
| if (result != EAS_SUCCESS) { |
| ALOGE("EAS_Render() returned %ld, numBytesOutput = %d", result, numBytesOutput); |
| buffer->release(); |
| return NULL; // Stop processing to prevent infinite loops. |
| } |
| p += numRendered * mEasConfig->numChannels; |
| numBytesOutput += numRendered * mEasConfig->numChannels * sizeof(EAS_PCM); |
| } |
| buffer->set_range(0, numBytesOutput); |
| ALOGV("readBuffer: returning %zd in buffer %p", buffer->range_length(), buffer); |
| return buffer; |
| } |
| |
| |
| // MidiExtractor |
| |
| MidiExtractor::MidiExtractor( |
| CDataSource *dataSource) |
| : mDataSource(dataSource), |
| mInitCheck(false) |
| { |
| ALOGV("MidiExtractor ctor"); |
| mFileMetadata = AMediaFormat_new(); |
| mTrackMetadata = AMediaFormat_new(); |
| mEngine = new MidiEngine(mDataSource, mFileMetadata, mTrackMetadata); |
| mInitCheck = mEngine->initCheck(); |
| } |
| |
| MidiExtractor::~MidiExtractor() |
| { |
| ALOGV("MidiExtractor dtor"); |
| AMediaFormat_delete(mFileMetadata); |
| AMediaFormat_delete(mTrackMetadata); |
| delete mEngine; |
| } |
| |
| size_t MidiExtractor::countTracks() |
| { |
| return mInitCheck == OK ? 1 : 0; |
| } |
| |
| MediaTrackHelper *MidiExtractor::getTrack(size_t index) |
| { |
| if (mInitCheck != OK || index > 0) { |
| return NULL; |
| } |
| return new MidiSource(*mEngine, mTrackMetadata); |
| } |
| |
| media_status_t MidiExtractor::getTrackMetaData( |
| AMediaFormat *meta, |
| size_t index, uint32_t /* flags */) { |
| ALOGV("MidiExtractor::getTrackMetaData"); |
| if (mInitCheck != OK || index > 0) { |
| return AMEDIA_ERROR_UNKNOWN; |
| } |
| return AMediaFormat_copy(meta, mTrackMetadata); |
| } |
| |
| media_status_t MidiExtractor::getMetaData(AMediaFormat *meta) |
| { |
| ALOGV("MidiExtractor::getMetaData"); |
| return AMediaFormat_copy(meta, mFileMetadata); |
| } |
| |
| // Sniffer |
| |
| bool SniffMidi(CDataSource *source, float *confidence) |
| { |
| MidiEngine p(source, NULL, NULL); |
| if (p.initCheck() == OK) { |
| *confidence = 0.8; |
| ALOGV("SniffMidi: yes"); |
| return true; |
| } |
| ALOGV("SniffMidi: no"); |
| return false; |
| |
| } |
| |
| static const char *extensions[] = { |
| "imy", |
| "mid", |
| "midi", |
| "mxmf", |
| "ota", |
| "rtttl", |
| "rtx", |
| "smf", |
| "xmf", |
| NULL |
| }; |
| |
| extern "C" { |
| // This is the only symbol that needs to be exported |
| __attribute__ ((visibility ("default"))) |
| ExtractorDef GETEXTRACTORDEF() { |
| return { |
| EXTRACTORDEF_VERSION, |
| UUID("ef6cca0a-f8a2-43e6-ba5f-dfcd7c9a7ef2"), |
| 1, |
| "MIDI Extractor", |
| { |
| .v3 = { |
| []( |
| CDataSource *source, |
| float *confidence, |
| void **, |
| FreeMetaFunc *) -> CreatorFunc { |
| if (SniffMidi(source, confidence)) { |
| return []( |
| CDataSource *source, |
| void *) -> CMediaExtractor* { |
| return wrap(new MidiExtractor(source));}; |
| } |
| return NULL; |
| }, |
| extensions |
| } |
| }, |
| }; |
| } |
| |
| } // extern "C" |
| |
| } // namespace android |