/* * 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 #include "MidiExtractor.h" #include #include #include #include #include #include #include namespace android { // how many Sonivox output buffers to aggregate into one MediaBufferBase static const int NUM_COMBINE_BUFFERS = 4; class MidiSource : public MediaTrack { public: MidiSource( MidiEngine &engine, MetaDataBase &trackMetadata); virtual status_t start(MetaDataBase *params); virtual status_t stop(); virtual status_t getFormat(MetaDataBase&); virtual status_t read( MediaBufferBase **buffer, const ReadOptions *options = NULL); protected: virtual ~MidiSource(); private: MidiEngine &mEngine; MetaDataBase &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, MetaDataBase &trackMetadata) : mEngine(engine), mTrackMetadata(trackMetadata), mInitCheck(false), mStarted(false) { ALOGV("MidiSource ctor"); mInitCheck = init(); } MidiSource::~MidiSource() { ALOGV("MidiSource dtor"); if (mStarted) { stop(); } } status_t MidiSource::start(MetaDataBase * /* params */) { ALOGV("MidiSource::start"); CHECK(!mStarted); mStarted = true; mEngine.allocateBuffers(); return OK; } status_t MidiSource::stop() { ALOGV("MidiSource::stop"); CHECK(mStarted); mStarted = false; mEngine.releaseBuffers(); return OK; } status_t MidiSource::getFormat(MetaDataBase &meta) { meta = mTrackMetadata; return OK; } status_t MidiSource::read( MediaBufferBase **outBuffer, const ReadOptions *options) { ALOGV("MidiSource::read"); MediaBufferBase *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 ? (status_t) OK : (status_t) ERROR_END_OF_STREAM; } status_t MidiSource::init() { ALOGV("MidiSource::init"); return OK; } // MidiEngine MidiEngine::MidiEngine(DataSourceBase *dataSource, MetaDataBase *fileMetadata, MetaDataBase *trackMetadata) : mGroup(NULL), 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) { fileMetadata->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_MIDI); } if (trackMetadata != NULL) { trackMetadata->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_RAW); trackMetadata->setInt64(kKeyDuration, 1000ll * temp); // milli->micro mEasConfig = EAS_Config(); trackMetadata->setInt32(kKeySampleRate, mEasConfig->sampleRate); trackMetadata->setInt32(kKeyChannelCount, mEasConfig->numChannels); trackMetadata->setInt32(kKeyPcmEncoding, kAudioEncodingPcm16bit); } mIsInitialized = true; } MidiEngine::~MidiEngine() { if (mEasHandle) { EAS_CloseFile(mEasData, mEasHandle); } if (mEasData) { EAS_Shutdown(mEasData); } delete mGroup; delete mIoWrapper; } status_t MidiEngine::initCheck() { return mIsInitialized ? OK : UNKNOWN_ERROR; } status_t MidiEngine::allocateBuffers() { // 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); mGroup = new MediaBufferGroup; int bufsize = sizeof(EAS_PCM) * mEasConfig->mixBufferSize * mEasConfig->numChannels * NUM_COMBINE_BUFFERS; ALOGV("using %d byte buffer", bufsize); mGroup->add_buffer(MediaBufferBase::Create(bufsize)); return OK; } status_t MidiEngine::releaseBuffers() { delete mGroup; mGroup = NULL; 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; } MediaBufferBase* MidiEngine::readBuffer() { EAS_STATE state; EAS_State(mEasData, mEasHandle, &state); if ((state == EAS_STATE_STOPPED) || (state == EAS_STATE_ERROR)) { return NULL; } MediaBufferBase *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; buffer->meta_data().setInt64(kKeyTime, timeUs); 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", result); break; } 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( DataSourceBase *dataSource) : mDataSource(dataSource), mInitCheck(false) { ALOGV("MidiExtractor ctor"); mEngine = new MidiEngine(mDataSource, &mFileMetadata, &mTrackMetadata); mInitCheck = mEngine->initCheck(); } MidiExtractor::~MidiExtractor() { ALOGV("MidiExtractor dtor"); } size_t MidiExtractor::countTracks() { return mInitCheck == OK ? 1 : 0; } MediaTrack *MidiExtractor::getTrack(size_t index) { if (mInitCheck != OK || index > 0) { return NULL; } return new MidiSource(*mEngine, mTrackMetadata); } status_t MidiExtractor::getTrackMetaData( MetaDataBase &meta, size_t index, uint32_t /* flags */) { ALOGV("MidiExtractor::getTrackMetaData"); if (mInitCheck != OK || index > 0) { return UNKNOWN_ERROR; } meta = mTrackMetadata; return OK; } status_t MidiExtractor::getMetaData(MetaDataBase &meta) { ALOGV("MidiExtractor::getMetaData"); meta = mFileMetadata; return OK; } // Sniffer bool SniffMidi(DataSourceBase *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; } extern "C" { // This is the only symbol that needs to be exported __attribute__ ((visibility ("default"))) MediaExtractor::ExtractorDef GETEXTRACTORDEF() { return { MediaExtractor::EXTRACTORDEF_VERSION, UUID("ef6cca0a-f8a2-43e6-ba5f-dfcd7c9a7ef2"), 1, "MIDI Extractor", []( DataSourceBase *source, float *confidence, void **, MediaExtractor::FreeMetaFunc *) -> MediaExtractor::CreatorFunc { if (SniffMidi(source, confidence)) { return []( DataSourceBase *source, void *) -> MediaExtractor* { return new MidiExtractor(source);}; } return NULL; } }; } } // extern "C" } // namespace android