blob: 949fbe00db6311318e2f00b2327ae736c611d9f0 [file] [log] [blame]
/*
* 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/MetaData.h>
#include <media/MediaTrack.h>
#include <libsonivox/eas_reverb.h>
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