diff options
Diffstat (limited to 'media/libstagefright')
-rw-r--r-- | media/libstagefright/Android.mk | 28 | ||||
-rw-r--r-- | media/libstagefright/AudioPlayer.cpp | 7 | ||||
-rw-r--r-- | media/libstagefright/AwesomePlayer.cpp | 188 | ||||
-rw-r--r-- | media/libstagefright/DataSource.cpp | 6 | ||||
-rw-r--r-- | media/libstagefright/ExtendedExtractor.cpp | 110 | ||||
-rw-r--r-- | media/libstagefright/ExtendedWriter.cpp | 390 | ||||
-rw-r--r-- | media/libstagefright/LPAPlayerALSA.cpp | 791 | ||||
-rw-r--r-- | media/libstagefright/MPEG4Extractor.cpp | 70 | ||||
-rw-r--r-- | media/libstagefright/MediaExtractor.cpp | 11 | ||||
-rw-r--r--[-rwxr-xr-x] | media/libstagefright/OMXCodec.cpp | 655 | ||||
-rw-r--r-- | media/libstagefright/QCMediaDefs.cpp | 55 | ||||
-rw-r--r-- | media/libstagefright/QCOMXCodec.cpp | 548 | ||||
-rw-r--r-- | media/libstagefright/StagefrightMediaScanner.cpp | 8 | ||||
-rw-r--r-- | media/libstagefright/TunnelPlayer.cpp | 782 | ||||
-rw-r--r-- | media/libstagefright/WAVEWriter.cpp | 323 | ||||
-rw-r--r-- | media/libstagefright/include/AwesomePlayer.h | 8 | ||||
-rw-r--r-- | media/libstagefright/include/ExtendedExtractor.h | 58 |
17 files changed, 3987 insertions, 51 deletions
diff --git a/media/libstagefright/Android.mk b/media/libstagefright/Android.mk index 7302692..35a5d05 100644 --- a/media/libstagefright/Android.mk +++ b/media/libstagefright/Android.mk @@ -1,6 +1,12 @@ LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) +ifeq ($(BOARD_USES_ALSA_AUDIO),true) + ifeq ($(call is-chipset-in-board-platform,msm8960),true) + LOCAL_CFLAGS += -DUSE_TUNNEL_MODE + endif +endif + include frameworks/av/media/libstagefright/codecs/common/Config.mk LOCAL_SRC_FILES:= \ @@ -63,7 +69,27 @@ LOCAL_C_INCLUDES:= \ $(TOP)/frameworks/native/include/media/openmax \ $(TOP)/external/flac/include \ $(TOP)/external/tremolo \ - $(TOP)/external/openssl/include \ + $(TOP)/external/openssl/include + +ifeq ($(BOARD_USES_QCOM_HARDWARE),true) +LOCAL_SRC_FILES += \ + ExtendedWriter.cpp \ + QCMediaDefs.cpp \ + QCOMXCodec.cpp \ + WAVEWriter.cpp \ + ExtendedExtractor.cpp + +LOCAL_C_INCLUDES += \ + $(TOP)/hardware/qcom/media/mm-core/inc + +ifeq ($(TARGET_QCOM_AUDIO_VARIANT),caf) +LOCAL_CFLAGS += -DQCOM_ENHANCED_AUDIO +LOCAL_SRC_FILES += \ + LPAPlayerALSA.cpp \ + TunnelPlayer.cpp +endif +endif + LOCAL_SHARED_LIBRARIES := \ libbinder \ diff --git a/media/libstagefright/AudioPlayer.cpp b/media/libstagefright/AudioPlayer.cpp index 4208019..deb6b70 100644 --- a/media/libstagefright/AudioPlayer.cpp +++ b/media/libstagefright/AudioPlayer.cpp @@ -310,6 +310,13 @@ size_t AudioPlayer::AudioSinkCallback( void *buffer, size_t size, void *cookie) { AudioPlayer *me = (AudioPlayer *)cookie; +#ifdef QCOM_ENHANCED_AUDIO + if (buffer == NULL) { + //Not applicable for AudioPlayer + ALOGE("This indicates the event underrun case for LPA/Tunnel"); + return 0; + } +#endif return me->fillBuffer(buffer, size); } diff --git a/media/libstagefright/AwesomePlayer.cpp b/media/libstagefright/AwesomePlayer.cpp index 48b6371..5cffad8 100644 --- a/media/libstagefright/AwesomePlayer.cpp +++ b/media/libstagefright/AwesomePlayer.cpp @@ -2,6 +2,10 @@ * Copyright (C) 2009 The Android Open Source Project * Copyright (c) 2012, The Linux Foundation. All rights reserved. * + * Copyright (c) 2011-2012, The Linux Foundation. All rights reserved. + * Not a Contribution, Apache license notifications and license are retained + * for attribution purposes only. + * 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 @@ -40,6 +44,12 @@ #include <media/stagefright/foundation/ADebug.h> #include <media/stagefright/timedtext/TimedTextDriver.h> #include <media/stagefright/AudioPlayer.h> +#ifdef QCOM_ENHANCED_AUDIO +#include <media/stagefright/LPAPlayer.h> +#ifdef USE_TUNNEL_MODE +#include <media/stagefright/TunnelPlayer.h> +#endif +#endif #include <media/stagefright/DataSource.h> #include <media/stagefright/FileSource.h> #include <media/stagefright/MediaBuffer.h> @@ -65,6 +75,9 @@ static int64_t kLowWaterMarkUs = 2000000ll; // 2secs static int64_t kHighWaterMarkUs = 5000000ll; // 5secs static const size_t kLowWaterMarkBytes = 40000; static const size_t kHighWaterMarkBytes = 200000; +#ifdef QCOM_ENHANCED_AUDIO +int AwesomePlayer::mTunnelAliveAP = 0; +#endif struct AwesomeEvent : public TimedEventQueue::Event { AwesomeEvent( @@ -215,6 +228,9 @@ AwesomePlayer::AwesomePlayer() mAudioStatusEventPending = false; reset(); +#ifdef QCOM_ENHANCED_AUDIO + mIsTunnelAudio = false; +#endif } AwesomePlayer::~AwesomePlayer() { @@ -224,6 +240,17 @@ AwesomePlayer::~AwesomePlayer() { reset(); +#ifdef QCOM_ENHANCED_AUDIO + // Disable Tunnel Mode Audio + if (mIsTunnelAudio) { + if(mTunnelAliveAP > 0) { + mTunnelAliveAP--; + ALOGV("mTunnelAliveAP = %d", mTunnelAliveAP); + } + } + mIsTunnelAudio = false; +#endif + mClient.disconnect(); } @@ -858,6 +885,9 @@ status_t AwesomePlayer::play() { } status_t AwesomePlayer::play_l() { +#ifdef QCOM_ENHANCED_AUDIO + int tunnelObjectsAlive = 0; +#endif modifyFlags(SEEK_PREVIEW, CLEAR); if (mFlags & PLAYING) { @@ -885,6 +915,13 @@ status_t AwesomePlayer::play_l() { if (mAudioSource != NULL) { if (mAudioPlayer == NULL) { if (mAudioSink != NULL) { +#ifdef QCOM_ENHANCED_AUDIO + sp<MetaData> format = mAudioTrack->getFormat(); + const char *mime; + bool success = format->findCString(kKeyMIMEType, &mime); + CHECK(success); +#endif + bool allowDeepBuffering; int64_t cachedDurationUs; bool eos; @@ -896,8 +933,64 @@ status_t AwesomePlayer::play_l() { } else { allowDeepBuffering = false; } - - mAudioPlayer = new AudioPlayer(mAudioSink, allowDeepBuffering, this); +#ifdef QCOM_ENHANCED_AUDIO +#ifdef USE_TUNNEL_MODE + // Create tunnel player if tunnel mode is enabled + ALOGW("Trying to create tunnel player mIsTunnelAudio %d, \ + LPAPlayer::objectsAlive %d, \ + TunnelPlayer::mTunnelObjectsAlive = %d,\ + (mAudioPlayer == NULL) %d", + mIsTunnelAudio, TunnelPlayer::mTunnelObjectsAlive, + LPAPlayer::objectsAlive,(mAudioPlayer == NULL)); + + if(mIsTunnelAudio && (mAudioPlayer == NULL) && + (LPAPlayer::objectsAlive == 0) && + (TunnelPlayer::mTunnelObjectsAlive == 0)) { + ALOGD("Tunnel player created for mime %s duration %lld\n",\ + mime, mDurationUs); + bool initCheck = false; + if(mVideoSource != NULL) { + // The parameter true is to inform tunnel player that + // clip is audio video + mAudioPlayer = new TunnelPlayer(mAudioSink, initCheck, + this, true); + } + else { + mAudioPlayer = new TunnelPlayer(mAudioSink, initCheck, + this); + } + if(!initCheck) { + ALOGE("deleting Tunnel Player - initCheck failed"); + delete mAudioPlayer; + mAudioPlayer = NULL; + } + } + tunnelObjectsAlive = (TunnelPlayer::mTunnelObjectsAlive); +#endif + char lpaDecode[128]; + property_get("lpa.decode",lpaDecode,"0"); + if((strcmp("true",lpaDecode) == 0) && (mAudioPlayer == NULL) && tunnelObjectsAlive==0 ) + { + ALOGV("LPAPlayer::getObjectsAlive() %d",LPAPlayer::objectsAlive); + if ( mDurationUs > 60000000 + && (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_MPEG) || !strcasecmp(mime,MEDIA_MIMETYPE_AUDIO_AAC)) + && LPAPlayer::objectsAlive == 0 && mVideoSource == NULL) { + ALOGD("LPAPlayer created, LPA MODE detected mime %s duration %lld", mime, mDurationUs); + bool initCheck = false; + mAudioPlayer = new LPAPlayer(mAudioSink, initCheck, this); + if(!initCheck) { + delete mAudioPlayer; + mAudioPlayer = NULL; + } + } + } + if(mAudioPlayer == NULL) { + ALOGV("AudioPlayer created, Non-LPA mode mime %s duration %lld\n", mime, mDurationUs); +#endif + mAudioPlayer = new AudioPlayer(mAudioSink, allowDeepBuffering, this); +#ifdef QCOM_ENHANCED_AUDIO + } +#endif mAudioPlayer->setSource(mAudioSource); mTimeSource = mAudioPlayer; @@ -915,9 +1008,14 @@ status_t AwesomePlayer::play_l() { if (mVideoSource == NULL) { // We don't want to post an error notification at this point, // the error returned from MediaPlayer::start() will suffice. - - status_t err = startAudioPlayer_l( - false /* sendErrorNotification */); + bool sendErrorNotification = false; +#ifdef QCOM_ENHANCED_AUDIO + if(mIsTunnelAudio) { + // For tunnel Audio error has to be posted to the client + sendErrorNotification = true; + } +#endif + status_t err = startAudioPlayer_l(sendErrorNotification); if (err != OK) { delete mAudioPlayer; @@ -1387,14 +1485,92 @@ status_t AwesomePlayer::initAudioDecoder() { const char *mime; CHECK(meta->findCString(kKeyMIMEType, &mime)); - +#ifdef QCOM_ENHANCED_AUDIO +#ifdef USE_TUNNEL_MODE + char value[PROPERTY_VALUE_MAX]; + char tunnelDecode[128]; + property_get("tunnel.decode",tunnelDecode,"0"); + // Enable tunnel mode for mp3 and aac and if the clip is not aac adif + // and if no other tunnel mode instances aare running. + ALOGD("Tunnel Mime Type: %s, object alive = %d, mTunnelAliveAP = %d",\ + mime, (TunnelPlayer::mTunnelObjectsAlive), mTunnelAliveAP); + if(((strcmp("true",tunnelDecode) == 0)||(atoi(tunnelDecode))) && + (TunnelPlayer::mTunnelObjectsAlive == 0) && + mTunnelAliveAP == 0 && + ((!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_MPEG)) || + (!strcasecmp(mime,MEDIA_MIMETYPE_AUDIO_AAC)))) { + + if(mVideoSource != NULL) { + char tunnelAVDecode[128]; + property_get("tunnel.audiovideo.decode",tunnelAVDecode,"0"); + if(((strncmp("true", tunnelAVDecode, 4) == 0)||(atoi(tunnelAVDecode)))) { + ALOGD("Enable Tunnel Mode for A-V playback"); + mIsTunnelAudio = true; + } + } + else { + ALOGI("Tunnel Mode Audio Enabled"); + mIsTunnelAudio = true; + } + } + else + ALOGD("Normal Audio Playback"); +#endif + if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_RAW) || + (mIsTunnelAudio && (mTunnelAliveAP == 0))) { + ALOGD("Set Audio Track as Audio Source"); + if(mIsTunnelAudio) { + mTunnelAliveAP++; + } +#else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_RAW)) { +#endif mAudioSource = mAudioTrack; } else { +#ifdef QCOM_ENHANCED_AUDIO + // For LPA Playback use the decoder without OMX layer + char *matchComponentName = NULL; + int64_t durationUs; + uint32_t flags = 0; + char lpaDecode[128]; + property_get("lpa.decode",lpaDecode,"0"); + if (mAudioTrack->getFormat()->findInt64(kKeyDuration, &durationUs)) { + Mutex::Autolock autoLock(mMiscStateLock); + if (mDurationUs < 0 || durationUs > mDurationUs) { + mDurationUs = durationUs; + } + } + if ( mDurationUs > 60000000 + && (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_MPEG) || !strcasecmp(mime,MEDIA_MIMETYPE_AUDIO_AAC)) + && LPAPlayer::objectsAlive == 0 && mVideoSource == NULL && (strcmp("true",lpaDecode) == 0)) { + char nonOMXDecoder[128]; + if(!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_MPEG)) { + ALOGD("matchComponentName is set to MP3Decoder %lld, mime %s",mDurationUs,mime); + property_get("use.non-omx.mp3.decoder",nonOMXDecoder,"0"); + if((strcmp("true",nonOMXDecoder) == 0)) { + matchComponentName = (char *) "MP3Decoder"; + } + } else if((!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AAC))) { + ALOGD("matchComponentName is set to AACDecoder %lld, mime %s",mDurationUs,mime); + property_get("use.non-omx.aac.decoder",nonOMXDecoder,"0"); + if((strcmp("true",nonOMXDecoder) == 0)) { + matchComponentName = (char *) "AACDecoder"; + } else { + matchComponentName = (char *) "OMX.google.aac.decoder"; + } + } + flags |= OMXCodec::kSoftwareCodecsOnly; + } + mAudioSource = OMXCodec::Create( + mClient.interface(), mAudioTrack->getFormat(), + false, // createEncoder + mAudioTrack, matchComponentName, flags,NULL); +#else mAudioSource = OMXCodec::Create( mClient.interface(), mAudioTrack->getFormat(), false, // createEncoder mAudioTrack); +#endif } if (mAudioSource != NULL) { diff --git a/media/libstagefright/DataSource.cpp b/media/libstagefright/DataSource.cpp index 9d0eea2..4ab602f 100644 --- a/media/libstagefright/DataSource.cpp +++ b/media/libstagefright/DataSource.cpp @@ -33,6 +33,9 @@ #include "include/OggExtractor.h" #include "include/WAVExtractor.h" #include "include/WVMExtractor.h" +#ifdef QCOM_HARDWARE +#include "include/ExtendedExtractor.h" +#endif #include "matroska/MatroskaExtractor.h" @@ -122,6 +125,9 @@ void DataSource::RegisterDefaultSniffers() { RegisterSniffer(SniffAAC); RegisterSniffer(SniffMPEG2PS); RegisterSniffer(SniffWVM); +#ifdef QCOM_HARDWARE + RegisterSniffer(SniffExtendedExtractor); +#endif char value[PROPERTY_VALUE_MAX]; if (property_get("drm.service.enabled", value, NULL) diff --git a/media/libstagefright/ExtendedExtractor.cpp b/media/libstagefright/ExtendedExtractor.cpp new file mode 100644 index 0000000..8e0d5d7 --- /dev/null +++ b/media/libstagefright/ExtendedExtractor.cpp @@ -0,0 +1,110 @@ +/*Copyright (c) 2012, The Linux Foundation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * * Neither the name of The Linux Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "ExtendedExtractor" +#include <utils/Log.h> +#include <dlfcn.h> // for dlopen/dlclose + +#include "include/ExtendedExtractor.h" + +static const char* EXTENDED_PARSER_LIB = "libExtendedExtractor.so"; + +namespace android { + +void* ExtendedParserLib() { + static void* extendedParserLib = NULL; + static bool alreadyTriedToOpenParsers = false; + + if(!alreadyTriedToOpenParsers) { + alreadyTriedToOpenParsers = true; + + extendedParserLib = ::dlopen(EXTENDED_PARSER_LIB, RTLD_LAZY); + + if(extendedParserLib == NULL) { + ALOGV("Failed to open EXTENDED_PARSER_LIB, dlerror = %s \n", dlerror()); + } + } + + return extendedParserLib; +} + +MediaExtractor* ExtendedExtractor::CreateExtractor(const sp<DataSource> &source, const char *mime) { + static MediaExtractorFactory mediaFactoryFunction = NULL; + static bool alreadyTriedToFindFactoryFunction = false; + + MediaExtractor* extractor = NULL; + + if(!alreadyTriedToFindFactoryFunction) { + + void *extendedParserLib = ExtendedParserLib(); + if (extendedParserLib != NULL) { + + mediaFactoryFunction = (MediaExtractorFactory) dlsym(extendedParserLib, MEDIA_CREATE_EXTRACTOR); + alreadyTriedToFindFactoryFunction = true; + } + } + + if(mediaFactoryFunction==NULL) { + ALOGE(" dlsym for ExtendedExtractor factory function failed, dlerror = %s \n", dlerror()); + return NULL; + } + + extractor = mediaFactoryFunction(source, mime); + if(extractor==NULL) { + ALOGE(" ExtendedExtractor failed to instantiate extractor \n"); + } + + return extractor; +} + +bool SniffExtendedExtractor(const sp<DataSource> &source, String8 *mimeType, + float *confidence,sp<AMessage> *meta) { + void *extendedParserLib = ExtendedParserLib(); + bool retVal = false; + if (extendedParserLib != NULL) { + ExtendedExtractorSniffers extendedExtractorSniffers= + (ExtendedExtractorSniffers) dlsym(extendedParserLib, EXTENDED_EXTRACTOR_SNIFFERS); + + if(extendedExtractorSniffers == NULL) { + ALOGE(" dlsym for extendedExtractorSniffers function failed, dlerror = %s \n", dlerror()); + return retVal; + } + + retVal = extendedExtractorSniffers(source, mimeType, confidence, meta); + + if(!retVal) { + ALOGV("ExtendedExtractor:: ExtendedExtractorSniffers Failed"); + } + } + return retVal; +} + +} // namespace android + + diff --git a/media/libstagefright/ExtendedWriter.cpp b/media/libstagefright/ExtendedWriter.cpp new file mode 100644 index 0000000..7c8b08e --- /dev/null +++ b/media/libstagefright/ExtendedWriter.cpp @@ -0,0 +1,390 @@ +/* + * Copyright (c) 2012, The Linux Foundation. All rights reserved. + * + * Not a Contribution, Apache license notifications and license are retained + * for attribution purposes only. + * 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. + */ + +#include <media/stagefright/ExtendedWriter.h> +#include <media/stagefright/MediaBuffer.h> +#include <media/stagefright/MediaDefs.h> +#include <media/stagefright/MediaErrors.h> +#include <media/stagefright/MediaSource.h> +#include <media/stagefright/MetaData.h> +#include <media/mediarecorder.h> +#include <system/audio.h> + +#include <sys/prctl.h> +#include <sys/resource.h> + +#include <arpa/inet.h> +#include <QCMediaDefs.h> + +#undef LOG_TAG +#define LOG_TAG "ExtendedWriter" + +namespace android { + +ExtendedWriter::ExtendedWriter(const char *filename) + : mFile(fopen(filename, "wb")), + mInitCheck(mFile != NULL ? OK : NO_INIT), + mStarted(false), + mPaused(false), + mResumed(false), + mOffset(0) { +} + +ExtendedWriter::ExtendedWriter(int fd) + : mFile(fdopen(fd, "wb")), + mInitCheck(mFile != NULL ? OK : NO_INIT), + mStarted(false), + mPaused(false), + mResumed(false), + mOffset(0) { +} + +ExtendedWriter::~ExtendedWriter() { + if (mStarted) { + stop(); + } + + if (mFile != NULL) { + fclose(mFile); + mFile = NULL; + } +} + +status_t ExtendedWriter::initCheck() const { + return mInitCheck; +} + +status_t ExtendedWriter::addSource(const sp<MediaSource> &source) { + if (mInitCheck != OK) { + ALOGE("Init Check not OK, return"); + return mInitCheck; + } + + if (mSource != NULL) { + ALOGE("A source already exists, return"); + return UNKNOWN_ERROR; + } + + sp<MetaData> meta = source->getFormat(); + + const char *mime; + CHECK(meta->findCString(kKeyMIMEType, &mime)); + + if ( !strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_QCELP)) { + mFormat = AUDIO_FORMAT_QCELP; + } else if ( !strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_EVRC)) { + mFormat = AUDIO_FORMAT_EVRC; + } + else { + return UNKNOWN_ERROR; + } + + int32_t channelCount; + int32_t sampleRate; + CHECK(meta->findInt32(kKeyChannelCount, &channelCount)); + CHECK_EQ(channelCount, 1); + CHECK(meta->findInt32(kKeySampleRate, &sampleRate)); + CHECK_EQ(sampleRate, 8000); + + mSource = source; + + return OK; +} + +status_t ExtendedWriter::start(MetaData *params) { + if (mInitCheck != OK) { + ALOGE("Init Check not OK, return"); + return mInitCheck; + } + + if (mSource == NULL) { + ALOGE("NULL Source"); + return UNKNOWN_ERROR; + } + + if (mStarted && mPaused) { + mPaused = false; + mResumed = true; + return OK; + } else if (mStarted) { + ALOGE("Already startd, return"); + return OK; + } + + //space for header; + size_t headerSize = sizeof( struct QCPEVRCHeader ); + uint8_t * header = (uint8_t *)malloc(headerSize); + memset( header, '?', headerSize); + fwrite( header, 1, headerSize, mFile ); + mOffset += headerSize; + delete header; + + status_t err = mSource->start(); + + if (err != OK) { + return err; + } + + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); + + mReachedEOS = false; + mDone = false; + + pthread_create(&mThread, &attr, ThreadWrapper, this); + pthread_attr_destroy(&attr); + + mStarted = true; + + return OK; +} + +status_t ExtendedWriter::pause() { + if (!mStarted) { + return OK; + } + mPaused = true; + return OK; +} + +status_t ExtendedWriter::stop() { + if (!mStarted) { + return OK; + } + + mDone = true; + + void *dummy; + pthread_join(mThread, &dummy); + + status_t err = (status_t) dummy; + { + status_t status = mSource->stop(); + if (err == OK && + (status != OK && status != ERROR_END_OF_STREAM)) { + err = status; + } + } + + mStarted = false; + return err; +} + +bool ExtendedWriter::exceedsFileSizeLimit() { + if (mMaxFileSizeLimitBytes == 0) { + return false; + } + return mEstimatedSizeBytes >= mMaxFileSizeLimitBytes; +} + +bool ExtendedWriter::exceedsFileDurationLimit() { + if (mMaxFileDurationLimitUs == 0) { + return false; + } + return mEstimatedDurationUs >= mMaxFileDurationLimitUs; +} + +// static +void *ExtendedWriter::ThreadWrapper(void *me) { + return (void *) static_cast<ExtendedWriter *>(me)->threadFunc(); +} + +status_t ExtendedWriter::threadFunc() { + mEstimatedDurationUs = 0; + mEstimatedSizeBytes = 0; + bool stoppedPrematurely = true; + int64_t previousPausedDurationUs = 0; + int64_t maxTimestampUs = 0; + status_t err = OK; + + pid_t tid = gettid(); + androidSetThreadPriority(tid, ANDROID_PRIORITY_AUDIO); + prctl(PR_SET_NAME, (unsigned long)"ExtendedWriter", 0, 0, 0); + while (!mDone) { + MediaBuffer *buffer; + err = mSource->read(&buffer); + + if (err != OK) { + break; + } + + if (mPaused) { + buffer->release(); + buffer = NULL; + continue; + } + + mEstimatedSizeBytes += buffer->range_length(); + if (exceedsFileSizeLimit()) { + buffer->release(); + buffer = NULL; + notify(MEDIA_RECORDER_EVENT_INFO, MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED, 0); + break; + } + + int64_t timestampUs; + CHECK(buffer->meta_data()->findInt64(kKeyTime, ×tampUs)); + if (timestampUs > mEstimatedDurationUs) { + mEstimatedDurationUs = timestampUs; + } + if (mResumed) { + previousPausedDurationUs += (timestampUs - maxTimestampUs - 20000); + mResumed = false; + } + timestampUs -= previousPausedDurationUs; + ALOGV("time stamp: %lld, previous paused duration: %lld", + timestampUs, previousPausedDurationUs); + if (timestampUs > maxTimestampUs) { + maxTimestampUs = timestampUs; + } + + if (exceedsFileDurationLimit()) { + buffer->release(); + buffer = NULL; + notify(MEDIA_RECORDER_EVENT_INFO, MEDIA_RECORDER_INFO_MAX_DURATION_REACHED, 0); + break; + } + ssize_t n = fwrite( + (const uint8_t *)buffer->data() + buffer->range_offset(), + 1, + buffer->range_length(), + mFile); + mOffset += n; + + if (n < (ssize_t)buffer->range_length()) { + buffer->release(); + buffer = NULL; + + break; + } + + // XXX: How to tell it is stopped prematurely? + if (stoppedPrematurely) { + stoppedPrematurely = false; + } + + buffer->release(); + buffer = NULL; + } + + if (stoppedPrematurely) { + notify(MEDIA_RECORDER_EVENT_INFO, MEDIA_RECORDER_TRACK_INFO_COMPLETION_STATUS, UNKNOWN_ERROR); + } + + if ( mFormat == AUDIO_FORMAT_QCELP ) { + writeQCPHeader( ); + } + else if ( mFormat == AUDIO_FORMAT_EVRC ) { + writeEVRCHeader( ); + } + + fflush(mFile); + fclose(mFile); + mFile = NULL; + mReachedEOS = true; + if (err == ERROR_END_OF_STREAM || (err == -ETIMEDOUT)) { + return OK; + } + return err; +} + +bool ExtendedWriter::reachedEOS() { + return mReachedEOS; +} + +status_t ExtendedWriter::writeQCPHeader() { + /* Common part */ + struct QCPEVRCHeader header = { + {'R', 'I', 'F', 'F'}, 0, {'Q', 'L', 'C', 'M'}, /* Riff */ + {'f', 'm', 't', ' '}, 150, 1, 0, 0, 0, 0,{0}, 0, {0},0,0,160,8000,16,0,{0},{0},{0}, /* Fmt */ + {'v','r','a','t'}, 0, 0, 0, /* Vrat */ + {'d','a','t','a'},0 /* Data */ + }; + + fseeko(mFile, 0, SEEK_SET); + header.s_riff = (mOffset - 8); + header.data1 = (0x5E7F6D41); + header.data2 = (0xB115); + header.data3 = (0x11D0); + header.data4[0] = 0xBA; + header.data4[1] = 0x91; + header.data4[2] = 0x00; + header.data4[3] = 0x80; + header.data4[4] = 0x5F; + header.data4[5] = 0xB4; + header.data4[6] = 0xB9; + header.data4[7] = 0x7E; + header.ver = (0x0002); + memcpy(header.name, "Qcelp 13K", 9); + header.abps = (13000); + header.bytes_per_pkt = (35); + header.vr_num_of_rates = 5; + header.vr_bytes_per_pkt[0] = (0x0422); + header.vr_bytes_per_pkt[1] = (0x0310); + header.vr_bytes_per_pkt[2] = (0x0207); + header.vr_bytes_per_pkt[3] = (0x0103); + header.s_vrat = (0x00000008); + header.v_rate = (0x00000001); + header.size_in_pkts = (mOffset - sizeof( struct QCPEVRCHeader ))/ header.bytes_per_pkt; + header.s_data = mOffset - sizeof( struct QCPEVRCHeader ); + fwrite( &header, 1, sizeof( struct QCPEVRCHeader ), mFile ); + return OK; +} + +status_t ExtendedWriter::writeEVRCHeader() { + /* Common part */ + struct QCPEVRCHeader header = { + {'R', 'I', 'F', 'F'}, 0, {'Q', 'L', 'C', 'M'}, /* Riff */ + {'f', 'm', 't', ' '}, 150, 1, 0, 0, 0, 0,{0}, 0, {0},0,0,160,8000,16,0,{0},{0},{0}, /* Fmt */ + {'v','r','a','t'}, 0, 0, 0, /* Vrat */ + {'d','a','t','a'},0 /* Data */ + }; + + fseeko(mFile, 0, SEEK_SET); + header.s_riff = (mOffset - 8); + header.data1 = (0xe689d48d); + header.data2 = (0x9076); + header.data3 = (0x46b5); + header.data4[0] = 0x91; + header.data4[1] = 0xef; + header.data4[2] = 0x73; + header.data4[3] = 0x6a; + header.data4[4] = 0x51; + header.data4[5] = 0x00; + header.data4[6] = 0xce; + header.data4[7] = 0xb4; + header.ver = (0x0001); + memcpy(header.name, "TIA IS-127 Enhanced Variable Rate Codec, Speech Service Option 3", 64); + header.abps = (9600); + header.bytes_per_pkt = (23); + header.vr_num_of_rates = 4; + header.vr_bytes_per_pkt[0] = (0x0416); + header.vr_bytes_per_pkt[1] = (0x030a); + header.vr_bytes_per_pkt[2] = (0x0200); + header.vr_bytes_per_pkt[3] = (0x0102); + header.s_vrat = (0x00000008); + header.v_rate = (0x00000001); + header.size_in_pkts = (mOffset - sizeof( struct QCPEVRCHeader )) / header.bytes_per_pkt; + header.s_data = mOffset - sizeof( struct QCPEVRCHeader ); + fwrite( &header, 1, sizeof( struct QCPEVRCHeader ), mFile ); + return OK; +} + + +} // namespace android diff --git a/media/libstagefright/LPAPlayerALSA.cpp b/media/libstagefright/LPAPlayerALSA.cpp new file mode 100644 index 0000000..db3a022 --- /dev/null +++ b/media/libstagefright/LPAPlayerALSA.cpp @@ -0,0 +1,791 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * Copyright (c) 2009-2012, The Linux Foundation. All rights reserved. + * Not a Contribution, Apache license notifications and license are retained + * for attribution purposes only. + * + * + * 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_NDDEBUG 0 +#define LOG_NDEBUG 0 +#define LOG_TAG "LPAPlayerALSA" + +#include <utils/Log.h> +#include <utils/threads.h> + +#include <signal.h> +#include <sys/prctl.h> +#include <sys/resource.h> +#include <sys/poll.h> +#include <sys/eventfd.h> +#include <binder/IPCThreadState.h> +#include <media/AudioTrack.h> + +#include <media/stagefright/LPAPlayer.h> +#include <media/stagefright/MediaDefs.h> +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/foundation/ALooper.h> +#include <media/stagefright/MediaErrors.h> +#include <media/stagefright/MediaSource.h> +#include <media/stagefright/MetaData.h> +#include <media/stagefright/MediaErrors.h> + +#include <hardware_legacy/power.h> + +#include <linux/unistd.h> + +#include "include/AwesomePlayer.h" +#include <powermanager/PowerManager.h> + +static const char mName[] = "LPAPlayer"; + +#define MEM_BUFFER_SIZE 262144 +#define MEM_BUFFER_COUNT 4 + +#define PCM_FORMAT 2 +#define NUM_FDS 2 +namespace android { +int LPAPlayer::objectsAlive = 0; + +LPAPlayer::LPAPlayer( + const sp<MediaPlayerBase::AudioSink> &audioSink, bool &initCheck, + AwesomePlayer *observer) +:AudioPlayer(audioSink,observer), +mPositionTimeMediaUs(-1), +mPositionTimeRealUs(-1), +mInternalSeeking(false), +mStarted(false), +mA2DPEnabled(false), +mSampleRate(0), +mLatencyUs(0), +mFrameSize(0), +mNumFramesPlayed(0), +mNumFramesPlayedSysTimeUs(0), +mInputBuffer(NULL), +mSeeking(false), +mReachedEOS(false), +mReachedOutputEOS(false), +mFinalStatus(OK), +mSeekTimeUs(0), +mPauseTime(0), +mIsFirstBuffer(false), +mFirstBufferResult(OK), +mFirstBuffer(NULL), +mAudioSink(audioSink), +mObserver(observer) { + ALOGV("LPAPlayer::LPAPlayer() ctor"); + objectsAlive++; + numChannels =0; + mPaused = false; + mIsA2DPEnabled = false; + mAudioFlinger = NULL; + AudioFlingerClient = NULL; + /* Initialize Suspend/Resume related variables */ + mQueue.start(); + mQueueStarted = true; + mPauseEvent = new TimedEvent(this, &LPAPlayer::onPauseTimeOut); + mPauseEventPending = false; + getAudioFlinger(); + ALOGV("Registering client with AudioFlinger"); + //mAudioFlinger->registerClient(AudioFlingerClient); + + mIsAudioRouted = false; + + initCheck = true; + +} + +LPAPlayer::~LPAPlayer() { + ALOGV("LPAPlayer::~LPAPlayer()"); + if (mQueueStarted) { + mQueue.stop(); + } + + reset(); + + //mAudioFlinger->deregisterClient(AudioFlingerClient); + objectsAlive--; +} + +void LPAPlayer::getAudioFlinger() { + Mutex::Autolock _l(AudioFlingerLock); + + if ( mAudioFlinger.get() == 0 ) { + sp<IServiceManager> sm = defaultServiceManager(); + sp<IBinder> binder; + do { + binder = sm->getService(String16("media.audio_flinger")); + if ( binder != 0 ) + break; + ALOGW("AudioFlinger not published, waiting..."); + usleep(500000); // 0.5 s + } while ( true ); + if ( AudioFlingerClient == NULL ) { + AudioFlingerClient = new AudioFlingerLPAdecodeClient(this); + } + + binder->linkToDeath(AudioFlingerClient); + mAudioFlinger = interface_cast<IAudioFlinger>(binder); + } + ALOGE_IF(mAudioFlinger==0, "no AudioFlinger!?"); +} + +LPAPlayer::AudioFlingerLPAdecodeClient::AudioFlingerLPAdecodeClient(void *obj) +{ + ALOGV("LPAPlayer::AudioFlingerLPAdecodeClient::AudioFlingerLPAdecodeClient"); + pBaseClass = (LPAPlayer*)obj; +} + +void LPAPlayer::AudioFlingerLPAdecodeClient::binderDied(const wp<IBinder>& who) { + Mutex::Autolock _l(pBaseClass->AudioFlingerLock); + + pBaseClass->mAudioFlinger.clear(); + ALOGW("AudioFlinger server died!"); +} + +void LPAPlayer::AudioFlingerLPAdecodeClient::ioConfigChanged(int event, audio_io_handle_t ioHandle, const void *param2) { + ALOGV("ioConfigChanged() event %d", event); + /* + if ( event != AudioSystem::A2DP_OUTPUT_STATE && + event != AudioSystem::EFFECT_CONFIG_CHANGED) { + return; + } + + switch ( event ) { + case AudioSystem::A2DP_OUTPUT_STATE: + { + ALOGV("ioConfigChanged() A2DP_OUTPUT_STATE iohandle is %d with A2DPEnabled in %d", ioHandle, pBaseClass->mIsA2DPEnabled); + if ( -1 == ioHandle ) { + if ( pBaseClass->mIsA2DPEnabled ) { + pBaseClass->mIsA2DPEnabled = false; + if (pBaseClass->mStarted) { + pBaseClass->handleA2DPSwitch(); + } + ALOGV("ioConfigChanged:: A2DP Disabled"); + } + } else { + if ( !pBaseClass->mIsA2DPEnabled ) { + + pBaseClass->mIsA2DPEnabled = true; + if (pBaseClass->mStarted) { + pBaseClass->handleA2DPSwitch(); + } + + ALOGV("ioConfigChanged:: A2DP Enabled"); + } + } + } + break; + } + ALOGV("ioConfigChanged Out"); + */ +} + +void LPAPlayer::handleA2DPSwitch() { + //TODO: Implement +} + +void LPAPlayer::setSource(const sp<MediaSource> &source) { + CHECK(mSource == NULL); + ALOGV("Setting source from LPA Player"); + mSource = source; +} + +status_t LPAPlayer::start(bool sourceAlreadyStarted) { + CHECK(!mStarted); + CHECK(mSource != NULL); + + ALOGV("start: sourceAlreadyStarted %d", sourceAlreadyStarted); + //Check if the source is started, start it + status_t err; + if (!sourceAlreadyStarted) { + err = mSource->start(); + + if (err != OK) { + return err; + } + } + + //Create decoder and a2dp notification thread and initialize all the + //mutexes and coditional variables + createThreads(); + ALOGV("All Threads Created."); + + // We allow an optional INFO_FORMAT_CHANGED at the very beginning + // of playback, if there is one, getFormat below will retrieve the + // updated format, if there isn't, we'll stash away the valid buffer + // of data to be used on the first audio callback. + + CHECK(mFirstBuffer == NULL); + + MediaSource::ReadOptions options; + if (mSeeking) { + options.setSeekTo(mSeekTimeUs); + mSeeking = false; + } + + mFirstBufferResult = mSource->read(&mFirstBuffer, &options); + if (mFirstBufferResult == INFO_FORMAT_CHANGED) { + ALOGV("INFO_FORMAT_CHANGED!!!"); + CHECK(mFirstBuffer == NULL); + mFirstBufferResult = OK; + mIsFirstBuffer = false; + } else { + mIsFirstBuffer = true; + } + + sp<MetaData> format = mSource->getFormat(); + const char *mime; + + bool success = format->findCString(kKeyMIMEType, &mime); + CHECK(success); + CHECK(!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_RAW)); + + success = format->findInt32(kKeySampleRate, &mSampleRate); + CHECK(success); + + success = format->findInt32(kKeyChannelCount, &numChannels); + CHECK(success); + + if(!format->findInt32(kKeyChannelMask, &mChannelMask)) { + // log only when there's a risk of ambiguity of channel mask selection + ALOGI_IF(numChannels > 2, + "source format didn't specify channel mask, using (%d) channel order", numChannels); + mChannelMask = CHANNEL_MASK_USE_CHANNEL_ORDER; + } + audio_output_flags_t flags = (audio_output_flags_t) (AUDIO_OUTPUT_FLAG_LPA | + AUDIO_OUTPUT_FLAG_DIRECT); + ALOGV("mAudiosink->open() mSampleRate %d, numChannels %d, mChannelMask %d, flags %d",mSampleRate, numChannels, mChannelMask, flags); + err = mAudioSink->open( + mSampleRate, numChannels, mChannelMask, AUDIO_FORMAT_PCM_16_BIT, + DEFAULT_AUDIOSINK_BUFFERCOUNT, + &LPAPlayer::AudioSinkCallback, + this, + (mA2DPEnabled ? AUDIO_OUTPUT_FLAG_NONE : flags)); + + if (err != OK) { + if (mFirstBuffer != NULL) { + mFirstBuffer->release(); + mFirstBuffer = NULL; + } + + if (!sourceAlreadyStarted) { + mSource->stop(); + } + + ALOGE("Opening a routing session failed"); + return err; + } + + mIsAudioRouted = true; + mStarted = true; + mAudioSink->start(); + ALOGV("Waking up decoder thread"); + pthread_cond_signal(&decoder_cv); + + return OK; +} + +status_t LPAPlayer::seekTo(int64_t time_us) { + Mutex::Autolock autoLock(mLock); + ALOGV("seekTo: time_us %lld", time_us); + if ( mReachedEOS ) { + mReachedEOS = false; + mReachedOutputEOS = false; + } + mSeeking = true; + mSeekTimeUs = time_us; + mPauseTime = mSeekTimeUs; + ALOGV("In seekTo(), mSeekTimeUs %lld",mSeekTimeUs); + mAudioSink->flush(); + pthread_cond_signal(&decoder_cv); + return OK; +} + +void LPAPlayer::pause(bool playPendingSamples) { + CHECK(mStarted); + if (mPaused) { + return; + } + ALOGV("pause: playPendingSamples %d", playPendingSamples); + mPaused = true; + A2DPState state; + if (playPendingSamples) { + if (!mIsA2DPEnabled) { + if (!mPauseEventPending) { + ALOGV("Posting an event for Pause timeout"); + mQueue.postEventWithDelay(mPauseEvent, LPA_PAUSE_TIMEOUT_USEC); + mPauseEventPending = true; + } + mPauseTime = mSeekTimeUs + getTimeStamp(A2DP_DISABLED); + } + else { + mPauseTime = mSeekTimeUs + getTimeStamp(A2DP_ENABLED); + } + if (mAudioSink.get() != NULL) + mAudioSink->pause(); + } else { + if (!mIsA2DPEnabled) { + if(!mPauseEventPending) { + ALOGV("Posting an event for Pause timeout"); + mQueue.postEventWithDelay(mPauseEvent, LPA_PAUSE_TIMEOUT_USEC); + mPauseEventPending = true; + } + mPauseTime = mSeekTimeUs + getTimeStamp(A2DP_DISABLED); + } else { + mPauseTime = mSeekTimeUs + getTimeStamp(A2DP_ENABLED); + } + if (mAudioSink.get() != NULL) { + ALOGV("AudioSink pause"); + mAudioSink->pause(); + } + } +} + +void LPAPlayer::resume() { + ALOGV("resume: mPaused %d",mPaused); + Mutex::Autolock autoLock(mResumeLock); + if ( mPaused) { + CHECK(mStarted); + if (!mIsA2DPEnabled) { + if(mPauseEventPending) { + ALOGV("Resume(): Cancelling the puaseTimeout event"); + mPauseEventPending = false; + mQueue.cancelEvent(mPauseEvent->eventID()); + } + + } + + if (!mIsAudioRouted) { + audio_output_flags_t flags = (audio_output_flags_t) (AUDIO_OUTPUT_FLAG_LPA | + AUDIO_OUTPUT_FLAG_DIRECT); + status_t err = mAudioSink->open( + mSampleRate, numChannels, mChannelMask, AUDIO_FORMAT_PCM_16_BIT, + DEFAULT_AUDIOSINK_BUFFERCOUNT, + &LPAPlayer::AudioSinkCallback, + this, + (mA2DPEnabled ? AUDIO_OUTPUT_FLAG_NONE : flags )); + if (err != NO_ERROR) { + ALOGE("Audio sink open failed."); + } + mIsAudioRouted = true; + } + mPaused = false; + mAudioSink->start(); + pthread_cond_signal(&decoder_cv); + } +} + +//static +size_t LPAPlayer::AudioSinkCallback( + MediaPlayerBase::AudioSink *audioSink, + void *buffer, size_t size, void *cookie) { + if (buffer == NULL && size == AudioTrack::EVENT_UNDERRUN) { + LPAPlayer *me = (LPAPlayer *)cookie; + me->mReachedEOS = true; + me->mReachedOutputEOS = true; + ALOGV("postAudioEOS"); + me->mObserver->postAudioEOS(0); + } + return 1; +} + +void LPAPlayer::reset() { + + ALOGV("Reset"); + // Close the audiosink after all the threads exited to make sure + mReachedEOS = true; + + // make sure Decoder thread has exited + ALOGV("Closing all the threads"); + requestAndWaitForDecoderThreadExit(); + requestAndWaitForA2DPNotificationThreadExit(); + + ALOGV("Close the Sink"); + if (mIsAudioRouted) { + mAudioSink->stop(); + mAudioSink->close(); + mAudioSink.clear(); + } + // Make sure to release any buffer we hold onto so that the + // source is able to stop(). + if (mFirstBuffer != NULL) { + mFirstBuffer->release(); + mFirstBuffer = NULL; + } + + if (mInputBuffer != NULL) { + ALOGV("AudioPlayer releasing input buffer."); + mInputBuffer->release(); + mInputBuffer = NULL; + } + + mSource->stop(); + + // The following hack is necessary to ensure that the OMX + // component is completely released by the time we may try + // to instantiate it again. + wp<MediaSource> tmp = mSource; + mSource.clear(); + while (tmp.promote() != NULL) { + usleep(1000); + } + + mPositionTimeMediaUs = -1; + mPositionTimeRealUs = -1; + mSeeking = false; + mReachedEOS = false; + mReachedOutputEOS = false; + mFinalStatus = OK; + mStarted = false; +} + + +bool LPAPlayer::isSeeking() { + Mutex::Autolock autoLock(mLock); + return mSeeking; +} + +bool LPAPlayer::reachedEOS(status_t *finalStatus) { + *finalStatus = OK; + Mutex::Autolock autoLock(mLock); + *finalStatus = mFinalStatus; + return mReachedOutputEOS; +} + + +void *LPAPlayer::decoderThreadWrapper(void *me) { + static_cast<LPAPlayer *>(me)->decoderThreadEntry(); + return NULL; +} + + +void LPAPlayer::decoderThreadEntry() { + + pthread_mutex_lock(&decoder_mutex); + + pid_t tid = gettid(); + androidSetThreadPriority(tid, ANDROID_PRIORITY_AUDIO); + prctl(PR_SET_NAME, (unsigned long)"LPA DecodeThread", 0, 0, 0); + + ALOGV("decoderThreadEntry wait for signal \n"); + if (!mStarted) { + pthread_cond_wait(&decoder_cv, &decoder_mutex); + } + ALOGV("decoderThreadEntry ready to work \n"); + pthread_mutex_unlock(&decoder_mutex); + if (killDecoderThread) { + return; + } + void* local_buf = malloc(MEM_BUFFER_SIZE); + int bytesWritten = 0; + while (!killDecoderThread) { + + if (mReachedEOS || mPaused || !mIsAudioRouted) { + pthread_mutex_lock(&decoder_mutex); + pthread_cond_wait(&decoder_cv, &decoder_mutex); + pthread_mutex_unlock(&decoder_mutex); + continue; + } + + if (!mIsA2DPEnabled) { + ALOGV("FillBuffer: MemBuffer size %d", MEM_BUFFER_SIZE); + ALOGV("Fillbuffer started"); + //TODO: Add memset + bytesWritten = fillBuffer(local_buf, MEM_BUFFER_SIZE); + ALOGV("FillBuffer completed bytesToWrite %d", bytesWritten); + + if(!killDecoderThread) { + mAudioSink->write(local_buf, bytesWritten); + } + } + } + + free(local_buf); + + //TODO: Call fillbuffer with different size and write to mAudioSink() +} + +void *LPAPlayer::A2DPNotificationThreadWrapper(void *me) { + static_cast<LPAPlayer *>(me)->A2DPNotificationThreadEntry(); + return NULL; +} + +void LPAPlayer::A2DPNotificationThreadEntry() { + while (1) { + pthread_mutex_lock(&a2dp_notification_mutex); + pthread_cond_wait(&a2dp_notification_cv, &a2dp_notification_mutex); + pthread_mutex_unlock(&a2dp_notification_mutex); + if (killA2DPNotificationThread) { + break; + } + + ALOGV("A2DP notification has come mIsA2DPEnabled: %d", mIsA2DPEnabled); + + if (mIsA2DPEnabled) { + //TODO: + } + else { + //TODO + } + } + a2dpNotificationThreadAlive = false; + ALOGV("A2DPNotificationThread is dying"); + +} + +void LPAPlayer::createThreads() { + + //Initialize all the Mutexes and Condition Variables + pthread_mutex_init(&decoder_mutex, NULL); + pthread_mutex_init(&a2dp_notification_mutex, NULL); + pthread_cond_init (&decoder_cv, NULL); + pthread_cond_init (&a2dp_notification_cv, NULL); + + // Create 4 threads Effect, decoder, event and A2dp + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); + + killDecoderThread = false; + killA2DPNotificationThread = false; + + decoderThreadAlive = true; + a2dpNotificationThreadAlive = true; + + ALOGV("Creating decoder Thread"); + pthread_create(&decoderThread, &attr, decoderThreadWrapper, this); + + ALOGV("Creating A2DP Notification Thread"); + pthread_create(&A2DPNotificationThread, &attr, A2DPNotificationThreadWrapper, this); + + pthread_attr_destroy(&attr); +} + +size_t LPAPlayer::fillBuffer(void *data, size_t size) { + + if (mReachedEOS) { + return 0; + } + + bool postSeekComplete = false; + + size_t size_done = 0; + size_t size_remaining = size; + while (size_remaining > 0) { + MediaSource::ReadOptions options; + + { + Mutex::Autolock autoLock(mLock); + + if (mSeeking) { + mInternalSeeking = false; + } + if (mSeeking || mInternalSeeking) { + if (mIsFirstBuffer) { + if (mFirstBuffer != NULL) { + mFirstBuffer->release(); + mFirstBuffer = NULL; + } + mIsFirstBuffer = false; + } + + options.setSeekTo(mSeekTimeUs); + + if (mInputBuffer != NULL) { + mInputBuffer->release(); + mInputBuffer = NULL; + } + + size_remaining = size; + size_done = 0; + + mSeeking = false; + if (mObserver && !mInternalSeeking) { + ALOGV("fillBuffer: Posting audio seek complete event"); + postSeekComplete = true; + } + mInternalSeeking = false; + } + } + + if (mInputBuffer == NULL) { + status_t err; + + if (mIsFirstBuffer) { + mInputBuffer = mFirstBuffer; + mFirstBuffer = NULL; + err = mFirstBufferResult; + + mIsFirstBuffer = false; + } else { + err = mSource->read(&mInputBuffer, &options); + } + + CHECK((err == OK && mInputBuffer != NULL) + || (err != OK && mInputBuffer == NULL)); + + Mutex::Autolock autoLock(mLock); + + if (err != OK) { + mReachedEOS = true; + mFinalStatus = err; + break; + } + + CHECK(mInputBuffer->meta_data()->findInt64( + kKeyTime, &mPositionTimeMediaUs)); + mFrameSize = mAudioSink->frameSize(); + mPositionTimeRealUs = + ((mNumFramesPlayed + size_done / mFrameSize) * 1000000) + / mSampleRate; + } + if (mInputBuffer->range_length() == 0) { + mInputBuffer->release(); + mInputBuffer = NULL; + + continue; + } + + size_t copy = size_remaining; + if (copy > mInputBuffer->range_length()) { + copy = mInputBuffer->range_length(); + } + + memcpy((char *)data + size_done, + (const char *)mInputBuffer->data() + mInputBuffer->range_offset(), + copy); + + mInputBuffer->set_range(mInputBuffer->range_offset() + copy, + mInputBuffer->range_length() - copy); + + size_done += copy; + size_remaining -= copy; + } + + if (postSeekComplete) { + mObserver->postAudioSeekComplete(); + } + + return size_done; +} + +int64_t LPAPlayer::getRealTimeUs() { + Mutex::Autolock autoLock(mLock); + return getRealTimeUsLocked(); +} + + +int64_t LPAPlayer::getRealTimeUsLocked(){ + //Used for AV sync: irrelevant API for LPA. + return 0; +} + +int64_t LPAPlayer::getTimeStamp(A2DPState state) { + uint64_t timestamp = 0; + switch (state) { + case A2DP_ENABLED: + case A2DP_DISCONNECT: + ALOGV("Get timestamp for A2DP"); + break; + case A2DP_DISABLED: + case A2DP_CONNECT: { + mAudioSink->getTimeStamp(×tamp); + break; + } + default: + break; + } + ALOGV("timestamp %lld ", timestamp); + return timestamp; +} + +int64_t LPAPlayer::getMediaTimeUs() { + Mutex::Autolock autoLock(mLock); + ALOGV("getMediaTimeUs() mPaused %d mSeekTimeUs %lld mPauseTime %lld", mPaused, mSeekTimeUs, mPauseTime); + if (mPaused) { + return mPauseTime; + } else { + A2DPState state = mIsA2DPEnabled ? A2DP_ENABLED : A2DP_DISABLED; + return (mSeekTimeUs + getTimeStamp(state)); + } +} + +bool LPAPlayer::getMediaTimeMapping( + int64_t *realtime_us, int64_t *mediatime_us) { + Mutex::Autolock autoLock(mLock); + + *realtime_us = mPositionTimeRealUs; + *mediatime_us = mPositionTimeMediaUs; + + return mPositionTimeRealUs != -1 && mPositionTimeMediaUs != -1; +} + +void LPAPlayer::requestAndWaitForDecoderThreadExit() { + + if (!decoderThreadAlive) + return; + killDecoderThread = true; + + /* Flush the audio sink to unblock the decoder thread + if any write to audio HAL is blocked */ + if (!mReachedOutputEOS && mIsAudioRouted) + mAudioSink->flush(); + + pthread_cond_signal(&decoder_cv); + pthread_join(decoderThread,NULL); + ALOGV("decoder thread killed"); + +} + +void LPAPlayer::requestAndWaitForA2DPNotificationThreadExit() { + if (!a2dpNotificationThreadAlive) + return; + killA2DPNotificationThread = true; + pthread_cond_signal(&a2dp_notification_cv); + pthread_join(A2DPNotificationThread,NULL); + ALOGV("a2dp notification thread killed"); +} + +void LPAPlayer::onPauseTimeOut() { + ALOGV("onPauseTimeOut"); + Mutex::Autolock autoLock(mResumeLock); + if (!mPauseEventPending) { + return; + } + mPauseEventPending = false; + if(!mIsA2DPEnabled) { + // 1.) Set seek flags + mReachedEOS = false; + mReachedOutputEOS = false; + if(mSeeking == false) { + mSeekTimeUs += getTimeStamp(A2DP_DISABLED); + mInternalSeeking = true; + } else { + //do not update seek time if user has already seeked + // to a new position + // also seek has to be posted back to player, + // so do not set mInternalSeeking flag + ALOGV("do not update seek time %lld ", mSeekTimeUs); + } + ALOGV("newseek time = %lld ", mSeekTimeUs); + // 2.) Close routing Session + mAudioSink->close(); + mIsAudioRouted = false; + } + +} + +} //namespace android diff --git a/media/libstagefright/MPEG4Extractor.cpp b/media/libstagefright/MPEG4Extractor.cpp index dc8e4a3..62ba826 100644 --- a/media/libstagefright/MPEG4Extractor.cpp +++ b/media/libstagefright/MPEG4Extractor.cpp @@ -40,6 +40,9 @@ #include <media/stagefright/MetaData.h> #include <media/stagefright/Utils.h> #include <utils/String8.h> +#ifdef QCOM_HARDWARE +#include <QCMediaDefs.h> +#endif namespace android { @@ -257,6 +260,28 @@ static const char *FourCC2MIME(uint32_t fourcc) { case FOURCC('a', 'v', 'c', '1'): return MEDIA_MIMETYPE_VIDEO_AVC; +#ifdef QCOM_HARDWARE + case FOURCC('s', 'q', 'c', 'p'): + return MEDIA_MIMETYPE_AUDIO_QCELP; + + case FOURCC('s', 'e', 'v', 'c'): + return MEDIA_MIMETYPE_AUDIO_EVRC; + + case FOURCC('d', 't', 's', 'c'): + case FOURCC('d', 't', 's', 'h'): + case FOURCC('d', 't', 's', 'l'): + return MEDIA_MIMETYPE_AUDIO_DTS; + + case FOURCC('d', 't', 's', 'e'): + return MEDIA_MIMETYPE_AUDIO_DTS_LBR; + + case FOURCC('a', 'c', '-', '3'): + return MEDIA_MIMETYPE_AUDIO_AC3; + + case FOURCC('e', 'c', '-', '3'): + return MEDIA_MIMETYPE_AUDIO_EAC3; +#endif + default: CHECK(!"should not be here."); return NULL; @@ -921,6 +946,17 @@ status_t MPEG4Extractor::parseChunk(off64_t *offset, int depth) { case FOURCC('m', 'p', '4', 'a'): case FOURCC('s', 'a', 'm', 'r'): case FOURCC('s', 'a', 'w', 'b'): +#ifdef QCOM_HARDWARE + case FOURCC('.', 'm', 'p', '3'): + case FOURCC('s', 'e', 'v', 'c'): + case FOURCC('s', 'q', 'c', 'p'): + case FOURCC('d', 't', 's', 'c'): + case FOURCC('d', 't', 's', 'h'): + case FOURCC('d', 't', 's', 'l'): + case FOURCC('d', 't', 's', 'e'): + case FOURCC('a', 'c', '-', '3'): + case FOURCC('e', 'c', '-', '3'): +#endif { uint8_t buffer[8 + 20]; if (chunk_data_size < (ssize_t)sizeof(buffer)) { @@ -961,7 +997,16 @@ status_t MPEG4Extractor::parseChunk(off64_t *offset, int depth) { mLastTrack->meta->setInt32(kKeySampleRate, sample_rate); off64_t stop_offset = *offset + chunk_size; - *offset = data_offset + sizeof(buffer); +#ifdef QCOM_HARDWARE + if (!strcasecmp(MEDIA_MIMETYPE_AUDIO_MPEG, FourCC2MIME(chunk_type)) || + !strcasecmp(MEDIA_MIMETYPE_AUDIO_AMR_WB, FourCC2MIME(chunk_type))) { + // ESD is not required in mp3 + // amr wb with damr atom corrupted can cause the clip to not play + *offset = stop_offset; + } else +#endif + *offset = data_offset + sizeof(buffer); + while (*offset < stop_offset) { status_t err = parseChunk(offset, depth + 1); if (err != OK) { @@ -1219,6 +1264,18 @@ status_t MPEG4Extractor::parseChunk(off64_t *offset, int depth) { break; } +#ifdef QCOM_HARDWARE + case FOURCC('d', 'd', 't', 's'): + case FOURCC('d', 'a', 'c', '3'): + case FOURCC('d', 'e', 'c', '3'): + { + //no information need to be passed here, just log and end + ALOGV("ddts/dac3/dec3 pass from mpeg4 extractor"); + *offset += chunk_size; + break; + } +#endif + case FOURCC('a', 'v', 'c', 'C'): { char buffer[256]; @@ -1799,6 +1856,14 @@ status_t MPEG4Extractor::updateAudioTrackInfoFromESDS_MPEG4Audio( return ERROR_MALFORMED; } +#ifdef QCOM_HARDWARE + if (objectTypeIndication == 0xA0) { + // This isn't MPEG4 audio at all, it's EVRC + mLastTrack->meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_EVRC); + return OK; + } +#endif + if (objectTypeIndication == 0xe1) { // This isn't MPEG4 audio at all, it's QCELP 14k... mLastTrack->meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_QCELP); @@ -2313,6 +2378,9 @@ static bool LegacySniffMPEG4( } if (!memcmp(header, "ftyp3gp", 7) || !memcmp(header, "ftypmp42", 8) +#ifdef QCOM_HARDWARE + || !memcmp(header, "ftyp3g2a", 8) || !memcmp(header, "ftyp3g2b", 8) || !memcmp(header, "ftyp3g2c", 8) +#endif || !memcmp(header, "ftyp3gr6", 8) || !memcmp(header, "ftyp3gs6", 8) || !memcmp(header, "ftyp3ge6", 8) || !memcmp(header, "ftyp3gg6", 8) || !memcmp(header, "ftypisom", 8) || !memcmp(header, "ftypM4V ", 8) diff --git a/media/libstagefright/MediaExtractor.cpp b/media/libstagefright/MediaExtractor.cpp index b18c916..cca49fe 100644 --- a/media/libstagefright/MediaExtractor.cpp +++ b/media/libstagefright/MediaExtractor.cpp @@ -30,6 +30,9 @@ #include "include/WVMExtractor.h" #include "include/FLACExtractor.h" #include "include/AACExtractor.h" +#ifdef QCOM_HARDWARE +#include "include/ExtendedExtractor.h" +#endif #include "matroska/MatroskaExtractor.h" @@ -132,6 +135,14 @@ sp<MediaExtractor> MediaExtractor::Create( } } +#ifdef QCOM_HARDWARE + if(ret == NULL) { + //Create Extended Extractor only if default extractor are not selected + ALOGV("Using ExtendedExtractor\n"); + ret = ExtendedExtractor::CreateExtractor(source, mime); + } +#endif + return ret; } diff --git a/media/libstagefright/OMXCodec.cpp b/media/libstagefright/OMXCodec.cpp index 560c1bb..4e6c395 100755..100644 --- a/media/libstagefright/OMXCodec.cpp +++ b/media/libstagefright/OMXCodec.cpp @@ -1,5 +1,9 @@ /* * Copyright (C) 2009 The Android Open Source Project + * Copyright (c) 2010 - 2012, The Linux Foundation. All rights reserved. + * + * Not a Contribution, Apache license notifications and license are retained + * for attribution purposes only. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -42,6 +46,12 @@ #include <OMX_Audio.h> #include <OMX_Component.h> +#ifdef QCOM_HARDWARE +#include <QCMediaDefs.h> +#include <QCMetaData.h> +#include <QOMX_AudioExtensions.h> +#include <OMX_QCOMExtns.h> +#endif #include "include/avc_utils.h" #ifdef USE_SAMSUNG_COLORFORMAT @@ -271,6 +281,26 @@ uint32_t OMXCodec::getComponentQuirks( index, "output-buffers-are-unreadable")) { quirks |= kOutputBuffersAreUnreadable; } +#ifdef QCOM_HARDWARE + if (list->codecHasQuirk( + index, "requies-loaded-to-idle-after-allocation")) { + quirks |= kRequiresLoadedToIdleAfterAllocation; + } + if (list->codecHasQuirk( + index, "requires-global-flush")) { + quirks |= kRequiresGlobalFlush; + } + if (list->codecHasQuirk( + index, "requires-wma-pro-component")) { + quirks |= kRequiresWMAProComponent; + } + if (list->codecHasQuirk( + index, "defers-output-buffer-allocation")) { + quirks |= kDefersOutputBufferAllocation; + } + + quirks |= QCOMXCodec::getQCComponentQuirks(list,index); +#endif return quirks; } @@ -352,7 +382,28 @@ sp<MediaSource> OMXCodec::Create( return softwareCodec; } } - +#ifdef QCOM_HARDWARE + //quirks = getComponentQuirks(componentNameBase, createEncoder); + if(quirks & kRequiresWMAProComponent) + { + int32_t version; + CHECK(meta->findInt32(kKeyWMAVersion, &version)); + if(version==kTypeWMA) + { + componentName = "OMX.qcom.audio.decoder.wma"; + } + else if(version==kTypeWMAPro) + { + componentName= "OMX.qcom.audio.decoder.wma10Pro"; + } + else if(version==kTypeWMALossLess) + { + componentName= "OMX.qcom.audio.decoder.wmaLossLess"; + } + } + + QCOMXCodec::setASFQuirks(quirks, meta, componentName); +#endif ALOGV("Attempting to allocate OMX node '%s'", componentName); if (!createEncoder @@ -515,9 +566,25 @@ status_t OMXCodec::configureCodec(const sp<MetaData> &meta) { CHECK(meta->findData(kKeyVorbisBooks, &type, &data, &size)); addCodecSpecificData(data, size); +#ifdef QCOM_HARDWARE + } else if (meta->findData(kKeyRawCodecSpecificData, &type, &data, &size)) { + ALOGV("OMXCodec::configureCodec found kKeyRawCodecSpecificData of size %d\n", size); + addCodecSpecificData(data, size); + } else { + QCOMXCodec::checkAndAddRawFormat(this,meta); +#endif } } +#ifdef QCOM_HARDWARE + status_t errRetVal = QCOMXCodec::configureDIVXCodec( meta, mMIME, mOMX, mNode, + (OMXCodec::mIsEncoder ? + kPortIndexOutput : kPortIndexInput)); + if(OK != errRetVal) { + return errRetVal; + } +#endif + int32_t bitRate = 0; if (mIsEncoder) { CHECK(meta->findInt32(kKeyBitRate, &bitRate)); @@ -545,6 +612,42 @@ status_t OMXCodec::configureCodec(const sp<MetaData> &meta) { CODEC_LOGE("setAACFormat() failed (err = %d)", err); return err; } + +#ifdef QCOM_HARDWARE + uint32_t type; + const void *data; + size_t size; + + if (meta->findData(kKeyAacCodecSpecificData, &type, &data, &size)) { + ALOGV("OMXCodec:: configureCodec found kKeyAacCodecSpecificData of size %d\n", size); + addCodecSpecificData(data, size); + } + } else if (!strcasecmp(MEDIA_MIMETYPE_AUDIO_AC3, mMIME)) { + int32_t numChannels, sampleRate; + CHECK(meta->findInt32(kKeyChannelCount, &numChannels)); + CHECK(meta->findInt32(kKeySampleRate, &sampleRate)); + setAC3Format(numChannels, sampleRate); + } else if (!strcasecmp(MEDIA_MIMETYPE_AUDIO_EAC3, mMIME)) { + int32_t numChannels, sampleRate; + CHECK(meta->findInt32(kKeyChannelCount, &numChannels)); + CHECK(meta->findInt32(kKeySampleRate, &sampleRate)); + setAC3Format(numChannels, sampleRate); //since AC3 and EAC3 use same format at present + } else if (!strcasecmp(MEDIA_MIMETYPE_AUDIO_EVRC, mMIME)) { + int32_t numChannels, sampleRate; + CHECK(meta->findInt32(kKeyChannelCount, &numChannels)); + CHECK(meta->findInt32(kKeySampleRate, &sampleRate)); + setEVRCFormat(numChannels, sampleRate, bitRate); + } else if (!strcasecmp(MEDIA_MIMETYPE_AUDIO_QCELP, mMIME)) { + int32_t numChannels, sampleRate; + CHECK(meta->findInt32(kKeyChannelCount, &numChannels)); + CHECK(meta->findInt32(kKeySampleRate, &sampleRate)); + setQCELPFormat(numChannels, sampleRate, bitRate); + } else if (!strcasecmp(MEDIA_MIMETYPE_AUDIO_WMA, mMIME)) { + status_t err = setWMAFormat(meta); + if(err!=OK){ + return err; + } +#endif } else if (!strcasecmp(MEDIA_MIMETYPE_AUDIO_G711_ALAW, mMIME) || !strcasecmp(MEDIA_MIMETYPE_AUDIO_G711_MLAW, mMIME)) { // These are PCM-like formats with a fixed sample rate but @@ -562,10 +665,37 @@ status_t OMXCodec::configureCodec(const sp<MetaData> &meta) { CHECK(meta->findInt32(kKeySampleRate, &sampleRate)); setRawAudioFormat(kPortIndexInput, sampleRate, numChannels); +#ifdef QCOM_HARDWARE + } else { + status_t err = QCOMXCodec::setQCFormat(meta, mMIME, mOMX, mNode, this, mIsEncoder); + + if(OK != err) { + return err; + } +#endif } if (!strncasecmp(mMIME, "video/", 6)) { +#ifdef QCOM_HARDWARE + if ((mFlags & kClientNeedsFramebuffer) && !strncmp(mComponentName, "OMX.qcom.", 9)) { + ALOGV("Enabling thumbnail mode."); + QOMX_ENABLETYPE enableType; + OMX_INDEXTYPE indexType; + + status_t err = mOMX->getExtensionIndex( + mNode, OMX_QCOM_INDEX_PARAM_VIDEO_SYNCFRAMEDECODINGMODE, &indexType); + + CHECK_EQ(err, (status_t)OK); + + enableType.bEnable = OMX_TRUE; + err = mOMX->setParameter( + mNode, indexType, &enableType, sizeof(enableType)); + CHECK_EQ(err, (status_t)OK); + + ALOGV("Thumbnail mode enabled."); + } +#endif if (mIsEncoder) { setVideoInputFormat(mMIME, meta); } else { @@ -833,8 +963,15 @@ void OMXCodec::setVideoInputFormat( } else if (!strcasecmp(MEDIA_MIMETYPE_VIDEO_H263, mime)) { compressionFormat = OMX_VIDEO_CodingH263; } else { - ALOGE("Not a supported video mime type: %s", mime); - CHECK(!"Should not be here. Not a supported video mime type."); +#ifdef QCOM_HARDWARE + status_t err = QCOMXCodec::setQCVideoInputFormat(mime, &compressionFormat); + if(err != OK) { +#endif + ALOGE("Not a supported video mime type: %s", mime); + CHECK(!"Should not be here. Not a supported video mime type."); +#ifdef QCOM_HARDWARE + } +#endif } OMX_COLOR_FORMATTYPE colorFormat; @@ -1230,8 +1367,16 @@ status_t OMXCodec::setVideoOutputFormat( } else if (!strcasecmp(MEDIA_MIMETYPE_VIDEO_MPEG2, mime)) { compressionFormat = OMX_VIDEO_CodingMPEG2; } else { - ALOGE("Not a supported video mime type: %s", mime); - CHECK(!"Should not be here. Not a supported video mime type."); +#ifdef QCOM_HARDWARE + status_t err = QCOMXCodec::setQCVideoOutputFormat(mime,&compressionFormat); + + if(err != OK) { +#endif + ALOGE("Not a supported video mime type: %s", mime); + CHECK(!"Should not be here. Not a supported video mime type."); +#ifdef QCOM_HARDWARE + } +#endif } status_t err = setVideoPortFormatType( @@ -1389,6 +1534,9 @@ OMXCodec::OMXCodec( mState(LOADED), mInitialBufferSubmit(true), mSignalledEOS(false), +#ifdef QCOM_HARDWARE + mFinalStatus(OK), +#endif mNoMoreOutputData(false), mOutputPortSettingsHaveChanged(false), mSeekTimeUs(-1), @@ -1437,6 +1585,12 @@ void OMXCodec::setComponentRole( "audio_decoder.g711mlaw", "audio_encoder.g711mlaw" }, { MEDIA_MIMETYPE_AUDIO_G711_ALAW, "audio_decoder.g711alaw", "audio_encoder.g711alaw" }, +#ifdef QCOM_HARDWARE + { MEDIA_MIMETYPE_AUDIO_EVRC, + "audio_decoder.evrchw", "audio_encoder.evrc" }, + { MEDIA_MIMETYPE_AUDIO_QCELP, + "audio_decoder,qcelp13Hw", "audio_encoder.qcelp13" }, +#endif { MEDIA_MIMETYPE_VIDEO_AVC, "video_decoder.avc", "video_encoder.avc" }, { MEDIA_MIMETYPE_VIDEO_MPEG4, @@ -1449,6 +1603,16 @@ void OMXCodec::setComponentRole( "audio_decoder.raw", "audio_encoder.raw" }, { MEDIA_MIMETYPE_AUDIO_FLAC, "audio_decoder.flac", "audio_encoder.flac" }, +#ifdef QCOM_HARDWARE + { MEDIA_MIMETYPE_VIDEO_DIVX, + "video_decoder.divx", NULL }, + { MEDIA_MIMETYPE_AUDIO_AC3, + "audio_decoder.ac3", NULL }, + { MEDIA_MIMETYPE_AUDIO_EAC3, + "audio_decoder.eac3", NULL }, + { MEDIA_MIMETYPE_VIDEO_DIVX311, + "video_decoder.divx", NULL }, +#endif }; static const size_t kNumMimeToRole = @@ -1462,6 +1626,9 @@ void OMXCodec::setComponentRole( } if (i == kNumMimeToRole) { +#ifdef QCOM_HARDWARE + QCOMXCodec::checkQCRole(omx, node, isEncoder, mime); +#endif return; } @@ -2627,11 +2794,22 @@ void OMXCodec::onCmdComplete(OMX_COMMANDTYPE cmd, OMX_U32 data) { CODEC_LOGV("FLUSH_DONE(%ld)", portIndex); - CHECK_EQ((int)mPortStatus[portIndex], (int)SHUTTING_DOWN); - mPortStatus[portIndex] = ENABLED; +#ifdef QCOM_HARDWARE + if (portIndex == (OMX_U32) -1) { + CHECK_EQ((int)mPortStatus[kPortIndexInput], (int)SHUTTING_DOWN); + mPortStatus[kPortIndexInput] = ENABLED; + CHECK_EQ((int)mPortStatus[kPortIndexOutput], (int)SHUTTING_DOWN); + mPortStatus[kPortIndexOutput] = ENABLED; + } else { +#endif + CHECK_EQ((int)mPortStatus[portIndex], (int)SHUTTING_DOWN); + mPortStatus[portIndex] = ENABLED; - CHECK_EQ(countBuffersWeOwn(mPortBuffers[portIndex]), - mPortBuffers[portIndex].size()); + CHECK_EQ(countBuffersWeOwn(mPortBuffers[portIndex]), + mPortBuffers[portIndex].size()); +#ifdef QCOM_HARDWARE + } +#endif if (mSkipCutBuffer != NULL && mPortStatus[kPortIndexOutput] == ENABLED) { mSkipCutBuffer->clear(); @@ -2896,21 +3074,30 @@ bool OMXCodec::flushPortAsync(OMX_U32 portIndex) { CHECK(mState == EXECUTING || mState == RECONFIGURING || mState == EXECUTING_TO_IDLE); - CODEC_LOGV("flushPortAsync(%ld): we own %d out of %d buffers already.", - portIndex, countBuffersWeOwn(mPortBuffers[portIndex]), - mPortBuffers[portIndex].size()); +#ifdef QCOM_HARDWARE + if (portIndex == (OMX_U32) -1 ) { + mPortStatus[kPortIndexInput] = SHUTTING_DOWN; + mPortStatus[kPortIndexOutput] = SHUTTING_DOWN; + } else { +#endif + CODEC_LOGV("flushPortAsync(%ld): we own %d out of %d buffers already.", + portIndex, countBuffersWeOwn(mPortBuffers[portIndex]), + mPortBuffers[portIndex].size()); - CHECK_EQ((int)mPortStatus[portIndex], (int)ENABLED); - mPortStatus[portIndex] = SHUTTING_DOWN; + CHECK_EQ((int)mPortStatus[portIndex], (int)ENABLED); + mPortStatus[portIndex] = SHUTTING_DOWN; - if ((mQuirks & kRequiresFlushCompleteEmulation) - && countBuffersWeOwn(mPortBuffers[portIndex]) - == mPortBuffers[portIndex].size()) { - // No flush is necessary and this component fails to send a - // flush-complete event in this case. + if ((mQuirks & kRequiresFlushCompleteEmulation) + && countBuffersWeOwn(mPortBuffers[portIndex]) + == mPortBuffers[portIndex].size()) { + // No flush is necessary and this component fails to send a + // flush-complete event in this case. - return false; + return false; + } +#ifdef QCOM_HARDWARE } +#endif status_t err = mOMX->sendCommand(mNode, OMX_CommandFlush, portIndex); @@ -2950,16 +3137,27 @@ void OMXCodec::fillOutputBuffers() { // end-of-output-stream. If we own all input buffers and also own // all output buffers and we already signalled end-of-input-stream, // the end-of-output-stream is implied. - if (mSignalledEOS + +#ifdef QCOM_HARDWARE + // NOTE: Thumbnail mode needs a call to fillOutputBuffer in order + // to get the decoded frame from the component. Currently, + // thumbnail mode calls emptyBuffer with an EOS flag on its first + // frame and sets mSignalledEOS to true, so without the check for + // !mThumbnailMode, fillOutputBuffer will never be called. + if (!((mFlags & kClientNeedsFramebuffer) && !strncmp(mComponentName, "OMX.qcom.", 9))){ +#endif + if (mSignalledEOS && countBuffersWeOwn(mPortBuffers[kPortIndexInput]) == mPortBuffers[kPortIndexInput].size() && countBuffersWeOwn(mPortBuffers[kPortIndexOutput]) == mPortBuffers[kPortIndexOutput].size()) { - mNoMoreOutputData = true; - mBufferFilled.signal(); - - return; + mNoMoreOutputData = true; + mBufferFilled.signal(); + return; + } +#ifdef QCOM_HARDWARE } +#endif Vector<BufferInfo> *buffers = &mPortBuffers[kPortIndexOutput]; for (size_t i = 0; i < buffers->size(); ++i) { @@ -3283,6 +3481,20 @@ bool OMXCodec::drainInputBuffer(BufferInfo *info) { if (signalEOS) { flags |= OMX_BUFFERFLAG_EOS; +#ifdef QCOM_HARDWARE + } else if ((mFlags & kClientNeedsFramebuffer) && !strncmp(mComponentName, "OMX.qcom.", 9)) { + // Because we don't get an EOS after getting the first frame, we + // need to notify the component with OMX_BUFFERFLAG_EOS, set + // mNoMoreOutputData to false so fillOutputBuffer gets called on + // the first output buffer (see comment in fillOutputBuffer), and + // mSignalledEOS must be true so drainInputBuffer is not executed + // on extra frames. Setting mFinalStatus to ERROR_END_OF_STREAM as + // we dont want to return OK and NULL buffer in read. + flags |= OMX_BUFFERFLAG_EOS; + mNoMoreOutputData = false; + mSignalledEOS = true; + mFinalStatus = ERROR_END_OF_STREAM; +#endif } else { mNoMoreOutputData = false; } @@ -3381,6 +3593,14 @@ status_t OMXCodec::waitForBufferFilled_l() { return mBufferFilled.wait(mLock); } status_t err = mBufferFilled.waitRelative(mLock, kBufferFilledEventTimeOutNs); +#ifdef QCOM_HARDWARE + if ((err == -ETIMEDOUT) && (mPaused == true)){ + // When the audio playback is paused, the fill buffer maybe timed out + // if input data is not available to decode. Hence, considering the + // timed out as a valid case. + err = OK; + } +#endif if (err != OK) { CODEC_LOGE("Timed out waiting for output buffers: %d/%d", countBuffersWeOwn(mPortBuffers[kPortIndexInput]), @@ -3605,6 +3825,182 @@ status_t OMXCodec::setAACFormat( return OK; } +#ifdef QCOM_HARDWARE +void OMXCodec::setAC3Format(int32_t numChannels, int32_t sampleRate) { + + QOMX_AUDIO_PARAM_AC3TYPE profileAC3; + QOMX_AUDIO_PARAM_AC3PP profileAC3PP; + OMX_INDEXTYPE indexTypeAC3; + OMX_INDEXTYPE indexTypeAC3PP; + OMX_PARAM_PORTDEFINITIONTYPE portParam; + + //configure input port + CODEC_LOGV("setAC3Format samplerate %d, numChannels %d", sampleRate, numChannels); + InitOMXParams(&portParam); + portParam.nPortIndex = 0; + status_t err = mOMX->getParameter( + mNode, OMX_IndexParamPortDefinition, &portParam, sizeof(portParam)); + CHECK_EQ(err, (status_t)OK); + err = mOMX->setParameter( + mNode, OMX_IndexParamPortDefinition, &portParam, sizeof(portParam)); + CHECK_EQ(err, (status_t)OK); + + //configure output port + portParam.nPortIndex = 1; + err = mOMX->getParameter( + mNode, OMX_IndexParamPortDefinition, &portParam, sizeof(portParam)); + CHECK_EQ(err, (status_t)OK); + err = mOMX->setParameter( + mNode, OMX_IndexParamPortDefinition, &portParam, sizeof(portParam)); + CHECK_EQ(err, (status_t)OK); + + err = mOMX->getExtensionIndex(mNode, OMX_QCOM_INDEX_PARAM_AC3TYPE, &indexTypeAC3); + + InitOMXParams(&profileAC3); + profileAC3.nPortIndex = kPortIndexInput; + err = mOMX->getParameter(mNode, indexTypeAC3, &profileAC3, sizeof(profileAC3)); + CHECK_EQ(err,(status_t)OK); + + profileAC3.nSamplingRate = sampleRate; + profileAC3.nChannels = 2; + profileAC3.eChannelConfig = OMX_AUDIO_AC3_CHANNEL_CONFIG_2_0; + + CODEC_LOGE("numChannels = %d, profileAC3.nChannels = %d", numChannels, profileAC3.nChannels); + + err = mOMX->setParameter(mNode, indexTypeAC3, &profileAC3, sizeof(profileAC3)); + CHECK_EQ(err,(status_t)OK); + + //for output port + OMX_AUDIO_PARAM_PCMMODETYPE profilePcm; + InitOMXParams(&profilePcm); + profilePcm.nPortIndex = kPortIndexOutput; + err = mOMX->getParameter(mNode, OMX_IndexParamAudioPcm, &profilePcm, sizeof(profilePcm)); + CHECK_EQ(err, (status_t)OK); + + profilePcm.nSamplingRate = sampleRate; + err = mOMX->setParameter(mNode, OMX_IndexParamAudioPcm, &profilePcm, sizeof(profilePcm)); + CHECK_EQ(err, (status_t)OK); + mOMX->getExtensionIndex(mNode, OMX_QCOM_INDEX_PARAM_AC3PP, &indexTypeAC3PP); + + InitOMXParams(&profileAC3PP); + profileAC3PP.nPortIndex = kPortIndexInput; + err = mOMX->getParameter(mNode, indexTypeAC3PP, &profileAC3PP, sizeof(profileAC3PP)); + CHECK_EQ(err, (status_t)OK); + + int i; + int channel_routing[6]; + + for (i=0; i<6; i++) { + channel_routing[i] = -1; + } + for (i=0; i<6; i++) { + profileAC3PP.eChannelRouting[i] = (OMX_AUDIO_AC3_CHANNEL_ROUTING)channel_routing[i]; + } + + profileAC3PP.eChannelRouting[0] = OMX_AUDIO_AC3_CHANNEL_LEFT; + profileAC3PP.eChannelRouting[1] = OMX_AUDIO_AC3_CHANNEL_RIGHT; + err = mOMX->setParameter(mNode, indexTypeAC3PP, &profileAC3PP, sizeof(profileAC3PP)); + CHECK_EQ(err, (status_t)OK); + +} + + +status_t OMXCodec::setWMAFormat(const sp<MetaData> &meta) +{ + if (mIsEncoder) { + CODEC_LOGE("WMA encoding not supported"); + return OK; + } else { + int32_t version; + OMX_AUDIO_PARAM_WMATYPE paramWMA; + QOMX_AUDIO_PARAM_WMA10PROTYPE paramWMA10; + CHECK(meta->findInt32(kKeyWMAVersion, &version)); + int32_t numChannels; + int32_t bitRate; + int32_t sampleRate; + int32_t encodeOptions; + int32_t blockAlign; + int32_t bitspersample; + int32_t formattag; + int32_t advencopt1; + int32_t advencopt2; + int32_t VirtualPktSize; + if(version==kTypeWMAPro || version==kTypeWMALossLess) { + CHECK(meta->findInt32(kKeyWMABitspersample, &bitspersample)); + CHECK(meta->findInt32(kKeyWMAFormatTag, &formattag)); + CHECK(meta->findInt32(kKeyWMAAdvEncOpt1,&advencopt1)); + CHECK(meta->findInt32(kKeyWMAAdvEncOpt2,&advencopt2)); + CHECK(meta->findInt32(kKeyWMAVirPktSize,&VirtualPktSize)); + } + if(version==kTypeWMA) { + InitOMXParams(¶mWMA); + paramWMA.nPortIndex = kPortIndexInput; + } else if(version==kTypeWMAPro || version==kTypeWMALossLess) { + InitOMXParams(¶mWMA10); + paramWMA10.nPortIndex = kPortIndexInput; + } + CHECK(meta->findInt32(kKeyChannelCount, &numChannels)); + CHECK(meta->findInt32(kKeySampleRate, &sampleRate)); + CHECK(meta->findInt32(kKeyBitRate, &bitRate)); + CHECK(meta->findInt32(kKeyWMAEncodeOpt, &encodeOptions)); + CHECK(meta->findInt32(kKeyWMABlockAlign, &blockAlign)); + CODEC_LOGV("Channels: %d, SampleRate: %d, BitRate; %d" + "EncodeOptions: %d, blockAlign: %d", numChannels, + sampleRate, bitRate, encodeOptions, blockAlign); + if(sampleRate>48000 || numChannels>2) + { + ALOGE("Unsupported samplerate/channels"); + return ERROR_UNSUPPORTED; + } + if(version==kTypeWMAPro || version==kTypeWMALossLess) + { + CODEC_LOGV("Bitspersample: %d, wmaformattag: %d," + "advencopt1: %d, advencopt2: %d VirtualPktSize %d", bitspersample, + formattag, advencopt1, advencopt2, VirtualPktSize); + } + status_t err = OK; + OMX_INDEXTYPE index; + if(version==kTypeWMA) { + err = mOMX->getParameter( + mNode, OMX_IndexParamAudioWma, ¶mWMA, sizeof(paramWMA)); + } else if(version==kTypeWMAPro || version==kTypeWMALossLess) { + mOMX->getExtensionIndex(mNode,"OMX.Qualcomm.index.audio.wma10Pro",&index); + err = mOMX->getParameter( + mNode, index, ¶mWMA10, sizeof(paramWMA10)); + } + CHECK_EQ(err, (status_t)OK); + if(version==kTypeWMA) { + paramWMA.nChannels = numChannels; + paramWMA.nSamplingRate = sampleRate; + paramWMA.nEncodeOptions = encodeOptions; + paramWMA.nBitRate = bitRate; + paramWMA.nBlockAlign = blockAlign; + } else if(version==kTypeWMAPro || version==kTypeWMALossLess) { + paramWMA10.nChannels = numChannels; + paramWMA10.nSamplingRate = sampleRate; + paramWMA10.nEncodeOptions = encodeOptions; + paramWMA10.nBitRate = bitRate; + paramWMA10.nBlockAlign = blockAlign; + } + if(version==kTypeWMAPro || version==kTypeWMALossLess) { + paramWMA10.advancedEncodeOpt = advencopt1; + paramWMA10.advancedEncodeOpt2 = advencopt2; + paramWMA10.formatTag = formattag; + paramWMA10.validBitsPerSample = bitspersample; + paramWMA10.nVirtualPktSize = VirtualPktSize; + } + if(version==kTypeWMA) { + err = mOMX->setParameter( + mNode, OMX_IndexParamAudioWma, ¶mWMA, sizeof(paramWMA)); + } else if(version==kTypeWMAPro || version==kTypeWMALossLess) { + err = mOMX->setParameter( + mNode, index, ¶mWMA10, sizeof(paramWMA10)); + } + return err; + } +} +#endif + void OMXCodec::setG711Format(int32_t numChannels) { CHECK(!mIsEncoder); setRawAudioFormat(kPortIndexInput, 8000, numChannels); @@ -3846,19 +4242,32 @@ status_t OMXCodec::stopOmxComponent_l() { CODEC_LOGV("This component requires a flush before transitioning " "from EXECUTING to IDLE..."); - bool emulateInputFlushCompletion = - !flushPortAsync(kPortIndexInput); +#ifdef QCOM_HARDWARE + //DSP supports flushing of ports simultaneously. + //Flushing individual port is not supported. + if(mQuirks & kRequiresGlobalFlush) { + bool emulateFlushCompletion = !flushPortAsync(kPortIndexBoth); + if (emulateFlushCompletion) { + onCmdComplete(OMX_CommandFlush, kPortIndexBoth); + } + } else { +#endif + bool emulateInputFlushCompletion = + !flushPortAsync(kPortIndexInput); - bool emulateOutputFlushCompletion = - !flushPortAsync(kPortIndexOutput); + bool emulateOutputFlushCompletion = + !flushPortAsync(kPortIndexOutput); - if (emulateInputFlushCompletion) { - onCmdComplete(OMX_CommandFlush, kPortIndexInput); - } + if (emulateInputFlushCompletion) { + onCmdComplete(OMX_CommandFlush, kPortIndexInput); + } - if (emulateOutputFlushCompletion) { - onCmdComplete(OMX_CommandFlush, kPortIndexOutput); + if (emulateOutputFlushCompletion) { + onCmdComplete(OMX_CommandFlush, kPortIndexOutput); + } +#ifdef QCOM_HARDWARE } +#endif } else { mPortStatus[kPortIndexInput] = SHUTTING_DOWN; mPortStatus[kPortIndexOutput] = SHUTTING_DOWN; @@ -3966,16 +4375,39 @@ status_t OMXCodec::read( CHECK_EQ((int)mState, (int)EXECUTING); - bool emulateInputFlushCompletion = !flushPortAsync(kPortIndexInput); - bool emulateOutputFlushCompletion = !flushPortAsync(kPortIndexOutput); +#ifdef QCOM_HARDWARE + //DSP supports flushing of ports simultaneously. Flushing individual port is not supported. - if (emulateInputFlushCompletion) { - onCmdComplete(OMX_CommandFlush, kPortIndexInput); - } + if(mQuirks & kRequiresGlobalFlush) { + bool emulateFlushCompletion = !flushPortAsync(kPortIndexBoth); + if (emulateFlushCompletion) { + onCmdComplete(OMX_CommandFlush, kPortIndexBoth); + } + } else { + + //DSP supports flushing of ports simultaneously. + //Flushing individual port is not supported. + if(mQuirks & kRequiresGlobalFlush) { + bool emulateFlushCompletion = !flushPortAsync(kPortIndexBoth); + if (emulateFlushCompletion) { + onCmdComplete(OMX_CommandFlush, kPortIndexBoth); + } + } else { +#endif + bool emulateInputFlushCompletion = !flushPortAsync(kPortIndexInput); + bool emulateOutputFlushCompletion = !flushPortAsync(kPortIndexOutput); - if (emulateOutputFlushCompletion) { - onCmdComplete(OMX_CommandFlush, kPortIndexOutput); + if (emulateInputFlushCompletion) { + onCmdComplete(OMX_CommandFlush, kPortIndexInput); + } + + if (emulateOutputFlushCompletion) { + onCmdComplete(OMX_CommandFlush, kPortIndexOutput); + } +#ifdef QCOM_HARDWARE + } } +#endif while (mSeekTimeUs >= 0) { if ((err = waitForBufferFilled_l()) != OK) { @@ -4553,9 +4985,46 @@ void OMXCodec::initOutputFormat(const sp<MetaData> &inputFormat) { mOutputFormat->setInt32(kKeyChannelCount, numChannels); mOutputFormat->setInt32(kKeySampleRate, sampleRate); mOutputFormat->setInt32(kKeyBitRate, bitRate); +#ifdef QCOM_HARDWARE + } else if (audio_def->eEncoding == OMX_AUDIO_CodingQCELP13 ) { + mOutputFormat->setCString( + kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_QCELP); + int32_t numChannels, sampleRate, bitRate; + inputFormat->findInt32(kKeyChannelCount, &numChannels); + inputFormat->findInt32(kKeySampleRate, &sampleRate); + inputFormat->findInt32(kKeyBitRate, &bitRate); + mOutputFormat->setInt32(kKeyChannelCount, numChannels); + mOutputFormat->setInt32(kKeySampleRate, sampleRate); + mOutputFormat->setInt32(kKeyBitRate, bitRate); + } else if (audio_def->eEncoding == OMX_AUDIO_CodingEVRC ) { + mOutputFormat->setCString( + kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_EVRC); + int32_t numChannels, sampleRate, bitRate; + inputFormat->findInt32(kKeyChannelCount, &numChannels); + inputFormat->findInt32(kKeySampleRate, &sampleRate); + inputFormat->findInt32(kKeyBitRate, &bitRate); + mOutputFormat->setInt32(kKeyChannelCount, numChannels); + mOutputFormat->setInt32(kKeySampleRate, sampleRate); + mOutputFormat->setInt32(kKeyBitRate, bitRate); } else { - CHECK(!"Should not be here. Unknown audio encoding."); + AString mimeType; + if(OK == QCOMXCodec::checkQCFormats(audio_def->eEncoding, &mimeType)) { + mOutputFormat->setCString( + kKeyMIMEType, mimeType.c_str()); + int32_t numChannels, sampleRate, bitRate; + inputFormat->findInt32(kKeyChannelCount, &numChannels); + inputFormat->findInt32(kKeySampleRate, &sampleRate); + inputFormat->findInt32(kKeyBitRate, &bitRate); + mOutputFormat->setInt32(kKeyChannelCount, numChannels); + mOutputFormat->setInt32(kKeySampleRate, sampleRate); + mOutputFormat->setInt32(kKeyBitRate, bitRate); +#endif + } else { + CHECK(!"Should not be here. Unknown audio encoding."); + } +#ifdef QCOM_HARDWARE } +#endif break; } @@ -4756,6 +5225,108 @@ status_t QueryCodecs( return QueryCodecs(omx, mimeType, queryDecoders, false /*hwCodecOnly*/, results); } +#ifdef QCOM_HARDWARE +void OMXCodec::setEVRCFormat(int32_t numChannels, int32_t sampleRate, int32_t bitRate) { + if (mIsEncoder) { + CHECK(numChannels == 1); + //////////////// input port //////////////////// + setRawAudioFormat(kPortIndexInput, sampleRate, numChannels); + //////////////// output port //////////////////// + // format + OMX_AUDIO_PARAM_PORTFORMATTYPE format; + format.nPortIndex = kPortIndexOutput; + format.nIndex = 0; + status_t err = OMX_ErrorNone; + while (OMX_ErrorNone == err) { + CHECK_EQ(mOMX->getParameter(mNode, OMX_IndexParamAudioPortFormat, + &format, sizeof(format)), (status_t)OK); + if (format.eEncoding == OMX_AUDIO_CodingEVRC) { + break; + } + format.nIndex++; + } + CHECK_EQ((status_t)OK, err); + CHECK_EQ(mOMX->setParameter(mNode, OMX_IndexParamAudioPortFormat, + &format, sizeof(format)), (status_t)OK); + + // port definition + OMX_PARAM_PORTDEFINITIONTYPE def; + InitOMXParams(&def); + def.nPortIndex = kPortIndexOutput; + def.format.audio.cMIMEType = NULL; + CHECK_EQ(mOMX->getParameter(mNode, OMX_IndexParamPortDefinition, + &def, sizeof(def)), (status_t)OK); + def.format.audio.bFlagErrorConcealment = OMX_TRUE; + def.format.audio.eEncoding = OMX_AUDIO_CodingEVRC; + CHECK_EQ(mOMX->setParameter(mNode, OMX_IndexParamPortDefinition, + &def, sizeof(def)), (status_t)OK); + + // profile + OMX_AUDIO_PARAM_EVRCTYPE profile; + InitOMXParams(&profile); + profile.nPortIndex = kPortIndexOutput; + CHECK_EQ(mOMX->getParameter(mNode, OMX_IndexParamAudioEvrc, + &profile, sizeof(profile)), (status_t)OK); + profile.nChannels = numChannels; + CHECK_EQ(mOMX->setParameter(mNode, OMX_IndexParamAudioEvrc, + &profile, sizeof(profile)), (status_t)OK); + } + else{ + ALOGI("EVRC decoder \n"); + } +} + +void OMXCodec::setQCELPFormat(int32_t numChannels, int32_t sampleRate, int32_t bitRate) { + if (mIsEncoder) { + CHECK(numChannels == 1); + //////////////// input port //////////////////// + setRawAudioFormat(kPortIndexInput, sampleRate, numChannels); + //////////////// output port //////////////////// + // format + OMX_AUDIO_PARAM_PORTFORMATTYPE format; + format.nPortIndex = kPortIndexOutput; + format.nIndex = 0; + status_t err = OMX_ErrorNone; + while (OMX_ErrorNone == err) { + CHECK_EQ(mOMX->getParameter(mNode, OMX_IndexParamAudioPortFormat, + &format, sizeof(format)), (status_t)OK); + if (format.eEncoding == OMX_AUDIO_CodingQCELP13) { + break; + } + format.nIndex++; + } + CHECK_EQ((status_t)OK, err); + CHECK_EQ(mOMX->setParameter(mNode, OMX_IndexParamAudioPortFormat, + &format, sizeof(format)), (status_t)OK); + + // port definition + OMX_PARAM_PORTDEFINITIONTYPE def; + InitOMXParams(&def); + def.nPortIndex = kPortIndexOutput; + def.format.audio.cMIMEType = NULL; + CHECK_EQ(mOMX->getParameter(mNode, OMX_IndexParamPortDefinition, + &def, sizeof(def)), (status_t)OK); + def.format.audio.bFlagErrorConcealment = OMX_TRUE; + def.format.audio.eEncoding = OMX_AUDIO_CodingQCELP13; + CHECK_EQ(mOMX->setParameter(mNode, OMX_IndexParamPortDefinition, + &def, sizeof(def)), (status_t)OK); + + // profile + OMX_AUDIO_PARAM_QCELP13TYPE profile; + InitOMXParams(&profile); + profile.nPortIndex = kPortIndexOutput; + CHECK_EQ(mOMX->getParameter(mNode, OMX_IndexParamAudioQcelp13, + &profile, sizeof(profile)), (status_t)OK); + profile.nChannels = numChannels; + CHECK_EQ(mOMX->setParameter(mNode, OMX_IndexParamAudioQcelp13, + &profile, sizeof(profile)), (status_t)OK); + } + else{ + ALOGI("QCELP decoder \n"); + } +} +#endif + // These are supposed be equivalent to the logic in // "audio_channel_out_mask_from_count". status_t getOMXChannelMapping(size_t numChannels, OMX_AUDIO_CHANNELTYPE map[]) { diff --git a/media/libstagefright/QCMediaDefs.cpp b/media/libstagefright/QCMediaDefs.cpp new file mode 100644 index 0000000..ec2d04e --- /dev/null +++ b/media/libstagefright/QCMediaDefs.cpp @@ -0,0 +1,55 @@ +/*Copyright (c) 2012, The Linux Foundation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * * Neither the name of The Linux Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <QCMediaDefs.h> + +namespace android { + +const char *MEDIA_MIMETYPE_AUDIO_EVRC = "audio/evrc"; + +const char *MEDIA_MIMETYPE_VIDEO_WMV = "video/x-ms-wmv"; +const char *MEDIA_MIMETYPE_AUDIO_WMA = "audio/x-ms-wma"; +const char *MEDIA_MIMETYPE_CONTAINER_ASF = "video/x-ms-asf"; +const char *MEDIA_MIMETYPE_VIDEO_DIVX = "video/divx"; +const char *MEDIA_MIMETYPE_AUDIO_AC3 = "audio/ac3"; +const char *MEDIA_MIMETYPE_CONTAINER_AAC = "audio/aac"; +const char *MEDIA_MIMETYPE_CONTAINER_QCP = "audio/vnd.qcelp"; +const char *MEDIA_MIMETYPE_VIDEO_DIVX311 = "video/divx311"; +const char *MEDIA_MIMETYPE_VIDEO_DIVX4 = "video/divx4"; + +const char *MEDIA_MIMETYPE_CONTAINER_MPEG2 = "video/mp2"; + +const char *MEDIA_MIMETYPE_CONTAINER_3G2 = "video/3g2"; +const char *MEDIA_MIMETYPE_AUDIO_DTS = "audio/dts"; + +const char *MEDIA_MIMETYPE_AUDIO_DTS_LBR = "audio/dts-lbr"; +const char *MEDIA_MIMETYPE_AUDIO_EAC3 = "audio/eac3"; +const char *MEDIA_MIMETYPE_AUDIO_AMR_WB_PLUS = "audio/amr-wb-plus"; + +} // namespace android + diff --git a/media/libstagefright/QCOMXCodec.cpp b/media/libstagefright/QCOMXCodec.cpp new file mode 100644 index 0000000..1b24c8b --- /dev/null +++ b/media/libstagefright/QCOMXCodec.cpp @@ -0,0 +1,548 @@ +/* + * Copyright (c) 2012, The Linux Foundation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * * Neither the name of The Linux Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "QCOMXCodec" +#include <utils/Log.h> + +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/MediaDefs.h> +#include <media/stagefright/MediaCodecList.h> + +#include <media/stagefright/MetaData.h> +#include <media/stagefright/QCOMXCodec.h> +#include <media/stagefright/OMXCodec.h> +#include <QCMetaData.h> +#include <QCMediaDefs.h> +#include <OMX_QCOMExtns.h> + +#include <OMX_Component.h> +#include <QOMX_AudioExtensions.h> + + +namespace android { + +uint32_t QCOMXCodec::getQCComponentQuirks(const MediaCodecList *list, size_t index) { + uint32_t quirks = 0; + + if (list->codecHasQuirk( + index, "requires-wma-pro-component")) { + quirks |= kRequiresWMAProComponent; + } + return quirks; +} + +void QCOMXCodec::setASFQuirks(uint32_t quirks, const sp<MetaData> &meta, const char* componentName) { + if(quirks & kRequiresWMAProComponent) + { + int32_t version; + CHECK(meta->findInt32(kKeyWMAVersion, &version)); + if(version==kTypeWMA) { + componentName = "OMX.qcom.audio.decoder.wma"; + } else if(version==kTypeWMAPro) { + componentName= "OMX.qcom.audio.decoder.wma10Pro"; + } else if(version==kTypeWMALossLess) { + componentName= "OMX.qcom.audio.decoder.wmaLossLess"; + } + } +} + +template<class T> +static void InitOMXParams(T *params) { + params->nSize = sizeof(T); + params->nVersion.s.nVersionMajor = 1; + params->nVersion.s.nVersionMinor = 0; + params->nVersion.s.nRevision = 0; + params->nVersion.s.nStep = 0; +} + + +status_t QCOMXCodec::configureDIVXCodec(const sp<MetaData> &meta, char* mime, sp<IOMX> OMXhandle, IOMX::node_id nodeID, int port_index) { + status_t err = OK; + if (!strcasecmp(MEDIA_MIMETYPE_VIDEO_DIVX, mime) || + !strcasecmp(MEDIA_MIMETYPE_VIDEO_DIVX4, mime) || + !strcasecmp(MEDIA_MIMETYPE_VIDEO_DIVX311, mime)) { + ALOGV("Setting the QOMX_VIDEO_PARAM_DIVXTYPE params "); + QOMX_VIDEO_PARAM_DIVXTYPE paramDivX; + InitOMXParams(¶mDivX); + paramDivX.nPortIndex = port_index; + int32_t DivxVersion = 0; + CHECK(meta->findInt32(kKeyDivXVersion,&DivxVersion)); + ALOGV("Divx Version Type %d\n",DivxVersion); + + if(DivxVersion == kTypeDivXVer_4) { + paramDivX.eFormat = QOMX_VIDEO_DIVXFormat4; + } else if(DivxVersion == kTypeDivXVer_5) { + paramDivX.eFormat = QOMX_VIDEO_DIVXFormat5; + } else if(DivxVersion == kTypeDivXVer_6) { + paramDivX.eFormat = QOMX_VIDEO_DIVXFormat6; + } else if(DivxVersion == kTypeDivXVer_3_11 ) { + paramDivX.eFormat = QOMX_VIDEO_DIVXFormat311; + } else { + paramDivX.eFormat = QOMX_VIDEO_DIVXFormatUnused; + } + paramDivX.eProfile = (QOMX_VIDEO_DIVXPROFILETYPE)0; //Not used for now. + + err = OMXhandle->setParameter(nodeID, + (OMX_INDEXTYPE)OMX_QcomIndexParamVideoDivx, + ¶mDivX, sizeof(paramDivX)); + } + + return err; +} + +void QCOMXCodec::checkAndAddRawFormat(OMXCodec *handle, const sp<MetaData> &meta){ + uint32_t type; + const void *data; + size_t size; + + if (meta->findData(kKeyRawCodecSpecificData, &type, &data, &size)) { + ALOGV("OMXCodec::configureCodec found kKeyRawCodecSpecificData of size %d\n", size); + handle->addCodecSpecificData(data, size); + } + +} + +status_t QCOMXCodec::setQCFormat(const sp<MetaData> &meta, char* mime, sp<IOMX> OMXhandle, + IOMX::node_id nodeID, OMXCodec *handle, bool isEncoder ) { + ALOGV("setQCFormat -- called "); + status_t err = OK; + if ((!strcasecmp(MEDIA_MIMETYPE_AUDIO_AC3, mime)) || + (!strcasecmp(MEDIA_MIMETYPE_AUDIO_EAC3, mime))){ + int32_t numChannels, sampleRate; + CHECK(meta->findInt32(kKeyChannelCount, &numChannels)); + CHECK(meta->findInt32(kKeySampleRate, &sampleRate)); + setAC3Format(numChannels, sampleRate, OMXhandle, nodeID); + } else if (!strcasecmp(MEDIA_MIMETYPE_AUDIO_EVRC, mime)) { + int32_t numChannels, sampleRate; + CHECK(meta->findInt32(kKeyChannelCount, &numChannels)); + CHECK(meta->findInt32(kKeySampleRate, &sampleRate)); + setEVRCFormat(numChannels, sampleRate, OMXhandle, nodeID, handle,isEncoder ); + } else if (!strcasecmp(MEDIA_MIMETYPE_AUDIO_QCELP, mime)) { + int32_t numChannels, sampleRate; + CHECK(meta->findInt32(kKeyChannelCount, &numChannels)); + CHECK(meta->findInt32(kKeySampleRate, &sampleRate)); + setQCELPFormat(numChannels, sampleRate, OMXhandle, nodeID, handle,isEncoder); + } else if (!strcasecmp(MEDIA_MIMETYPE_AUDIO_WMA, mime)) { + err = setWMAFormat(meta, OMXhandle, nodeID, isEncoder); + } + return err; +} + + +void QCOMXCodec::setEVRCFormat(int32_t numChannels, int32_t sampleRate, sp<IOMX> OMXhandle, + IOMX::node_id nodeID, OMXCodec *handle, bool isEncoder ) { + ALOGV("setEVRCFormat -- called "); + if (isEncoder) { + CHECK(numChannels == 1); + //////////////// input port //////////////////// + handle->setRawAudioFormat(OMXCodec::kPortIndexInput, sampleRate, numChannels); + //////////////// output port //////////////////// + // format + OMX_AUDIO_PARAM_PORTFORMATTYPE format; + format.nPortIndex = OMXCodec::kPortIndexOutput; + format.nIndex = 0; + status_t err = OMX_ErrorNone; + while (OMX_ErrorNone == err) { + CHECK_EQ(OMXhandle->getParameter(nodeID, OMX_IndexParamAudioPortFormat, + &format, sizeof(format)), (status_t)OK); + if (format.eEncoding == OMX_AUDIO_CodingEVRC) { + break; + } + format.nIndex++; + } + CHECK_EQ((status_t)OK, err); + CHECK_EQ(OMXhandle->setParameter(nodeID, OMX_IndexParamAudioPortFormat, + &format, sizeof(format)), (status_t)OK); + + // port definition + OMX_PARAM_PORTDEFINITIONTYPE def; + InitOMXParams(&def); + def.nPortIndex = OMXCodec::kPortIndexOutput; + def.format.audio.cMIMEType = NULL; + CHECK_EQ(OMXhandle->getParameter(nodeID, OMX_IndexParamPortDefinition, + &def, sizeof(def)), (status_t)OK); + def.format.audio.bFlagErrorConcealment = OMX_TRUE; + def.format.audio.eEncoding = OMX_AUDIO_CodingEVRC; + CHECK_EQ(OMXhandle->setParameter(nodeID, OMX_IndexParamPortDefinition, + &def, sizeof(def)), (status_t)OK); + + // profile + OMX_AUDIO_PARAM_EVRCTYPE profile; + InitOMXParams(&profile); + profile.nPortIndex = OMXCodec::kPortIndexOutput; + CHECK_EQ(OMXhandle->getParameter(nodeID, OMX_IndexParamAudioEvrc, + &profile, sizeof(profile)), (status_t)OK); + profile.nChannels = numChannels; + CHECK_EQ(OMXhandle->setParameter(nodeID, OMX_IndexParamAudioEvrc, + &profile, sizeof(profile)), (status_t)OK); + } + else{ + ALOGI("EVRC decoder \n"); + } +} + + +void QCOMXCodec::setQCELPFormat(int32_t numChannels, int32_t sampleRate, sp<IOMX> OMXhandle, + IOMX::node_id nodeID, OMXCodec *handle, bool isEncoder ) { + if (isEncoder) { + CHECK(numChannels == 1); + //////////////// input port //////////////////// + handle->setRawAudioFormat(OMXCodec::kPortIndexInput, sampleRate, numChannels); + //////////////// output port //////////////////// + // format + OMX_AUDIO_PARAM_PORTFORMATTYPE format; + format.nPortIndex = OMXCodec::kPortIndexOutput; + format.nIndex = 0; + status_t err = OMX_ErrorNone; + while (OMX_ErrorNone == err) { + CHECK_EQ(OMXhandle->getParameter(nodeID, OMX_IndexParamAudioPortFormat, + &format, sizeof(format)), (status_t)OK); + if (format.eEncoding == OMX_AUDIO_CodingQCELP13) { + break; + } + format.nIndex++; + } + CHECK_EQ((status_t)OK, err); + CHECK_EQ(OMXhandle->setParameter(nodeID, OMX_IndexParamAudioPortFormat, + &format, sizeof(format)), (status_t)OK); + + // port definition + OMX_PARAM_PORTDEFINITIONTYPE def; + InitOMXParams(&def); + def.nPortIndex = OMXCodec::kPortIndexOutput; + def.format.audio.cMIMEType = NULL; + CHECK_EQ(OMXhandle->getParameter(nodeID, OMX_IndexParamPortDefinition, + &def, sizeof(def)), (status_t)OK); + def.format.audio.bFlagErrorConcealment = OMX_TRUE; + def.format.audio.eEncoding = OMX_AUDIO_CodingQCELP13; + CHECK_EQ(OMXhandle->setParameter(nodeID, OMX_IndexParamPortDefinition, + &def, sizeof(def)), (status_t)OK); + + // profile + OMX_AUDIO_PARAM_QCELP13TYPE profile; + InitOMXParams(&profile); + profile.nPortIndex = OMXCodec::kPortIndexOutput; + CHECK_EQ(OMXhandle->getParameter(nodeID, OMX_IndexParamAudioQcelp13, + &profile, sizeof(profile)), (status_t)OK); + profile.nChannels = numChannels; + CHECK_EQ(OMXhandle->setParameter(nodeID, OMX_IndexParamAudioQcelp13, + &profile, sizeof(profile)), (status_t)OK); + } + else { + ALOGI("QCELP decoder \n"); + } +} + +status_t QCOMXCodec::setWMAFormat(const sp<MetaData> &meta, sp<IOMX> OMXhandle, + IOMX::node_id nodeID, bool isEncoder ) { + ALOGV("setWMAFormat Called"); + if (isEncoder) { + ALOGE("WMA encoding not supported"); + return OK; + } else { + int32_t version; + OMX_AUDIO_PARAM_WMATYPE paramWMA; + QOMX_AUDIO_PARAM_WMA10PROTYPE paramWMA10; + CHECK(meta->findInt32(kKeyWMAVersion, &version)); + int32_t numChannels; + int32_t bitRate; + int32_t sampleRate; + int32_t encodeOptions; + int32_t blockAlign; + int32_t bitspersample; + int32_t formattag; + int32_t advencopt1; + int32_t advencopt2; + int32_t VirtualPktSize; + if(version==kTypeWMAPro || version==kTypeWMALossLess) { + CHECK(meta->findInt32(kKeyWMABitspersample, &bitspersample)); + CHECK(meta->findInt32(kKeyWMAFormatTag, &formattag)); + CHECK(meta->findInt32(kKeyWMAAdvEncOpt1,&advencopt1)); + CHECK(meta->findInt32(kKeyWMAAdvEncOpt2,&advencopt2)); + CHECK(meta->findInt32(kKeyWMAVirPktSize,&VirtualPktSize)); + } + if(version==kTypeWMA) { + InitOMXParams(¶mWMA); + paramWMA.nPortIndex = OMXCodec::kPortIndexInput; + } else if(version==kTypeWMAPro || version==kTypeWMALossLess) { + InitOMXParams(¶mWMA10); + paramWMA10.nPortIndex = OMXCodec::kPortIndexInput; + } + CHECK(meta->findInt32(kKeyChannelCount, &numChannels)); + CHECK(meta->findInt32(kKeySampleRate, &sampleRate)); + CHECK(meta->findInt32(kKeyBitRate, &bitRate)); + CHECK(meta->findInt32(kKeyWMAEncodeOpt, &encodeOptions)); + CHECK(meta->findInt32(kKeyWMABlockAlign, &blockAlign)); + ALOGV("Channels: %d, SampleRate: %d, BitRate; %d" + "EncodeOptions: %d, blockAlign: %d", numChannels, + sampleRate, bitRate, encodeOptions, blockAlign); + if(sampleRate>48000 || numChannels>2) + { + ALOGE("Unsupported samplerate/channels"); + return ERROR_UNSUPPORTED; + } + if(version==kTypeWMAPro || version==kTypeWMALossLess) + { + ALOGV("Bitspersample: %d, wmaformattag: %d," + "advencopt1: %d, advencopt2: %d VirtualPktSize %d", bitspersample, + formattag, advencopt1, advencopt2, VirtualPktSize); + } + status_t err = OK; + OMX_INDEXTYPE index; + if(version==kTypeWMA) { + err = OMXhandle->getParameter( + nodeID, OMX_IndexParamAudioWma, ¶mWMA, sizeof(paramWMA)); + } else if(version==kTypeWMAPro || version==kTypeWMALossLess) { + OMXhandle->getExtensionIndex(nodeID,"OMX.Qualcomm.index.audio.wma10Pro",&index); + err = OMXhandle->getParameter( + nodeID, index, ¶mWMA10, sizeof(paramWMA10)); + } + CHECK_EQ(err, (status_t)OK); + if(version==kTypeWMA) { + paramWMA.nChannels = numChannels; + paramWMA.nSamplingRate = sampleRate; + paramWMA.nEncodeOptions = encodeOptions; + paramWMA.nBitRate = bitRate; + paramWMA.nBlockAlign = blockAlign; + } else if(version==kTypeWMAPro || version==kTypeWMALossLess) { + paramWMA10.nChannels = numChannels; + paramWMA10.nSamplingRate = sampleRate; + paramWMA10.nEncodeOptions = encodeOptions; + paramWMA10.nBitRate = bitRate; + paramWMA10.nBlockAlign = blockAlign; + } + if(version==kTypeWMAPro || version==kTypeWMALossLess) { + paramWMA10.advancedEncodeOpt = advencopt1; + paramWMA10.advancedEncodeOpt2 = advencopt2; + paramWMA10.formatTag = formattag; + paramWMA10.validBitsPerSample = bitspersample; + paramWMA10.nVirtualPktSize = VirtualPktSize; + } + if(version==kTypeWMA) { + err = OMXhandle->setParameter( + nodeID, OMX_IndexParamAudioWma, ¶mWMA, sizeof(paramWMA)); + } else if(version==kTypeWMAPro || version==kTypeWMALossLess) { + err = OMXhandle->setParameter( + nodeID, index, ¶mWMA10, sizeof(paramWMA10)); + } + return err; + } + return OK; +} + + +void QCOMXCodec::setAC3Format(int32_t numChannels, int32_t sampleRate, sp<IOMX> OMXhandle, + IOMX::node_id nodeID) { + QOMX_AUDIO_PARAM_AC3TYPE profileAC3; + QOMX_AUDIO_PARAM_AC3PP profileAC3PP; + OMX_INDEXTYPE indexTypeAC3; + OMX_INDEXTYPE indexTypeAC3PP; + OMX_PARAM_PORTDEFINITIONTYPE portParam; + + //configure input port + ALOGV("setAC3Format samplerate %d, numChannels %d", sampleRate, numChannels); + InitOMXParams(&portParam); + portParam.nPortIndex = 0; + status_t err = OMXhandle->getParameter( + nodeID, OMX_IndexParamPortDefinition, &portParam, sizeof(portParam)); + CHECK_EQ(err, (status_t)OK); + err = OMXhandle->setParameter( + nodeID, OMX_IndexParamPortDefinition, &portParam, sizeof(portParam)); + CHECK_EQ(err, (status_t)OK); + + //configure output port + portParam.nPortIndex = 1; + err = OMXhandle->getParameter( + nodeID, OMX_IndexParamPortDefinition, &portParam, sizeof(portParam)); + CHECK_EQ(err, (status_t)OK); + err = OMXhandle->setParameter( + nodeID, OMX_IndexParamPortDefinition, &portParam, sizeof(portParam)); + CHECK_EQ(err, (status_t)OK); + + err = OMXhandle->getExtensionIndex(nodeID, OMX_QCOM_INDEX_PARAM_AC3TYPE, &indexTypeAC3); + + InitOMXParams(&profileAC3); + profileAC3.nPortIndex = OMXCodec::kPortIndexInput; + err = OMXhandle->getParameter(nodeID, indexTypeAC3, &profileAC3, sizeof(profileAC3)); + CHECK_EQ(err,(status_t)OK); + + profileAC3.nSamplingRate = sampleRate; + profileAC3.nChannels = 2; + profileAC3.eChannelConfig = OMX_AUDIO_AC3_CHANNEL_CONFIG_2_0; + + ALOGV("numChannels = %d, profileAC3.nChannels = %d", numChannels, profileAC3.nChannels); + + err = OMXhandle->setParameter(nodeID, indexTypeAC3, &profileAC3, sizeof(profileAC3)); + CHECK_EQ(err,(status_t)OK); + + //for output port + OMX_AUDIO_PARAM_PCMMODETYPE profilePcm; + InitOMXParams(&profilePcm); + profilePcm.nPortIndex = OMXCodec::kPortIndexOutput; + err = OMXhandle->getParameter(nodeID, OMX_IndexParamAudioPcm, &profilePcm, sizeof(profilePcm)); + CHECK_EQ(err, (status_t)OK); + + profilePcm.nSamplingRate = sampleRate; + err = OMXhandle->setParameter(nodeID, OMX_IndexParamAudioPcm, &profilePcm, sizeof(profilePcm)); + CHECK_EQ(err, (status_t)OK); + OMXhandle->getExtensionIndex(nodeID, OMX_QCOM_INDEX_PARAM_AC3PP, &indexTypeAC3PP); + + InitOMXParams(&profileAC3PP); + profileAC3PP.nPortIndex = OMXCodec::kPortIndexInput; + err = OMXhandle->getParameter(nodeID, indexTypeAC3PP, &profileAC3PP, sizeof(profileAC3PP)); + CHECK_EQ(err, (status_t)OK); + + int i; + int channel_routing[6]; + + for (i=0; i<6; i++) { + channel_routing[i] = -1; + } + for (i=0; i<6; i++) { + profileAC3PP.eChannelRouting[i] = (OMX_AUDIO_AC3_CHANNEL_ROUTING)channel_routing[i]; + } + + profileAC3PP.eChannelRouting[0] = OMX_AUDIO_AC3_CHANNEL_LEFT; + profileAC3PP.eChannelRouting[1] = OMX_AUDIO_AC3_CHANNEL_RIGHT; + err = OMXhandle->setParameter(nodeID, indexTypeAC3PP, &profileAC3PP, sizeof(profileAC3PP)); + CHECK_EQ(err, (status_t)OK); +} + + +status_t QCOMXCodec::setQCVideoInputFormat(const char *mime, OMX_VIDEO_CODINGTYPE *compressionFormat) { + status_t retVal = OK; + if (!strcasecmp(MEDIA_MIMETYPE_VIDEO_DIVX, mime)){ + *compressionFormat= (OMX_VIDEO_CODINGTYPE)QOMX_VIDEO_CodingDivx; + } else if (!strcasecmp(MEDIA_MIMETYPE_VIDEO_DIVX4, mime)){ + *compressionFormat= (OMX_VIDEO_CODINGTYPE)QOMX_VIDEO_CodingDivx; + } else if (!strcasecmp(MEDIA_MIMETYPE_VIDEO_DIVX311, mime)){ + *compressionFormat= (OMX_VIDEO_CODINGTYPE)QOMX_VIDEO_CodingDivx; + } else if (!strcasecmp(MEDIA_MIMETYPE_VIDEO_WMV, mime)){ + *compressionFormat = OMX_VIDEO_CodingWMV; + } else if (!strcasecmp(MEDIA_MIMETYPE_CONTAINER_MPEG2, mime)){ + *compressionFormat = OMX_VIDEO_CodingMPEG2; + } else { + retVal = BAD_VALUE; + } + + return retVal; +} + +status_t QCOMXCodec::setQCVideoOutputFormat(const char *mime, OMX_VIDEO_CODINGTYPE *compressionFormat) { + status_t retVal = OK; + if(!strcasecmp(MEDIA_MIMETYPE_VIDEO_DIVX, mime)) { + *compressionFormat = (OMX_VIDEO_CODINGTYPE)QOMX_VIDEO_CodingDivx; + } else if(!strcasecmp(MEDIA_MIMETYPE_VIDEO_DIVX311, mime)) { + *compressionFormat = (OMX_VIDEO_CODINGTYPE)QOMX_VIDEO_CodingDivx; + } else if(!strcasecmp(MEDIA_MIMETYPE_VIDEO_DIVX4, mime)) { + *compressionFormat = (OMX_VIDEO_CODINGTYPE)QOMX_VIDEO_CodingDivx; + } else if (!strcasecmp(MEDIA_MIMETYPE_VIDEO_WMV, mime)){ + *compressionFormat = OMX_VIDEO_CodingWMV; + } else { + retVal = BAD_VALUE; + } + return retVal; +} + + +void QCOMXCodec::checkQCRole( const sp<IOMX> &omx, IOMX::node_id node, + bool isEncoder, const char *mime){ + ALOGV("checkQCRole Called"); + struct MimeToRole { + const char *mime; + const char *decoderRole; + const char *encoderRole; + }; + + static const MimeToRole kQCMimeToRole[] = { + { MEDIA_MIMETYPE_AUDIO_EVRC, + "audio_decoder.evrchw", "audio_encoder.evrc" }, + { MEDIA_MIMETYPE_AUDIO_QCELP, + "audio_decoder,qcelp13Hw", "audio_encoder.qcelp13" }, + { MEDIA_MIMETYPE_VIDEO_DIVX, + "video_decoder.divx", NULL }, + { MEDIA_MIMETYPE_AUDIO_AC3, + "audio_decoder.ac3", NULL }, + { MEDIA_MIMETYPE_VIDEO_DIVX311, + "video_decoder.divx", NULL }, + }; + + static const size_t kNumMimeToRole = + sizeof(kQCMimeToRole) / sizeof(kQCMimeToRole[0]); + + size_t i; + for (i = 0; i < kNumMimeToRole; ++i) { + if (!strcasecmp(mime, kQCMimeToRole[i].mime)) { + break; + } + } + + if (i == kNumMimeToRole) { + return; + } + + const char *role = + isEncoder ? kQCMimeToRole[i].encoderRole + : kQCMimeToRole[i].decoderRole; + + if (role != NULL) { + OMX_PARAM_COMPONENTROLETYPE roleParams; + InitOMXParams(&roleParams); + + strncpy((char *)roleParams.cRole, + role, OMX_MAX_STRINGNAME_SIZE - 1); + + roleParams.cRole[OMX_MAX_STRINGNAME_SIZE - 1] = '\0'; + + status_t err = omx->setParameter( + node, OMX_IndexParamStandardComponentRole, + &roleParams, sizeof(roleParams)); + + if (err != OK) { + ALOGW("Failed to set standard component role '%s'.", role); + } + } + +} + +status_t QCOMXCodec::checkQCFormats(int format, AString* meta){ + ALOGV("checkQCFormats called"); + status_t retVal = OK; + if (format == OMX_AUDIO_CodingQCELP13 ) { + *meta = MEDIA_MIMETYPE_AUDIO_QCELP; + } else if(format == OMX_AUDIO_CodingEVRC ) { + *meta = MEDIA_MIMETYPE_AUDIO_EVRC; + } else { + retVal = BAD_VALUE; + } + return retVal; +} + +} diff --git a/media/libstagefright/StagefrightMediaScanner.cpp b/media/libstagefright/StagefrightMediaScanner.cpp index b7cf96e..510252a 100644 --- a/media/libstagefright/StagefrightMediaScanner.cpp +++ b/media/libstagefright/StagefrightMediaScanner.cpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2009 The Android Open Source Project + * Copyright (c) 2012, The Linux Foundation. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -42,7 +43,12 @@ static bool FileHasAcceptableExtension(const char *extension) { ".mpeg", ".ogg", ".mid", ".smf", ".imy", ".wma", ".aac", ".wav", ".amr", ".midi", ".xmf", ".rtttl", ".rtx", ".ota", ".mkv", ".mka", ".webm", ".ts", ".fl", ".flac", ".mxmf", - ".avi", ".mpeg", ".mpg" + ".avi", ".mpg", +#ifndef QCOM_HARDWARE + ".mpeg" +#else + ".qcp", ".awb", ".ac3", ".dts", ".wmv" +#endif }; static const size_t kNumValidExtensions = sizeof(kValidExtensions) / sizeof(kValidExtensions[0]); diff --git a/media/libstagefright/TunnelPlayer.cpp b/media/libstagefright/TunnelPlayer.cpp new file mode 100644 index 0000000..34c260f --- /dev/null +++ b/media/libstagefright/TunnelPlayer.cpp @@ -0,0 +1,782 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * Copyright (c) 2009-2012, The Linux Foundation. All rights reserved. + * Not a Contribution, Apache license notifications and license are retained + * for attribution purposes only. + * + * + * 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_NDDEBUG 0 +#define LOG_NDEBUG 0 +#define LOG_TAG "TunnelPlayer" +#include <utils/Log.h> +#include <utils/threads.h> + +#include <signal.h> +#include <sys/prctl.h> +#include <sys/resource.h> +#include <sys/poll.h> +#include <sys/eventfd.h> +#include <binder/IPCThreadState.h> +#include <media/AudioTrack.h> + +#include <media/stagefright/TunnelPlayer.h> +#include <media/stagefright/MediaDefs.h> +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/foundation/ALooper.h> +#include <media/stagefright/MediaErrors.h> +#include <media/stagefright/MediaSource.h> +#include <media/stagefright/MetaData.h> +#include <media/stagefright/MediaErrors.h> + +#include <hardware_legacy/power.h> + +#include <linux/unistd.h> + +#include "include/AwesomePlayer.h" +#include <powermanager/PowerManager.h> + +static const char mName[] = "TunnelPlayer"; +#define MEM_METADATA_SIZE 64 +#define MEM_BUFFER_SIZE (600*1024 - MEM_METADATA_SIZE) +#define MEM_BUFFER_COUNT 4 + +namespace android { +int TunnelPlayer::mTunnelObjectsAlive = 0; + +TunnelPlayer::TunnelPlayer( + const sp<MediaPlayerBase::AudioSink> &audioSink, bool &initCheck, + AwesomePlayer *observer, bool hasVideo) +:AudioPlayer(audioSink,observer), +mPositionTimeMediaUs(-1), +mPositionTimeRealUs(-1), +mInternalSeeking(false), +mStarted(false), +mA2DPEnabled(false), +mSampleRate(0), +mLatencyUs(0), +mFrameSize(0), +mNumFramesPlayed(0), +mNumFramesPlayedSysTimeUs(0), +mInputBuffer(NULL), +mSeeking(false), +mReachedEOS(false), +mReachedOutputEOS(false), +mFinalStatus(OK), +mSeekTimeUs(0), +mPauseTime(0), +mIsFirstBuffer(false), +mFirstBufferResult(OK), +mFirstBuffer(NULL), +mAudioSink(audioSink), +mObserver(observer) { + ALOGD("TunnelPlayer::TunnelPlayer()"); + mTunnelObjectsAlive++; + numChannels = 0; + mPaused = false; + mIsA2DPEnabled = false; + mAudioFlinger = NULL; + mAudioFlingerClient = NULL; + mFormat = AUDIO_FORMAT_MP3; + mQueue.start(); + mQueueStarted = true; + mPauseEvent = new TunnelEvent(this, &TunnelPlayer::onPauseTimeOut); + mPauseEventPending = false; + + //getAudioFlinger(); + //ALOGD("Registering client with AudioFlinger"); + //mAudioFlinger->registerClient(mAudioFlingerClient); + + mSeekTimeUs = 0; + + mHasVideo = hasVideo; + initCheck = true; + + //mDeathRecipient = new PMDeathRecipient(this); +} +void TunnelPlayer::acquireWakeLock() +{ + /*Mutex::Autolock _l(pmLock); + + if (mPowerManager == 0) { + // use checkService() to avoid blocking if power service is not up yet + sp<IBinder> binder = + defaultServiceManager()->checkService(String16("power")); + if (binder == 0) { + ALOGW("Thread %s cannot connect to the power manager service", mName); + } else { + mPowerManager = interface_cast<IPowerManager>(binder); + binder->linkToDeath(mDeathRecipient); + } + } + if (mPowerManager != 0 && mWakeLockToken == 0) { + sp<IBinder> binder = new BBinder(); + status_t status = mPowerManager->acquireWakeLock(POWERMANAGER_PARTIAL_WAKE_LOCK, + binder, + String16(mName)); + if (status == NO_ERROR) { + mWakeLockToken = binder; + } + ALOGV("acquireWakeLock() %s status %d", mName, status); + }*/ +} + +void TunnelPlayer::releaseWakeLock() +{ + /*Mutex::Autolock _l(pmLock); + + if (mWakeLockToken != 0) { + ALOGV("releaseWakeLock() %s", mName); + if (mPowerManager != 0) { + mPowerManager->releaseWakeLock(mWakeLockToken, 0); + } + mWakeLockToken.clear(); + }*/ +} + +void TunnelPlayer::clearPowerManager() +{ + Mutex::Autolock _l(pmLock); + releaseWakeLock(); + mPowerManager.clear(); +} + +void TunnelPlayer::PMDeathRecipient::binderDied(const wp<IBinder>& who) +{ + parentClass->clearPowerManager(); + ALOGW("power manager service died !!!"); +} + +TunnelPlayer::~TunnelPlayer() { + ALOGD("TunnelPlayer::~TunnelPlayer()"); + if (mQueueStarted) { + mQueue.stop(); + } + + reset(); + //mAudioFlinger->deregisterClient(mAudioFlingerClient); + mTunnelObjectsAlive--; + + releaseWakeLock(); + if (mPowerManager != 0) { + sp<IBinder> binder = mPowerManager->asBinder(); + binder->unlinkToDeath(mDeathRecipient); + } + + +} + +void TunnelPlayer::getAudioFlinger() { +/* Mutex::Autolock _l(mAudioFlingerLock); + + if ( mAudioFlinger.get() == 0 ) { + sp<IServiceManager> sm = defaultServiceManager(); + sp<IBinder> binder; + do { + binder = sm->getService(String16("media.audio_flinger")); + if ( binder != 0 ) + break; + ALOGW("AudioFlinger not published, waiting..."); + usleep(500000); // 0.5 s + } while ( true ); + if ( mAudioFlingerClient == NULL ) { + mAudioFlingerClient = new AudioFlingerTunnelDecodeClient(this); + } + + binder->linkToDeath(mAudioFlingerClient); + mAudioFlinger = interface_cast<IAudioFlinger>(binder); + } + ALOGE_IF(mAudioFlinger==0, "no AudioFlinger!?");*/ +} + +/*TunnelPlayer::AudioFlingerTunnelDecodeClient::AudioFlingerTunnelDecodeClient(void *obj) +{ + ALOGD("TunnelPlayer::AudioFlingerTunnelDecodeClient - Constructor"); + pBaseClass = (TunnelPlayer*)obj; +} + +void TunnelPlayer::AudioFlingerTunnelDecodeClient::binderDied(const wp<IBinder>& who) { + Mutex::Autolock _l(pBaseClass->mAudioFlingerLock); + + pBaseClass->mAudioFlinger.clear(); + ALOGW("AudioFlinger server died!"); +}*/ + +/*void TunnelPlayer::AudioFlingerTunnelDecodeClient::ioConfigChanged(int event, int ioHandle, void *param2) { + ALOGV("ioConfigChanged() event %d", event); + + + if (event != AudioSystem::A2DP_OUTPUT_STATE) { + return; + } + + switch ( event ) { + case AudioSystem::A2DP_OUTPUT_STATE: + { + if ( -1 == ioHandle ) { + if ( pBaseClass->mIsA2DPEnabled ) { + pBaseClass->mIsA2DPEnabled = false; + if (pBaseClass->mStarted) { + pBaseClass->handleA2DPSwitch(); + } + ALOGV("ioConfigChanged:: A2DP Disabled"); + } + } else { + if ( !pBaseClass->mIsA2DPEnabled ) { + pBaseClass->mIsA2DPEnabled = true; + if (pBaseClass->mStarted) { + pBaseClass->handleA2DPSwitch(); + } + ALOGV("ioConfigChanged:: A2DP Enabled"); + } + } + } + break; + default: + break; + } + ALOGV("ioConfigChanged Out"); +}*/ + +/*void TunnelPlayer::handleA2DPSwitch() { + //TODO: Implement +} +*/ + +void TunnelPlayer::setSource(const sp<MediaSource> &source) { + CHECK(mSource == NULL); + ALOGD("Setting source from Tunnel Player"); + mSource = source; +} + +status_t TunnelPlayer::start(bool sourceAlreadyStarted) { + CHECK(!mStarted); + CHECK(mSource != NULL); + + ALOGD("start: sourceAlreadyStarted %d", sourceAlreadyStarted); + //Check if the source is started, start it + status_t err; + if (!sourceAlreadyStarted) { + err = mSource->start(); + if (err != OK) { + return err; + } + } + + //Create decoder and a2dp notification thread and initialize all the + //mutexes and coditional variables + createThreads(); + ALOGV("All Threads Created."); + + // We allow an optional INFO_FORMAT_CHANGED at the very beginning + // of playback, if there is one, getFormat below will retrieve the + // updated format, if there isn't, we'll stash away the valid buffer + // of data to be used on the first audio callback. + + CHECK(mFirstBuffer == NULL); + + MediaSource::ReadOptions options; + if (mSeeking) { + options.setSeekTo(mSeekTimeUs); + mSeeking = false; + } + + mFirstBufferResult = mSource->read(&mFirstBuffer, &options); + if (mFirstBufferResult == INFO_FORMAT_CHANGED) { + ALOGV("INFO_FORMAT_CHANGED!!!"); + CHECK(mFirstBuffer == NULL); + mFirstBufferResult = OK; + mIsFirstBuffer = false; + } else { + mIsFirstBuffer = true; + } + + sp<MetaData> format = mSource->getFormat(); + const char *mime; + bool success = format->findCString(kKeyMIMEType, &mime); + if (!strcasecmp(mime,MEDIA_MIMETYPE_AUDIO_AAC)) { + mFormat = AUDIO_FORMAT_AAC; + } + if (!strcasecmp(mime,MEDIA_MIMETYPE_AUDIO_AMR_WB)) { + mFormat = AUDIO_FORMAT_AMR_WB; + ALOGV("TunnelPlayer::start AUDIO_FORMAT_AMR_WB"); + } +// if (!strcasecmp(mime,MEDIA_MIMETYPE_AUDIO_AMR_WB_PLUS)) { +// mFormat = AUDIO_FORMAT_AMR_WB_PLUS; +// ALOGV("TunnelPlayer::start AUDIO_FORMAT_AMR_WB_PLUS"); +// } + + CHECK(success); + + success = format->findInt32(kKeySampleRate, &mSampleRate); + CHECK(success); + + success = format->findInt32(kKeyChannelCount, &numChannels); + CHECK(success); + + if(!format->findInt32(kKeyChannelMask, &mChannelMask)) { + // log only when there's a risk of ambiguity of channel mask selection + ALOGI_IF(numChannels > 2, + "source format didn't specify channel mask, using (%d) channel order", numChannels); + mChannelMask = CHANNEL_MASK_USE_CHANNEL_ORDER; + } + audio_output_flags_t flags = (audio_output_flags_t) (AUDIO_OUTPUT_FLAG_TUNNEL | + AUDIO_OUTPUT_FLAG_DIRECT); + ALOGV("mAudiosink->open() mSampleRate %d, numChannels %d, mChannelMask %d, flags %d",mSampleRate, numChannels, mChannelMask, flags); + err = mAudioSink->open( + mSampleRate, numChannels, mChannelMask, mFormat, + DEFAULT_AUDIOSINK_BUFFERCOUNT, + &TunnelPlayer::AudioSinkCallback, + this, + (mA2DPEnabled ? AUDIO_OUTPUT_FLAG_NONE : flags)); + + if (err != OK) { + if (mFirstBuffer != NULL) { + mFirstBuffer->release(); + mFirstBuffer = NULL; + } + + if (!sourceAlreadyStarted) { + mSource->stop(); + } + + ALOGE("Opening a routing session failed"); + return err; + } + + if (!mIsA2DPEnabled) { + acquireWakeLock(); + } + + mIsAudioRouted = true; + mStarted = true; + mAudioSink->start(); + ALOGV("Waking up decoder thread"); + pthread_cond_signal(&extractor_cv); + + return OK; +} + +status_t TunnelPlayer::seekTo(int64_t time_us) { + + ALOGV("seekTo: time_us %lld", time_us); + if ( mReachedEOS ) { + mReachedEOS = false; + mReachedOutputEOS = false; + } + mSeeking = true; + mSeekTimeUs = time_us; + ALOGV("In seekTo(), mSeekTimeUs %lld",mSeekTimeUs); + mAudioSink->flush(); + pthread_cond_signal(&extractor_cv); + //TODO: Update the mPauseTime + return OK; +} +void TunnelPlayer::pause(bool playPendingSamples) { + CHECK(mStarted); + if (mPaused) { + return; + } + ALOGV("pause: playPendingSamples %d", playPendingSamples); + mPaused = true; + A2DPState state; + if(!mPauseEventPending) { + ALOGV("Posting an event for Pause timeout"); + mQueue.postEventWithDelay(mPauseEvent, TUNNEL_PAUSE_TIMEOUT_USEC); + mPauseEventPending = true; + } + mPauseTime = mSeekTimeUs + getTimeStamp(A2DP_DISABLED); + if (mAudioSink.get() != NULL) { + ALOGV("AudioSink pause"); + mAudioSink->pause(); + } +} + +void TunnelPlayer::resume() { + ALOGV("resume: mPaused %d",mPaused); + if ( mPaused) { + CHECK(mStarted); + if (!mIsA2DPEnabled) { + if(mPauseEventPending) { + ALOGV("Resume(): Cancelling the puaseTimeout event"); + mPauseEventPending = false; + mQueue.cancelEvent(mPauseEvent->eventID()); + } + + } + audio_format_t format; + + if (!mIsAudioRouted) { + audio_output_flags_t flags = (audio_output_flags_t) (AUDIO_OUTPUT_FLAG_TUNNEL | + AUDIO_OUTPUT_FLAG_DIRECT); + status_t err = mAudioSink->open( + mSampleRate, numChannels, mChannelMask, mFormat, + DEFAULT_AUDIOSINK_BUFFERCOUNT, + &TunnelPlayer::AudioSinkCallback, + this, + (mA2DPEnabled ? AUDIO_OUTPUT_FLAG_NONE : flags )); + if (err != NO_ERROR) { + ALOGE("Audio sink open failed."); + } + mIsAudioRouted = true; + } + mPaused = false; + mAudioSink->start(); + pthread_cond_signal(&extractor_cv); + } +} + +//static +size_t TunnelPlayer::AudioSinkCallback( + MediaPlayerBase::AudioSink *audioSink, + void *buffer, size_t size, void *cookie) { + if (buffer == NULL && size == AudioTrack::EVENT_UNDERRUN) { + TunnelPlayer *me = (TunnelPlayer *)cookie; + me->mReachedEOS = true; + me->mReachedOutputEOS = true; + ALOGV("postAudioEOS"); + me->mObserver->postAudioEOS(0); + } + return 1; +} + +void TunnelPlayer::reset() { + + mReachedEOS = true; + + // make sure Decoder thread has exited + requestAndWaitForExtractorThreadExit(); + + // Close the audiosink after all the threads exited to make sure + mAudioSink->stop(); + mAudioSink->close(); + //TODO: Release Wake lock + + // Make sure to release any buffer we hold onto so that the + // source is able to stop(). + if (mFirstBuffer != NULL) { + mFirstBuffer->release(); + mFirstBuffer = NULL; + } + + if (mInputBuffer != NULL) { + ALOGV("AudioPlayer releasing input buffer."); + mInputBuffer->release(); + mInputBuffer = NULL; + } + + mSource->stop(); + + // The following hack is necessary to ensure that the OMX + // component is completely released by the time we may try + // to instantiate it again. + wp<MediaSource> tmp = mSource; + mSource.clear(); + while (tmp.promote() != NULL) { + usleep(1000); + } + + mPositionTimeMediaUs = -1; + mPositionTimeRealUs = -1; + mSeeking = false; + mReachedEOS = false; + mReachedOutputEOS = false; + mFinalStatus = OK; + mStarted = false; +} + + +bool TunnelPlayer::isSeeking() { + Mutex::Autolock autoLock(mLock); + return mSeeking; +} + +bool TunnelPlayer::reachedEOS(status_t *finalStatus) { + *finalStatus = OK; + Mutex::Autolock autoLock(mLock); + *finalStatus = mFinalStatus; + return mReachedOutputEOS; +} + + +void *TunnelPlayer::extractorThreadWrapper(void *me) { + static_cast<TunnelPlayer *>(me)->extractorThreadEntry(); + return NULL; +} + + +void TunnelPlayer::extractorThreadEntry() { + + pthread_mutex_lock(&extractor_mutex); + uint32_t BufferSizeToUse = MEM_BUFFER_SIZE; + + pid_t tid = gettid(); + androidSetThreadPriority(tid, ANDROID_PRIORITY_AUDIO); + prctl(PR_SET_NAME, (unsigned long)"Tunnel DecodeThread", 0, 0, 0); + + ALOGV("extractorThreadEntry wait for signal \n"); + if (!mStarted) { + pthread_cond_wait(&extractor_cv, &extractor_mutex); + } + ALOGV("extractorThreadEntry ready to work \n"); + pthread_mutex_unlock(&extractor_mutex); + if (killExtractorThread) { + return; + } + if(mSource != NULL) { + sp<MetaData> format = mSource->getFormat(); + const char *mime; + bool success = format->findCString(kKeyMIMEType, &mime); + } + void* local_buf = malloc(BufferSizeToUse); + int bytesWritten = 0; + while (!killExtractorThread) { + + if (mReachedEOS || mPaused || !mIsAudioRouted) { + pthread_mutex_lock(&extractor_mutex); + pthread_cond_wait(&extractor_cv, &extractor_mutex); + pthread_mutex_unlock(&extractor_mutex); + continue; + } + + if (!mIsA2DPEnabled) { + ALOGW("FillBuffer: MemBuffer size %d", BufferSizeToUse); + ALOGV("Fillbuffer started"); + bytesWritten = fillBuffer(local_buf, BufferSizeToUse); + ALOGV("FillBuffer completed bytesToWrite %d", bytesWritten); + if(!killExtractorThread) { + mAudioSink->write(local_buf, bytesWritten); + if(mReachedEOS && bytesWritten) + mAudioSink->write(local_buf, 0); + } + } + } + + free(local_buf); + + //TODO: Call fillbuffer with different size and write to mAudioSink() +} +void TunnelPlayer::createThreads() { + + //Initialize all the Mutexes and Condition Variables + pthread_mutex_init(&extractor_mutex, NULL); + pthread_cond_init (&extractor_cv, NULL); + + // Create 4 threads Effect, decoder, event and A2dp + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); + + killExtractorThread = false; + + extractorThreadAlive = true; + + ALOGV("Creating decoder Thread"); + pthread_create(&extractorThread, &attr, extractorThreadWrapper, this); + + pthread_attr_destroy(&attr); +} +size_t TunnelPlayer::fillBuffer(void *data, size_t size) { + + if (mReachedEOS) { + return 0; + } + + bool postSeekComplete = false; + + size_t size_done = 0; + size_t size_remaining = size; + + while (size_remaining > 0) { + MediaSource::ReadOptions options; + { + Mutex::Autolock autoLock(mLock); + if(mSeeking) { + mInternalSeeking = false; + } + + if (mSeeking || mInternalSeeking) { + if (mIsFirstBuffer) { + if (mFirstBuffer != NULL) { + mFirstBuffer->release(); + mFirstBuffer = NULL; + } + mIsFirstBuffer = false; + } + + MediaSource::ReadOptions::SeekMode seekMode; + seekMode = MediaSource::ReadOptions::SEEK_CLOSEST_SYNC; + options.setSeekTo(mSeekTimeUs, seekMode ); + if (mInputBuffer != NULL) { + mInputBuffer->release(); + mInputBuffer = NULL; + } + + // This is to ignore the data already filled in the output buffer + size_done = 0; + size_remaining = size; + + mSeeking = false; + if (mObserver && !mInternalSeeking) { + ALOGD("fillBuffer: Posting audio seek complete event"); + postSeekComplete = true; + } + mInternalSeeking = false; + } + } + if (mInputBuffer == NULL) { + status_t err; + + if (mIsFirstBuffer) { + mInputBuffer = mFirstBuffer; + mFirstBuffer = NULL; + err = mFirstBufferResult; + + mIsFirstBuffer = false; + } else { + err = mSource->read(&mInputBuffer, &options); + } + + CHECK((err == OK && mInputBuffer != NULL) + || (err != OK && mInputBuffer == NULL)); + { + Mutex::Autolock autoLock(mLock); + + if (err != OK) { + ALOGD("fill buffer - reached eos true"); + mReachedEOS = true; + mFinalStatus = err; + break; + } + } + + } + if (mInputBuffer->range_length() == 0) { + mInputBuffer->release(); + mInputBuffer = NULL; + continue; + } + + size_t copy = size_remaining; + if (copy > mInputBuffer->range_length()) { + copy = mInputBuffer->range_length(); + } + memcpy((char *)data + size_done, + (const char *)mInputBuffer->data() + mInputBuffer->range_offset(), + copy); + + mInputBuffer->set_range(mInputBuffer->range_offset() + copy, + mInputBuffer->range_length() - copy); + + size_done += copy; + size_remaining -= copy; + } + if(mReachedEOS) + memset((char *)data + size_done, 0x0, size_remaining); + ALOGV("fill buffer size_done = %d",size_done); + + if (postSeekComplete) { + mObserver->postAudioSeekComplete(); + } + + return size_done; +} + +int64_t TunnelPlayer::getRealTimeUs() { + Mutex::Autolock autoLock(mLock); + return getRealTimeUsLocked(); +} + + +int64_t TunnelPlayer::getRealTimeUsLocked(){ + //Used for AV sync: irrelevant API for Tunnel. + return 0; +} + +int64_t TunnelPlayer::getTimeStamp(A2DPState state) { + uint64_t timestamp = 0; + switch (state) { + case A2DP_ENABLED: + case A2DP_DISCONNECT: + ALOGV("Get timestamp for A2DP"); + break; + case A2DP_DISABLED: + case A2DP_CONNECT: { + mAudioSink->getTimeStamp(×tamp); + break; + } + default: + break; + } + ALOGV("timestamp %lld ", timestamp); + return timestamp; +} + +int64_t TunnelPlayer::getMediaTimeUs() { + Mutex::Autolock autoLock(mLock); + ALOGV("getMediaTimeUs() mPaused %d mSeekTimeUs %lld mPauseTime %lld", mPaused, mSeekTimeUs, mPauseTime); + if (mPaused) { + return mPauseTime; + } else { + A2DPState state = mIsA2DPEnabled ? A2DP_ENABLED : A2DP_DISABLED; + return (mSeekTimeUs + getTimeStamp(state)); + } +} + +bool TunnelPlayer::getMediaTimeMapping( + int64_t *realtime_us, int64_t *mediatime_us) { + Mutex::Autolock autoLock(mLock); + + *realtime_us = mPositionTimeRealUs; + *mediatime_us = mPositionTimeMediaUs; + + return mPositionTimeRealUs != -1 && mPositionTimeMediaUs != -1; +} + +void TunnelPlayer::requestAndWaitForExtractorThreadExit() { + + if (!extractorThreadAlive) + return; + mAudioSink->flush(); + killExtractorThread = true; + pthread_cond_signal(&extractor_cv); + pthread_join(extractorThread,NULL); + ALOGD("Extractor thread killed"); +} + +void TunnelPlayer::onPauseTimeOut() { + ALOGV("onPauseTimeOut"); + if (!mPauseEventPending) { + return; + } + mPauseEventPending = false; + if(!mIsA2DPEnabled) { + // 1.) Set seek flags + mReachedEOS = false; + mReachedOutputEOS = false; + mSeekTimeUs += getTimeStamp(A2DP_DISABLED); + mInternalSeeking = true; + + // 2.) Close routing Session + mAudioSink->close(); + mIsAudioRouted = false; + + // 3.) Release Wake Lock + releaseWakeLock(); + } + +} + +} //namespace android diff --git a/media/libstagefright/WAVEWriter.cpp b/media/libstagefright/WAVEWriter.cpp new file mode 100644 index 0000000..9700fa7 --- /dev/null +++ b/media/libstagefright/WAVEWriter.cpp @@ -0,0 +1,323 @@ +/* + * Copyright (c) 2012, The Linux Foundation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * * Neither the name of The Linux Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "WAVEWriter" +#include <utils/Log.h> + +#include <media/stagefright/WAVEWriter.h> +#include <media/stagefright/MediaBuffer.h> +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/MediaDefs.h> +#include <media/stagefright/MediaErrors.h> +#include <media/stagefright/MediaSource.h> +#include <media/stagefright/MetaData.h> +#include <media/mediarecorder.h> +#include <sys/prctl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +namespace android { + +static struct wav_header hdr; + + +WAVEWriter::WAVEWriter(const char *filename) + : mFd(-1), + mInitCheck(NO_INIT), + mStarted(false), + mPaused(false), + mResumed(false) { + + mFd = open(filename, O_CREAT | O_LARGEFILE | O_TRUNC | O_RDWR, S_IRUSR | S_IWUSR); + if (mFd >= 0) { + mInitCheck = OK; + } +} + +WAVEWriter::WAVEWriter(int fd) + : mFd(dup(fd)), + mInitCheck(mFd < 0? NO_INIT: OK), + mStarted(false), + mPaused(false), + mResumed(false) { +} + +WAVEWriter::~WAVEWriter() { + if (mStarted) { + stop(); + } + + if (mFd != -1) { + close(mFd); + mFd = -1; + } +} + +status_t WAVEWriter::initCheck() const { + return mInitCheck; +} + +status_t WAVEWriter::addSource(const sp<MediaSource> &source) { + uint32_t count; + if (mInitCheck != OK) { + ALOGE("Init Check not OK, return"); + return mInitCheck; + } + + if (mSource != NULL) { + ALOGE("A source already exists, return"); + return UNKNOWN_ERROR; + } + + sp<MetaData> meta = source->getFormat(); + + const char *mime; + CHECK(meta->findCString(kKeyMIMEType, &mime)); + + int32_t channelCount; + int32_t sampleRate; + CHECK(meta->findInt32(kKeyChannelCount, &channelCount)); + CHECK(meta->findInt32(kKeySampleRate, &sampleRate)); + + memset(&hdr, 0, sizeof(struct wav_header)); + hdr.riff_id = ID_RIFF; + hdr.riff_fmt = ID_WAVE; + hdr.fmt_id = ID_FMT; + hdr.fmt_sz = 16; + hdr.audio_format = FORMAT_PCM; + hdr.num_channels = channelCount; + hdr.sample_rate = sampleRate; + hdr.bits_per_sample = 16; + hdr.byte_rate = (sampleRate * channelCount * hdr.bits_per_sample) / 8; + hdr.block_align = ( hdr.bits_per_sample * channelCount ) / 8; + hdr.data_id = ID_DATA; + hdr.data_sz = 0; + hdr.riff_sz = hdr.data_sz + 44 - 8; + + if (write(mFd, &hdr, sizeof(hdr)) != sizeof(hdr)) { + ALOGE("Write header error, return ERROR_IO"); + return -ERROR_IO; + } + + mSource = source; + + return OK; +} + +status_t WAVEWriter::start(MetaData *params) { + if (mInitCheck != OK) { + ALOGE("Init Check not OK, return"); + return mInitCheck; + } + + if (mSource == NULL) { + ALOGE("NULL Source"); + return UNKNOWN_ERROR; + } + + if (mStarted && mPaused) { + mPaused = false; + mResumed = true; + return OK; + } else if (mStarted) { + ALOGE("Already startd, return"); + return OK; + } + + status_t err = mSource->start(); + + if (err != OK) { + return err; + } + + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); + + mReachedEOS = false; + mDone = false; + + pthread_create(&mThread, &attr, ThreadWrapper, this); + pthread_attr_destroy(&attr); + + mStarted = true; + + return OK; +} + +status_t WAVEWriter::pause() { + if (!mStarted) { + return OK; + } + mPaused = true; + return OK; +} + +status_t WAVEWriter::stop() { + if (!mStarted) { + return OK; + } + + mDone = true; + + void *dummy; + pthread_join(mThread, &dummy); + + status_t err = (status_t) dummy; + { + status_t status = mSource->stop(); + if (err == OK && + (status != OK && status != ERROR_END_OF_STREAM)) { + err = status; + } + } + + mStarted = false; + return err; +} + +bool WAVEWriter::exceedsFileSizeLimit() { + if (mMaxFileSizeLimitBytes == 0) { + return false; + } + return mEstimatedSizeBytes >= mMaxFileSizeLimitBytes; +} + +bool WAVEWriter::exceedsFileDurationLimit() { + if (mMaxFileDurationLimitUs == 0) { + return false; + } + return mEstimatedDurationUs >= mMaxFileDurationLimitUs; +} + +// static +void *WAVEWriter::ThreadWrapper(void *me) { + return (void *) static_cast<WAVEWriter *>(me)->threadFunc(); +} + +status_t WAVEWriter::threadFunc() { + mEstimatedDurationUs = 0; + mEstimatedSizeBytes = 0; + bool stoppedPrematurely = true; + int64_t previousPausedDurationUs = 0; + int64_t maxTimestampUs = 0; + status_t err = OK; + + prctl(PR_SET_NAME, (unsigned long)"WAVEWriter", 0, 0, 0); + hdr.data_sz = 0; + while (!mDone) { + MediaBuffer *buffer; + err = mSource->read(&buffer); + + if (err != OK) { + break; + } + + if (mPaused) { + buffer->release(); + buffer = NULL; + continue; + } + + mEstimatedSizeBytes += buffer->range_length(); + if (exceedsFileSizeLimit()) { + buffer->release(); + buffer = NULL; + notify(MEDIA_RECORDER_EVENT_INFO, MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED, 0); + break; + } + + int64_t timestampUs; + CHECK(buffer->meta_data()->findInt64(kKeyTime, ×tampUs)); + if (timestampUs > mEstimatedDurationUs) { + mEstimatedDurationUs = timestampUs; + } + if (mResumed) { + previousPausedDurationUs += (timestampUs - maxTimestampUs - 20000); + mResumed = false; + } + timestampUs -= previousPausedDurationUs; + ALOGV("time stamp: %lld, previous paused duration: %lld", + timestampUs, previousPausedDurationUs); + if (timestampUs > maxTimestampUs) { + maxTimestampUs = timestampUs; + } + + if (exceedsFileDurationLimit()) { + buffer->release(); + buffer = NULL; + notify(MEDIA_RECORDER_EVENT_INFO, MEDIA_RECORDER_INFO_MAX_DURATION_REACHED, 0); + break; + } + ssize_t n = write(mFd, + (const uint8_t *)buffer->data() + buffer->range_offset(), + buffer->range_length()); + + hdr.data_sz += (ssize_t)buffer->range_length(); + hdr.riff_sz = hdr.data_sz + 44 - 8; + + if (n < (ssize_t)buffer->range_length()) { + buffer->release(); + buffer = NULL; + + break; + } + + if (stoppedPrematurely) { + stoppedPrematurely = false; + } + + buffer->release(); + buffer = NULL; + } + + if (stoppedPrematurely) { + notify(MEDIA_RECORDER_EVENT_INFO, MEDIA_RECORDER_TRACK_INFO_COMPLETION_STATUS, UNKNOWN_ERROR); + } + + lseek(mFd, 0, SEEK_SET); + write(mFd, &hdr, sizeof(hdr)); + lseek(mFd, 0, SEEK_END); + + close(mFd); + mFd = -1; + mReachedEOS = true; + if (err == ERROR_END_OF_STREAM) { + return OK; + } + return err; +} + +bool WAVEWriter::reachedEOS() { + return mReachedEOS; +} + +} // namespace android diff --git a/media/libstagefright/include/AwesomePlayer.h b/media/libstagefright/include/AwesomePlayer.h index 1422687..107c5da 100644 --- a/media/libstagefright/include/AwesomePlayer.h +++ b/media/libstagefright/include/AwesomePlayer.h @@ -200,6 +200,9 @@ private: bool mWatchForAudioSeekComplete; bool mWatchForAudioEOS; +#ifdef QCOM_ENHANCED_AUDIO + static int mTunnelAliveAP; +#endif sp<TimedEventQueue::Event> mVideoEvent; bool mVideoEventPending; @@ -339,6 +342,11 @@ private: size_t countTracks() const; +#ifdef QCOM_ENHANCED_AUDIO + //Flag to check if tunnel mode audio is enabled + bool mIsTunnelAudio; +#endif + AwesomePlayer(const AwesomePlayer &); AwesomePlayer &operator=(const AwesomePlayer &); }; diff --git a/media/libstagefright/include/ExtendedExtractor.h b/media/libstagefright/include/ExtendedExtractor.h new file mode 100644 index 0000000..e7d8704 --- /dev/null +++ b/media/libstagefright/include/ExtendedExtractor.h @@ -0,0 +1,58 @@ +/*Copyright (c) 2012, The Linux Foundation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * * Neither the name of The Linux Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef EXTENDED_EXTRACTOR_ +#define EXTENDED_EXTRACTOR_ + +#include <media/stagefright/DataSource.h> + +namespace android { + +class MediaExtractor; + +typedef MediaExtractor* (*MediaExtractorFactory)(const sp<DataSource> &source, const char* mime); + +static const char* MEDIA_CREATE_EXTRACTOR = "CreateExtractor"; + +typedef bool (*ExtendedExtractorSniffers)(const sp<DataSource> &source, String8 *mimeType, + float *confidence,sp<AMessage> *meta); + +static const char* EXTENDED_EXTRACTOR_SNIFFERS = "SniffExtendedExtractor"; + +class ExtendedExtractor +{ +public: + static MediaExtractor* CreateExtractor(const sp<DataSource> &source, const char *mime); +}; + +bool SniffExtendedExtractor(const sp<DataSource> &source, String8 *mimeType, + float *confidence,sp<AMessage> *meta); + +} // namespace android + +#endif //EXTENDED_EXTRACTOR_ |