/* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ //#define USE_LOG SLAndroidLogLevel_Verbose #include "sles_allinclusive.h" #include "android/android_AudioSfDecoder.h" #include "android/channels.h" #include #include #include #include #include #include #include #include #include #include #include #include #define SIZE_CACHED_HIGH_BYTES 1000000 #define SIZE_CACHED_MED_BYTES 700000 #define SIZE_CACHED_LOW_BYTES 400000 namespace android { //-------------------------------------------------------------------------------------------------- AudioSfDecoder::AudioSfDecoder(const AudioPlayback_Parameters* params) : GenericPlayer(params), mDataSource(0), mAudioSource(0), mAudioSourceStarted(false), mBitrate(-1), mDurationUsec(ANDROID_UNKNOWN_TIME), mDecodeBuffer(NULL), mSeekTimeMsec(0), // play event logic depends on the initial time being zero not ANDROID_UNKNOWN_TIME mLastDecodedPositionUs(0) { SL_LOGD("AudioSfDecoder::AudioSfDecoder()"); } AudioSfDecoder::~AudioSfDecoder() { SL_LOGD("AudioSfDecoder::~AudioSfDecoder()"); } void AudioSfDecoder::preDestroy() { GenericPlayer::preDestroy(); SL_LOGD("AudioSfDecoder::preDestroy()"); { Mutex::Autolock _l(mBufferSourceLock); if (NULL != mDecodeBuffer) { mDecodeBuffer->release(); mDecodeBuffer = NULL; } if ((mAudioSource != 0) && mAudioSourceStarted) { mAudioSource->stop(); mAudioSourceStarted = false; } } } //-------------------------------------------------- void AudioSfDecoder::play() { SL_LOGD("AudioSfDecoder::play"); GenericPlayer::play(); (new AMessage(kWhatDecode, this))->post(); } void AudioSfDecoder::getPositionMsec(int* msec) { int64_t timeUsec = getPositionUsec(); if (timeUsec == ANDROID_UNKNOWN_TIME) { *msec = ANDROID_UNKNOWN_TIME; } else { *msec = timeUsec / 1000; } } //-------------------------------------------------- uint32_t AudioSfDecoder::getPcmFormatKeyCount() const { return NB_PCMMETADATA_KEYS; } //-------------------------------------------------- bool AudioSfDecoder::getPcmFormatKeySize(uint32_t index, uint32_t* pKeySize) { if (index >= NB_PCMMETADATA_KEYS) { return false; } else { *pKeySize = strlen(kPcmDecodeMetadataKeys[index]) +1; return true; } } //-------------------------------------------------- bool AudioSfDecoder::getPcmFormatKeyName(uint32_t index, uint32_t keySize, char* keyName) { uint32_t actualKeySize; if (!getPcmFormatKeySize(index, &actualKeySize)) { return false; } if (keySize < actualKeySize) { return false; } strncpy(keyName, kPcmDecodeMetadataKeys[index], actualKeySize); return true; } //-------------------------------------------------- bool AudioSfDecoder::getPcmFormatValueSize(uint32_t index, uint32_t* pValueSize) { if (index >= NB_PCMMETADATA_KEYS) { *pValueSize = 0; return false; } else { *pValueSize = sizeof(uint32_t); return true; } } //-------------------------------------------------- bool AudioSfDecoder::getPcmFormatKeyValue(uint32_t index, uint32_t size, uint32_t* pValue) { uint32_t valueSize = 0; if (!getPcmFormatValueSize(index, &valueSize)) { return false; } else if (size != valueSize) { // this ensures we are accessing mPcmFormatValues with a valid size for that index SL_LOGE("Error retrieving metadata value at index %d: using size of %d, should be %d", index, size, valueSize); return false; } else { android::Mutex::Autolock autoLock(mPcmFormatLock); *pValue = mPcmFormatValues[index]; return true; } } //-------------------------------------------------- // Event handlers // it is strictly verboten to call those methods outside of the event loop // Initializes the data and audio sources, and update the PCM format info // post-condition: upon successful initialization based on the player data locator // GenericPlayer::onPrepare() was called // mDataSource != 0 // mAudioSource != 0 // mAudioSourceStarted == true // All error returns from this method are via notifyPrepared(status) followed by "return". void AudioSfDecoder::onPrepare() { SL_LOGD("AudioSfDecoder::onPrepare()"); Mutex::Autolock _l(mBufferSourceLock); { android::Mutex::Autolock autoLock(mPcmFormatLock); // Initialize the PCM format info with the known parameters before the start of the decode mPcmFormatValues[ANDROID_KEY_INDEX_PCMFORMAT_BITSPERSAMPLE] = SL_PCMSAMPLEFORMAT_FIXED_16; mPcmFormatValues[ANDROID_KEY_INDEX_PCMFORMAT_CONTAINERSIZE] = 16; mPcmFormatValues[ANDROID_KEY_INDEX_PCMFORMAT_ENDIANNESS] = SL_BYTEORDER_LITTLEENDIAN; // initialization with the default values: they will be replaced by the actual values // once the decoder has figured them out mPcmFormatValues[ANDROID_KEY_INDEX_PCMFORMAT_NUMCHANNELS] = UNKNOWN_NUMCHANNELS; mPcmFormatValues[ANDROID_KEY_INDEX_PCMFORMAT_SAMPLERATE] = UNKNOWN_SAMPLERATE; mPcmFormatValues[ANDROID_KEY_INDEX_PCMFORMAT_CHANNELMASK] = SL_ANDROID_UNKNOWN_CHANNELMASK; } //--------------------------------- // Instantiate and initialize the data source for the decoder sp dataSource; switch (mDataLocatorType) { case kDataLocatorNone: SL_LOGE("AudioSfDecoder::onPrepare: no data locator set"); notifyPrepared(MEDIA_ERROR_BASE); return; case kDataLocatorUri: dataSource = DataSourceFactory::getInstance()->CreateFromURI( NULL /* XXX httpService */, mDataLocator.uriRef); if (dataSource == NULL) { SL_LOGE("AudioSfDecoder::onPrepare(): Error opening %s", mDataLocator.uriRef); notifyPrepared(MEDIA_ERROR_BASE); return; } break; case kDataLocatorFd: { // As FileSource unconditionally takes ownership of the fd and closes it, then // we have to make a dup for FileSource if the app wants to keep ownership itself int fd = mDataLocator.fdi.fd; if (mDataLocator.fdi.mCloseAfterUse) { mDataLocator.fdi.mCloseAfterUse = false; } else { fd = ::dup(fd); } dataSource = new FileSource(fd, mDataLocator.fdi.offset, mDataLocator.fdi.length); status_t err = dataSource->initCheck(); if (err != OK) { notifyPrepared(err); return; } break; } // AndroidBufferQueue data source is handled by a subclass, // which does not call up to this method. Hence, the missing case. default: TRESPASS(); } //--------------------------------- // Instantiate and initialize the decoder attached to the data source sp extractor = MediaExtractorFactory::Create(dataSource); if (extractor == NULL) { SL_LOGE("AudioSfDecoder::onPrepare: Could not instantiate extractor."); notifyPrepared(ERROR_UNSUPPORTED); return; } ssize_t audioTrackIndex = -1; bool isRawAudio = false; for (size_t i = 0; i < extractor->countTracks(); ++i) { sp meta = extractor->getTrackMetaData(i); const char *mime; CHECK(meta->findCString(kKeyMIMEType, &mime)); if (!strncasecmp("audio/", mime, 6)) { if (isSupportedCodec(mime)) { audioTrackIndex = i; if (!strcasecmp(MEDIA_MIMETYPE_AUDIO_RAW, mime)) { isRawAudio = true; } break; } } } if (audioTrackIndex < 0) { SL_LOGE("AudioSfDecoder::onPrepare: Could not find a supported audio track."); notifyPrepared(ERROR_UNSUPPORTED); return; } sp source = CreateMediaSourceFromIMediaSource( extractor->getTrack(audioTrackIndex)); sp meta = source->getFormat(); // we can't trust the OMXCodec (if there is one) to issue a INFO_FORMAT_CHANGED so we want // to have some meaningful values as soon as possible. int32_t channelCount; bool hasChannelCount = meta->findInt32(kKeyChannelCount, &channelCount); int32_t sr; bool hasSampleRate = meta->findInt32(kKeySampleRate, &sr); // first compute the duration off64_t size; int64_t durationUs; int32_t durationMsec; if (dataSource->getSize(&size) == OK && meta->findInt64(kKeyDuration, &durationUs)) { if (durationUs != 0) { mBitrate = size * 8000000LL / durationUs; // in bits/sec } else { mBitrate = -1; } mDurationUsec = durationUs; durationMsec = durationUs / 1000; } else { mBitrate = -1; mDurationUsec = ANDROID_UNKNOWN_TIME; durationMsec = ANDROID_UNKNOWN_TIME; } // then assign the duration under the settings lock { Mutex::Autolock _l(mSettingsLock); mDurationMsec = durationMsec; } // the audio content is not raw PCM, so we need a decoder if (!isRawAudio) { source = SimpleDecodingSource::Create(source); if (source == NULL) { SL_LOGE("AudioSfDecoder::onPrepare: Could not instantiate decoder."); notifyPrepared(ERROR_UNSUPPORTED); return; } meta = source->getFormat(); } if (source->start() != OK) { SL_LOGE("AudioSfDecoder::onPrepare: Failed to start source/decoder."); notifyPrepared(MEDIA_ERROR_BASE); return; } //--------------------------------- // The data source, and audio source (a decoder if required) are ready to be used mDataSource = dataSource; mAudioSource = source; mAudioSourceStarted = true; if (!hasChannelCount) { CHECK(meta->findInt32(kKeyChannelCount, &channelCount)); } if (!hasSampleRate) { CHECK(meta->findInt32(kKeySampleRate, &sr)); } // FIXME add code below once channel mask support is in, currently initialized to default // value computed from the channel count // if (!hasChannelMask) { // CHECK(meta->findInt32(kKeyChannelMask, &channelMask)); // } if (!wantPrefetch()) { SL_LOGV("AudioSfDecoder::onPrepare: no need to prefetch"); // doesn't need prefetching, notify good to go mCacheStatus = kStatusHigh; mCacheFill = 1000; notifyStatus(); notifyCacheFill(); } { android::Mutex::Autolock autoLock(mPcmFormatLock); mPcmFormatValues[ANDROID_KEY_INDEX_PCMFORMAT_SAMPLERATE] = sr; mPcmFormatValues[ANDROID_KEY_INDEX_PCMFORMAT_NUMCHANNELS] = channelCount; mPcmFormatValues[ANDROID_KEY_INDEX_PCMFORMAT_CHANNELMASK] = sles_channel_out_mask_from_count(channelCount); } // at this point we have enough information about the source to create the sink that // will consume the data createAudioSink(); // signal successful completion of prepare mStateFlags |= kFlagPrepared; GenericPlayer::onPrepare(); SL_LOGD("AudioSfDecoder::onPrepare() done, mStateFlags=0x%x", mStateFlags); } void AudioSfDecoder::onPause() { SL_LOGV("AudioSfDecoder::onPause()"); GenericPlayer::onPause(); pauseAudioSink(); } void AudioSfDecoder::onPlay() { SL_LOGV("AudioSfDecoder::onPlay()"); GenericPlayer::onPlay(); startAudioSink(); } void AudioSfDecoder::onSeek(const sp &msg) { SL_LOGV("AudioSfDecoder::onSeek"); int64_t timeMsec; CHECK(msg->findInt64(WHATPARAM_SEEK_SEEKTIME_MS, &timeMsec)); Mutex::Autolock _l(mTimeLock); mStateFlags |= kFlagSeeking; mSeekTimeMsec = timeMsec; // don't set mLastDecodedPositionUs to ANDROID_UNKNOWN_TIME; getPositionUsec // ignores mLastDecodedPositionUs while seeking, and substitutes the seek goal instead // nop for now GenericPlayer::onSeek(msg); } void AudioSfDecoder::onLoop(const sp &msg) { SL_LOGV("AudioSfDecoder::onLoop"); int32_t loop; CHECK(msg->findInt32(WHATPARAM_LOOP_LOOPING, &loop)); if (loop) { //SL_LOGV("AudioSfDecoder::onLoop start looping"); mStateFlags |= kFlagLooping; } else { //SL_LOGV("AudioSfDecoder::onLoop stop looping"); mStateFlags &= ~kFlagLooping; } // nop for now GenericPlayer::onLoop(msg); } void AudioSfDecoder::onCheckCache(const sp &msg) { //SL_LOGV("AudioSfDecoder::onCheckCache"); bool eos; CacheStatus_t status = getCacheRemaining(&eos); if (eos || status == kStatusHigh || ((mStateFlags & kFlagPreparing) && (status >= kStatusEnough))) { if (mStateFlags & kFlagPlaying) { startAudioSink(); } mStateFlags &= ~kFlagBuffering; SL_LOGV("AudioSfDecoder::onCheckCache: buffering done."); if (mStateFlags & kFlagPreparing) { //SL_LOGV("AudioSfDecoder::onCheckCache: preparation done."); mStateFlags &= ~kFlagPreparing; } if (mStateFlags & kFlagPlaying) { (new AMessage(kWhatDecode, this))->post(); } return; } msg->post(100000); } void AudioSfDecoder::onDecode() { SL_LOGV("AudioSfDecoder::onDecode"); //-------------------------------- Need to buffer some more before decoding? bool eos; if (mDataSource == 0) { // application set play state to paused which failed, then set play state to playing return; } if (wantPrefetch() && (getCacheRemaining(&eos) == kStatusLow) && !eos) { SL_LOGV("buffering more."); if (mStateFlags & kFlagPlaying) { pauseAudioSink(); } mStateFlags |= kFlagBuffering; (new AMessage(kWhatCheckCache, this))->post(100000); return; } if (!(mStateFlags & (kFlagPlaying | kFlagBuffering | kFlagPreparing))) { // don't decode if we're not buffering, prefetching or playing //SL_LOGV("don't decode: not buffering, prefetching or playing"); return; } //-------------------------------- Decode status_t err; MediaSource::ReadOptions readOptions; if (mStateFlags & kFlagSeeking) { assert(mSeekTimeMsec != ANDROID_UNKNOWN_TIME); readOptions.setSeekTo(mSeekTimeMsec * 1000); } int64_t timeUsec = ANDROID_UNKNOWN_TIME; { Mutex::Autolock _l(mBufferSourceLock); if (NULL != mDecodeBuffer) { // the current decoded buffer hasn't been rendered, drop it mDecodeBuffer->release(); mDecodeBuffer = NULL; } if (!mAudioSourceStarted) { return; } err = mAudioSource->read(&mDecodeBuffer, &readOptions); if (err == OK) { // FIXME workaround apparent bug in AAC decoder: kKeyTime is 3 frames old if length is 0 if (mDecodeBuffer->range_length() == 0) { timeUsec = ANDROID_UNKNOWN_TIME; } else { CHECK(mDecodeBuffer->meta_data().findInt64(kKeyTime, &timeUsec)); } } else { // errors are handled below } } { Mutex::Autolock _l(mTimeLock); if (mStateFlags & kFlagSeeking) { mStateFlags &= ~kFlagSeeking; mSeekTimeMsec = ANDROID_UNKNOWN_TIME; } if (timeUsec != ANDROID_UNKNOWN_TIME) { // Note that though we've decoded this position, we haven't rendered it yet. // So a GetPosition called after this point will observe the advanced position, // even though the PCM may not have been supplied to the sink. That's OK as // we don't claim to provide AAC frame-accurate (let alone sample-accurate) GetPosition. mLastDecodedPositionUs = timeUsec; } } //-------------------------------- Handle return of decode if (err != OK) { bool continueDecoding = false; switch (err) { case ERROR_END_OF_STREAM: if (0 < mDurationUsec) { Mutex::Autolock _l(mTimeLock); mLastDecodedPositionUs = mDurationUsec; } // handle notification and looping at end of stream if (mStateFlags & kFlagPlaying) { notify(PLAYEREVENT_ENDOFSTREAM, 1, true /*async*/); } if (mStateFlags & kFlagLooping) { seek(0); // kick-off decoding again continueDecoding = true; } break; case INFO_FORMAT_CHANGED: SL_LOGD("MediaSource::read encountered INFO_FORMAT_CHANGED"); // reconfigure output { Mutex::Autolock _l(mBufferSourceLock); hasNewDecodeParams(); } continueDecoding = true; break; case INFO_DISCONTINUITY: SL_LOGD("MediaSource::read encountered INFO_DISCONTINUITY"); continueDecoding = true; break; default: SL_LOGE("MediaSource::read returned error %d", err); break; } if (continueDecoding) { if (NULL == mDecodeBuffer) { (new AMessage(kWhatDecode, this))->post(); return; } } else { return; } } //-------------------------------- Render sp msg = new AMessage(kWhatRender, this); msg->post(); } void AudioSfDecoder::onMessageReceived(const sp &msg) { switch (msg->what()) { case kWhatDecode: onDecode(); break; case kWhatRender: onRender(); break; case kWhatCheckCache: onCheckCache(msg); break; default: GenericPlayer::onMessageReceived(msg); break; } } //-------------------------------------------------- // Prepared state, prefetch status notifications void AudioSfDecoder::notifyPrepared(status_t prepareRes) { assert(!(mStateFlags & (kFlagPrepared | kFlagPreparedUnsuccessfully))); if (NO_ERROR == prepareRes) { // The "then" fork is not currently used, but is kept here to make it easier // to replace by a new signalPrepareCompletion(status) if we re-visit this later. mStateFlags |= kFlagPrepared; } else { mStateFlags |= kFlagPreparedUnsuccessfully; } // Do not call the superclass onPrepare to notify, because it uses a default error // status code but we can provide a more specific one. // GenericPlayer::onPrepare(); notify(PLAYEREVENT_PREPARED, (int32_t)prepareRes, true /*async*/); SL_LOGD("AudioSfDecoder::onPrepare() done, mStateFlags=0x%x", mStateFlags); } void AudioSfDecoder::onNotify(const sp &msg) { notif_cbf_t notifyClient; void* notifyUser; { android::Mutex::Autolock autoLock(mNotifyClientLock); if (NULL == mNotifyClient) { return; } else { notifyClient = mNotifyClient; notifyUser = mNotifyUser; } } int32_t val; if (msg->findInt32(PLAYEREVENT_PREFETCHSTATUSCHANGE, &val)) { SL_LOGV("\tASfPlayer notifying %s = %d", PLAYEREVENT_PREFETCHSTATUSCHANGE, val); notifyClient(kEventPrefetchStatusChange, val, 0, notifyUser); } else if (msg->findInt32(PLAYEREVENT_PREFETCHFILLLEVELUPDATE, &val)) { SL_LOGV("\tASfPlayer notifying %s = %d", PLAYEREVENT_PREFETCHFILLLEVELUPDATE, val); notifyClient(kEventPrefetchFillLevelUpdate, val, 0, notifyUser); } else if (msg->findInt32(PLAYEREVENT_ENDOFSTREAM, &val)) { SL_LOGV("\tASfPlayer notifying %s = %d", PLAYEREVENT_ENDOFSTREAM, val); notifyClient(kEventEndOfStream, val, 0, notifyUser); } else { GenericPlayer::onNotify(msg); } } //-------------------------------------------------- // Private utility functions bool AudioSfDecoder::wantPrefetch() { if (mDataSource != 0) { return (mDataSource->flags() & DataSource::kWantsPrefetching); } else { // happens if an improper data locator was passed, if the media extractor couldn't be // initialized, if there is no audio track in the media, if the OMX decoder couldn't be // instantiated, if the source couldn't be opened, or if the MediaSource // couldn't be started SL_LOGV("AudioSfDecoder::wantPrefetch() tries to access NULL mDataSource"); return false; } } int64_t AudioSfDecoder::getPositionUsec() { Mutex::Autolock _l(mTimeLock); if (mStateFlags & kFlagSeeking) { return mSeekTimeMsec * 1000; } else { return mLastDecodedPositionUs; } } CacheStatus_t AudioSfDecoder::getCacheRemaining(bool *eos) { sp cachedSource = static_cast(mDataSource.get()); CacheStatus_t oldStatus = mCacheStatus; status_t finalStatus; size_t dataRemaining = cachedSource->approxDataRemaining(&finalStatus); *eos = (finalStatus != OK); CHECK_GE(mBitrate, 0); int64_t dataRemainingUs = dataRemaining * 8000000LL / mBitrate; //SL_LOGV("AudioSfDecoder::getCacheRemaining: approx %.2f secs remaining (eos=%d)", // dataRemainingUs / 1E6, *eos); if (*eos) { // data is buffered up to the end of the stream, it can't get any better than this mCacheStatus = kStatusHigh; mCacheFill = 1000; } else { if (mDurationUsec > 0) { // known duration: // fill level is ratio of how much has been played + how much is // cached, divided by total duration int64_t currentPositionUsec = getPositionUsec(); if (currentPositionUsec == ANDROID_UNKNOWN_TIME) { // if we don't know where we are, assume the worst for the fill ratio currentPositionUsec = 0; } if (mDurationUsec > 0) { mCacheFill = (int16_t) ((1000.0 * (double)(currentPositionUsec + dataRemainingUs) / mDurationUsec)); } else { mCacheFill = 0; } //SL_LOGV("cacheFill = %d", mCacheFill); // cache status is evaluated against duration thresholds if (dataRemainingUs > DURATION_CACHED_HIGH_MS*1000) { mCacheStatus = kStatusHigh; //ALOGV("high"); } else if (dataRemainingUs > DURATION_CACHED_MED_MS*1000) { //ALOGV("enough"); mCacheStatus = kStatusEnough; } else if (dataRemainingUs < DURATION_CACHED_LOW_MS*1000) { //ALOGV("low"); mCacheStatus = kStatusLow; } else { mCacheStatus = kStatusIntermediate; } } else { // unknown duration: // cache status is evaluated against cache amount thresholds // (no duration so we don't have the bitrate either, could be derived from format?) if (dataRemaining > SIZE_CACHED_HIGH_BYTES) { mCacheStatus = kStatusHigh; } else if (dataRemaining > SIZE_CACHED_MED_BYTES) { mCacheStatus = kStatusEnough; } else if (dataRemaining < SIZE_CACHED_LOW_BYTES) { mCacheStatus = kStatusLow; } else { mCacheStatus = kStatusIntermediate; } } } if (oldStatus != mCacheStatus) { notifyStatus(); } if (abs(mCacheFill - mLastNotifiedCacheFill) > mCacheFillNotifThreshold) { notifyCacheFill(); } return mCacheStatus; } void AudioSfDecoder::hasNewDecodeParams() { if ((mAudioSource != 0) && mAudioSourceStarted) { sp meta = mAudioSource->getFormat(); int32_t channelCount; CHECK(meta->findInt32(kKeyChannelCount, &channelCount)); int32_t sr; CHECK(meta->findInt32(kKeySampleRate, &sr)); // FIXME similar to onPrepare() { android::Mutex::Autolock autoLock(mPcmFormatLock); SL_LOGV("format changed: old sr=%d, channels=%d; new sr=%d, channels=%d", mPcmFormatValues[ANDROID_KEY_INDEX_PCMFORMAT_SAMPLERATE], mPcmFormatValues[ANDROID_KEY_INDEX_PCMFORMAT_NUMCHANNELS], sr, channelCount); mPcmFormatValues[ANDROID_KEY_INDEX_PCMFORMAT_NUMCHANNELS] = channelCount; mPcmFormatValues[ANDROID_KEY_INDEX_PCMFORMAT_SAMPLERATE] = sr; mPcmFormatValues[ANDROID_KEY_INDEX_PCMFORMAT_CHANNELMASK] = sles_channel_out_mask_from_count(channelCount); } // there's no need to do a notify of PLAYEREVENT_CHANNEL_COUNT, // because the only listener is for volume updates, and decoders don't support that } // alert users of those params updateAudioSink(); } static const char* const kPlaybackOnlyCodecs[] = { MEDIA_MIMETYPE_AUDIO_AMR_NB, MEDIA_MIMETYPE_AUDIO_AMR_WB }; #define NB_PLAYBACK_ONLY_CODECS (sizeof(kPlaybackOnlyCodecs)/sizeof(kPlaybackOnlyCodecs[0])) bool AudioSfDecoder::isSupportedCodec(const char* mime) { bool codecRequiresPermission = false; for (unsigned int i = 0 ; i < NB_PLAYBACK_ONLY_CODECS ; i++) { if (!strcasecmp(mime, kPlaybackOnlyCodecs[i])) { codecRequiresPermission = true; break; } } if (codecRequiresPermission) { // verify only the system can decode, for playback only return checkCallingPermission( String16("android.permission.ALLOW_ANY_CODEC_FOR_PLAYBACK")); } else { return true; } } } // namespace android