diff options
Diffstat (limited to 'media/libstagefright/ACodec.cpp')
-rw-r--r-- | media/libstagefright/ACodec.cpp | 1238 |
1 files changed, 1031 insertions, 207 deletions
diff --git a/media/libstagefright/ACodec.cpp b/media/libstagefright/ACodec.cpp index 1a87e9c..0060b88 100644 --- a/media/libstagefright/ACodec.cpp +++ b/media/libstagefright/ACodec.cpp @@ -17,6 +17,10 @@ //#define LOG_NDEBUG 0 #define LOG_TAG "ACodec" +#ifdef __LP64__ +#define OMX_ANDROID_COMPILE_AS_32BIT_ON_64BIT_PLATFORMS +#endif + #include <inttypes.h> #include <utils/Trace.h> @@ -38,12 +42,57 @@ #include <media/hardware/HardwareAPI.h> +#include <OMX_AudioExt.h> +#include <OMX_VideoExt.h> #include <OMX_Component.h> +#include <OMX_IndexExt.h> #include "include/avc_utils.h" namespace android { +// OMX errors are directly mapped into status_t range if +// there is no corresponding MediaError status code. +// Use the statusFromOMXError(int32_t omxError) function. +// +// Currently this is a direct map. +// See frameworks/native/include/media/openmax/OMX_Core.h +// +// Vendor OMX errors from 0x90000000 - 0x9000FFFF +// Extension OMX errors from 0x8F000000 - 0x90000000 +// Standard OMX errors from 0x80001000 - 0x80001024 (0x80001024 current) +// + +// returns true if err is a recognized OMX error code. +// as OMX error is OMX_S32, this is an int32_t type +static inline bool isOMXError(int32_t err) { + return (ERROR_CODEC_MIN <= err && err <= ERROR_CODEC_MAX); +} + +// converts an OMX error to a status_t +static inline status_t statusFromOMXError(int32_t omxError) { + switch (omxError) { + case OMX_ErrorInvalidComponentName: + case OMX_ErrorComponentNotFound: + return NAME_NOT_FOUND; // can trigger illegal argument error for provided names. + default: + return isOMXError(omxError) ? omxError : 0; // no translation required + } +} + +// checks and converts status_t to a non-side-effect status_t +static inline status_t makeNoSideEffectStatus(status_t err) { + switch (err) { + // the following errors have side effects and may come + // from other code modules. Remap for safety reasons. + case INVALID_OPERATION: + case DEAD_OBJECT: + return UNKNOWN_ERROR; + default: + return err; + } +} + template<class T> static void InitOMXParams(T *params) { params->nSize = sizeof(T); @@ -98,12 +147,6 @@ struct CodecObserver : public BnOMXObserver { msg->setInt64( "timestamp", omx_msg.u.extended_buffer_data.timestamp); - msg->setPointer( - "platform_private", - omx_msg.u.extended_buffer_data.platform_private); - msg->setPointer( - "data_ptr", - omx_msg.u.extended_buffer_data.data_ptr); break; } @@ -158,9 +201,7 @@ private: IOMX::buffer_id bufferID, size_t rangeOffset, size_t rangeLength, OMX_U32 flags, - int64_t timeUs, - void *platformPrivate, - void *dataPtr); + int64_t timeUs); void getMoreInputDataIfPossible(); @@ -366,16 +407,20 @@ ACodec::ACodec() mIsEncoder(false), mUseMetadataOnEncoderOutput(false), mShutdownInProgress(false), - mIsConfiguredForAdaptivePlayback(false), + mExplicitShutdown(false), mEncoderDelay(0), mEncoderPadding(0), + mRotationDegrees(0), mChannelMaskPresent(false), mChannelMask(0), mDequeueCounter(0), mStoreMetaDataInOutputBuffers(false), mMetaDataBuffersToSubmit(0), mRepeatFrameDelayUs(-1ll), - mMaxPtsGapUs(-1ll) { + mMaxPtsGapUs(-1ll), + mTimePerFrameUs(-1ll), + mTimePerCaptureUs(-1ll), + mCreateInputBuffersSuspended(false) { mUninitializedState = new UninitializedState(this); mLoadedState = new LoadedState(this); mLoadedToIdleState = new LoadedToIdleState(this); @@ -545,7 +590,7 @@ status_t ACodec::allocateBuffersOnPort(OMX_U32 portIndex) { } sp<AMessage> notify = mNotify->dup(); - notify->setInt32("what", ACodec::kWhatBuffersAllocated); + notify->setInt32("what", CodecBase::kWhatBuffersAllocated); notify->setInt32("portIndex", portIndex); @@ -589,6 +634,27 @@ status_t ACodec::configureOutputBuffersFromNativeWindow( return err; } + if (mRotationDegrees != 0) { + uint32_t transform = 0; + switch (mRotationDegrees) { + case 0: transform = 0; break; + case 90: transform = HAL_TRANSFORM_ROT_90; break; + case 180: transform = HAL_TRANSFORM_ROT_180; break; + case 270: transform = HAL_TRANSFORM_ROT_270; break; + default: transform = 0; break; + } + + if (transform > 0) { + err = native_window_set_buffers_transform( + mNativeWindow.get(), transform); + if (err != 0) { + ALOGE("native_window_set_buffers_transform failed: %s (%d)", + strerror(-err), -err); + return err; + } + } + } + // Set up the native window. OMX_U32 usage = 0; err = mOMX->getGraphicBufferUsage(mNode, kPortIndexOutput, &usage); @@ -747,7 +813,10 @@ status_t ACodec::allocateOutputBuffersFromNativeWindow() { for (OMX_U32 i = cancelStart; i < cancelEnd; i++) { BufferInfo *info = &mBuffers[kPortIndexOutput].editItemAt(i); - cancelBufferToNativeWindow(info); + status_t error = cancelBufferToNativeWindow(info); + if (err == 0) { + err = error; + } } return err; @@ -822,11 +891,12 @@ status_t ACodec::cancelBufferToNativeWindow(BufferInfo *info) { int err = mNativeWindow->cancelBuffer( mNativeWindow.get(), info->mGraphicBuffer.get(), -1); - CHECK_EQ(err, 0); + ALOGW_IF(err != 0, "[%s] can not return buffer %u to native window", + mComponentName.c_str(), info->mBufferID); info->mStatus = BufferInfo::OWNED_BY_NATIVE_WINDOW; - return OK; + return err; } ACodec::BufferInfo *ACodec::dequeueBufferFromNativeWindow() { @@ -926,7 +996,7 @@ status_t ACodec::freeBuffer(OMX_U32 portIndex, size_t i) { if (portIndex == kPortIndexOutput && mNativeWindow != NULL && info->mStatus == BufferInfo::OWNED_BY_US) { - CHECK_EQ((status_t)OK, cancelBufferToNativeWindow(info)); + cancelBufferToNativeWindow(info); } CHECK_EQ(mOMX->freeBuffer( @@ -980,12 +1050,16 @@ status_t ACodec::setComponentRole( "audio_decoder.aac", "audio_encoder.aac" }, { MEDIA_MIMETYPE_AUDIO_VORBIS, "audio_decoder.vorbis", "audio_encoder.vorbis" }, + { MEDIA_MIMETYPE_AUDIO_OPUS, + "audio_decoder.opus", "audio_encoder.opus" }, { MEDIA_MIMETYPE_AUDIO_G711_MLAW, "audio_decoder.g711mlaw", "audio_encoder.g711mlaw" }, { MEDIA_MIMETYPE_AUDIO_G711_ALAW, "audio_decoder.g711alaw", "audio_encoder.g711alaw" }, { MEDIA_MIMETYPE_VIDEO_AVC, "video_decoder.avc", "video_encoder.avc" }, + { MEDIA_MIMETYPE_VIDEO_HEVC, + "video_decoder.hevc", "video_encoder.hevc" }, { MEDIA_MIMETYPE_VIDEO_MPEG4, "video_decoder.mpeg4", "video_encoder.mpeg4" }, { MEDIA_MIMETYPE_VIDEO_H263, @@ -1000,6 +1074,10 @@ status_t ACodec::setComponentRole( "audio_decoder.flac", "audio_encoder.flac" }, { MEDIA_MIMETYPE_AUDIO_MSGSM, "audio_decoder.gsm", "audio_encoder.gsm" }, + { MEDIA_MIMETYPE_VIDEO_MPEG2, + "video_decoder.mpeg2", "video_encoder.mpeg2" }, + { MEDIA_MIMETYPE_AUDIO_AC3, + "audio_decoder.ac3", "audio_encoder.ac3" }, }; static const size_t kNumMimeToRole = @@ -1051,6 +1129,9 @@ status_t ACodec::configureCodec( encoder = false; } + sp<AMessage> inputFormat = new AMessage(); + sp<AMessage> outputFormat = new AMessage(); + mIsEncoder = encoder; status_t err = setComponentRole(encoder /* isEncoder */, mime); @@ -1133,74 +1214,120 @@ status_t ACodec::configureCodec( } if (!msg->findInt64("max-pts-gap-to-encoder", &mMaxPtsGapUs)) { - mMaxPtsGapUs = -1l; + mMaxPtsGapUs = -1ll; + } + + if (!msg->findInt64("time-lapse", &mTimePerCaptureUs)) { + mTimePerCaptureUs = -1ll; + } + + if (!msg->findInt32( + "create-input-buffers-suspended", + (int32_t*)&mCreateInputBuffersSuspended)) { + mCreateInputBuffersSuspended = false; } } - // Always try to enable dynamic output buffers on native surface sp<RefBase> obj; int32_t haveNativeWindow = msg->findObject("native-window", &obj) && - obj != NULL; + obj != NULL; mStoreMetaDataInOutputBuffers = false; - mIsConfiguredForAdaptivePlayback = false; + if (video && !encoder) { + inputFormat->setInt32("adaptive-playback", false); + } if (!encoder && video && haveNativeWindow) { - err = mOMX->storeMetaDataInBuffers(mNode, kPortIndexOutput, OMX_TRUE); - if (err != OK) { - ALOGE("[%s] storeMetaDataInBuffers failed w/ err %d", - mComponentName.c_str(), err); - - // if adaptive playback has been requested, try JB fallback - // NOTE: THIS FALLBACK MECHANISM WILL BE REMOVED DUE TO ITS - // LARGE MEMORY REQUIREMENT - - // we will not do adaptive playback on software accessed - // surfaces as they never had to respond to changes in the - // crop window, and we don't trust that they will be able to. - int usageBits = 0; - bool canDoAdaptivePlayback; - - sp<NativeWindowWrapper> windowWrapper( - static_cast<NativeWindowWrapper *>(obj.get())); - sp<ANativeWindow> nativeWindow = windowWrapper->getNativeWindow(); - - if (nativeWindow->query( - nativeWindow.get(), - NATIVE_WINDOW_CONSUMER_USAGE_BITS, - &usageBits) != OK) { - canDoAdaptivePlayback = false; - } else { - canDoAdaptivePlayback = - (usageBits & - (GRALLOC_USAGE_SW_READ_MASK | - GRALLOC_USAGE_SW_WRITE_MASK)) == 0; + sp<NativeWindowWrapper> windowWrapper( + static_cast<NativeWindowWrapper *>(obj.get())); + sp<ANativeWindow> nativeWindow = windowWrapper->getNativeWindow(); + + int32_t tunneled; + if (msg->findInt32("feature-tunneled-playback", &tunneled) && + tunneled != 0) { + ALOGI("Configuring TUNNELED video playback."); + + int32_t audioHwSync = 0; + if (!msg->findInt32("audio-hw-sync", &audioHwSync)) { + ALOGW("No Audio HW Sync provided for video tunnel"); + } + err = configureTunneledVideoPlayback(audioHwSync, nativeWindow); + if (err != OK) { + ALOGE("configureTunneledVideoPlayback(%d,%p) failed!", + audioHwSync, nativeWindow.get()); + return err; } - int32_t maxWidth = 0, maxHeight = 0; - if (canDoAdaptivePlayback && - msg->findInt32("max-width", &maxWidth) && - msg->findInt32("max-height", &maxHeight)) { - ALOGV("[%s] prepareForAdaptivePlayback(%dx%d)", - mComponentName.c_str(), maxWidth, maxHeight); - - err = mOMX->prepareForAdaptivePlayback( - mNode, kPortIndexOutput, OMX_TRUE, maxWidth, maxHeight); - ALOGW_IF(err != OK, - "[%s] prepareForAdaptivePlayback failed w/ err %d", + inputFormat->setInt32("adaptive-playback", true); + } else { + // Always try to enable dynamic output buffers on native surface + err = mOMX->storeMetaDataInBuffers( + mNode, kPortIndexOutput, OMX_TRUE); + if (err != OK) { + ALOGE("[%s] storeMetaDataInBuffers failed w/ err %d", mComponentName.c_str(), err); - mIsConfiguredForAdaptivePlayback = (err == OK); + + // if adaptive playback has been requested, try JB fallback + // NOTE: THIS FALLBACK MECHANISM WILL BE REMOVED DUE TO ITS + // LARGE MEMORY REQUIREMENT + + // we will not do adaptive playback on software accessed + // surfaces as they never had to respond to changes in the + // crop window, and we don't trust that they will be able to. + int usageBits = 0; + bool canDoAdaptivePlayback; + + if (nativeWindow->query( + nativeWindow.get(), + NATIVE_WINDOW_CONSUMER_USAGE_BITS, + &usageBits) != OK) { + canDoAdaptivePlayback = false; + } else { + canDoAdaptivePlayback = + (usageBits & + (GRALLOC_USAGE_SW_READ_MASK | + GRALLOC_USAGE_SW_WRITE_MASK)) == 0; + } + + int32_t maxWidth = 0, maxHeight = 0; + if (canDoAdaptivePlayback && + msg->findInt32("max-width", &maxWidth) && + msg->findInt32("max-height", &maxHeight)) { + ALOGV("[%s] prepareForAdaptivePlayback(%dx%d)", + mComponentName.c_str(), maxWidth, maxHeight); + + err = mOMX->prepareForAdaptivePlayback( + mNode, kPortIndexOutput, OMX_TRUE, maxWidth, + maxHeight); + ALOGW_IF(err != OK, + "[%s] prepareForAdaptivePlayback failed w/ err %d", + mComponentName.c_str(), err); + + if (err == OK) { + inputFormat->setInt32("max-width", maxWidth); + inputFormat->setInt32("max-height", maxHeight); + inputFormat->setInt32("adaptive-playback", true); + } + } + // allow failure + err = OK; + } else { + ALOGV("[%s] storeMetaDataInBuffers succeeded", + mComponentName.c_str()); + mStoreMetaDataInOutputBuffers = true; + inputFormat->setInt32("adaptive-playback", true); + } + + int32_t push; + if (msg->findInt32("push-blank-buffers-on-shutdown", &push) + && push != 0) { + mFlags |= kFlagPushBlankBuffersToNativeWindowOnShutdown; } - // allow failure - err = OK; - } else { - ALOGV("[%s] storeMetaDataInBuffers succeeded", mComponentName.c_str()); - mStoreMetaDataInOutputBuffers = true; - mIsConfiguredForAdaptivePlayback = true; } - int32_t push; - if (msg->findInt32("push-blank-buffers-on-shutdown", &push) - && push != 0) { - mFlags |= kFlagPushBlankBuffersToNativeWindowOnShutdown; + int32_t rotationDegrees; + if (msg->findInt32("rotation-degrees", &rotationDegrees)) { + mRotationDegrees = rotationDegrees; + } else { + mRotationDegrees = 0; } } @@ -1208,13 +1335,7 @@ status_t ACodec::configureCodec( if (encoder) { err = setupVideoEncoder(mime, msg); } else { - int32_t width, height; - if (!msg->findInt32("width", &width) - || !msg->findInt32("height", &height)) { - err = INVALID_OPERATION; - } else { - err = setupVideoDecoder(mime, width, height); - } + err = setupVideoDecoder(mime, msg); } } else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_MPEG)) { int32_t numChannels, sampleRate; @@ -1236,16 +1357,46 @@ status_t ACodec::configureCodec( err = INVALID_OPERATION; } else { int32_t isADTS, aacProfile; + int32_t sbrMode; + int32_t maxOutputChannelCount; + drcParams_t drc; if (!msg->findInt32("is-adts", &isADTS)) { isADTS = 0; } if (!msg->findInt32("aac-profile", &aacProfile)) { aacProfile = OMX_AUDIO_AACObjectNull; } + if (!msg->findInt32("aac-sbr-mode", &sbrMode)) { + sbrMode = -1; + } + + if (!msg->findInt32("aac-max-output-channel_count", &maxOutputChannelCount)) { + maxOutputChannelCount = -1; + } + if (!msg->findInt32("aac-encoded-target-level", &drc.encodedTargetLevel)) { + // value is unknown + drc.encodedTargetLevel = -1; + } + if (!msg->findInt32("aac-drc-cut-level", &drc.drcCut)) { + // value is unknown + drc.drcCut = -1; + } + if (!msg->findInt32("aac-drc-boost-level", &drc.drcBoost)) { + // value is unknown + drc.drcBoost = -1; + } + if (!msg->findInt32("aac-drc-heavy-compression", &drc.heavyCompression)) { + // value is unknown + drc.heavyCompression = -1; + } + if (!msg->findInt32("aac-target-ref-level", &drc.targetRefLevel)) { + // value is unknown + drc.targetRefLevel = -1; + } err = setupAACCodec( encoder, numChannels, sampleRate, bitRate, aacProfile, - isADTS != 0); + isADTS != 0, sbrMode, maxOutputChannelCount, drc); } } else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AMR_NB)) { err = setupAMRCodec(encoder, false /* isWAMR */, bitRate); @@ -1272,8 +1423,10 @@ status_t ACodec::configureCodec( } else { if (encoder) { if (!msg->findInt32( + "complexity", &compressionLevel) && + !msg->findInt32( "flac-compression-level", &compressionLevel)) { - compressionLevel = 5;// default FLAC compression level + compressionLevel = 5; // default FLAC compression level } else if (compressionLevel < 0) { ALOGW("compression level %d outside [0..8] range, " "using 0", @@ -1298,6 +1451,15 @@ status_t ACodec::configureCodec( } else { err = setupRawAudioFormat(kPortIndexInput, sampleRate, numChannels); } + } else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AC3)) { + int32_t numChannels; + int32_t sampleRate; + if (!msg->findInt32("channel-count", &numChannels) + || !msg->findInt32("sample-rate", &sampleRate)) { + err = INVALID_OPERATION; + } else { + err = setupAC3Codec(encoder, numChannels, sampleRate); + } } if (err != OK) { @@ -1325,6 +1487,11 @@ status_t ACodec::configureCodec( err = setMinBufferSize(kPortIndexInput, 8192); // XXX } + CHECK_EQ(getPortFormat(kPortIndexInput, inputFormat), (status_t)OK); + CHECK_EQ(getPortFormat(kPortIndexOutput, outputFormat), (status_t)OK); + mInputFormat = inputFormat; + mOutputFormat = outputFormat; + return err; } @@ -1393,7 +1560,8 @@ status_t ACodec::selectAudioPortFormat( status_t ACodec::setupAACCodec( bool encoder, int32_t numChannels, int32_t sampleRate, - int32_t bitRate, int32_t aacProfile, bool isADTS) { + int32_t bitRate, int32_t aacProfile, bool isADTS, int32_t sbrMode, + int32_t maxOutputChannelCount, const drcParams_t& drc) { if (encoder && isADTS) { return -EINVAL; } @@ -1460,6 +1628,32 @@ status_t ACodec::setupAACCodec( profile.nAACERtools = OMX_AUDIO_AACERNone; profile.eAACProfile = (OMX_AUDIO_AACPROFILETYPE) aacProfile; profile.eAACStreamFormat = OMX_AUDIO_AACStreamFormatMP4FF; + switch (sbrMode) { + case 0: + // disable sbr + profile.nAACtools &= ~OMX_AUDIO_AACToolAndroidSSBR; + profile.nAACtools &= ~OMX_AUDIO_AACToolAndroidDSBR; + break; + case 1: + // enable single-rate sbr + profile.nAACtools |= OMX_AUDIO_AACToolAndroidSSBR; + profile.nAACtools &= ~OMX_AUDIO_AACToolAndroidDSBR; + break; + case 2: + // enable dual-rate sbr + profile.nAACtools &= ~OMX_AUDIO_AACToolAndroidSSBR; + profile.nAACtools |= OMX_AUDIO_AACToolAndroidDSBR; + break; + case -1: + // enable both modes -> the codec will decide which mode should be used + profile.nAACtools |= OMX_AUDIO_AACToolAndroidSSBR; + profile.nAACtools |= OMX_AUDIO_AACToolAndroidDSBR; + break; + default: + // unsupported sbr mode + return BAD_VALUE; + } + err = mOMX->setParameter( mNode, OMX_IndexParamAudioAac, &profile, sizeof(profile)); @@ -1490,8 +1684,61 @@ status_t ACodec::setupAACCodec( ? OMX_AUDIO_AACStreamFormatMP4ADTS : OMX_AUDIO_AACStreamFormatMP4FF; + OMX_AUDIO_PARAM_ANDROID_AACPRESENTATIONTYPE presentation; + presentation.nMaxOutputChannels = maxOutputChannelCount; + presentation.nDrcCut = drc.drcCut; + presentation.nDrcBoost = drc.drcBoost; + presentation.nHeavyCompression = drc.heavyCompression; + presentation.nTargetReferenceLevel = drc.targetRefLevel; + presentation.nEncodedTargetLevel = drc.encodedTargetLevel; + + status_t res = mOMX->setParameter(mNode, OMX_IndexParamAudioAac, &profile, sizeof(profile)); + if (res == OK) { + // optional parameters, will not cause configuration failure + mOMX->setParameter(mNode, (OMX_INDEXTYPE)OMX_IndexParamAudioAndroidAacPresentation, + &presentation, sizeof(presentation)); + } else { + ALOGW("did not set AudioAndroidAacPresentation due to error %d when setting AudioAac", res); + } + return res; +} + +status_t ACodec::setupAC3Codec( + bool encoder, int32_t numChannels, int32_t sampleRate) { + status_t err = setupRawAudioFormat( + encoder ? kPortIndexInput : kPortIndexOutput, sampleRate, numChannels); + + if (err != OK) { + return err; + } + + if (encoder) { + ALOGW("AC3 encoding is not supported."); + return INVALID_OPERATION; + } + + OMX_AUDIO_PARAM_ANDROID_AC3TYPE def; + InitOMXParams(&def); + def.nPortIndex = kPortIndexInput; + + err = mOMX->getParameter( + mNode, + (OMX_INDEXTYPE)OMX_IndexParamAudioAndroidAc3, + &def, + sizeof(def)); + + if (err != OK) { + return err; + } + + def.nChannels = numChannels; + def.nSampleRate = sampleRate; + return mOMX->setParameter( - mNode, OMX_IndexParamAudioAac, &profile, sizeof(profile)); + mNode, + (OMX_INDEXTYPE)OMX_IndexParamAudioAndroidAc3, + &def, + sizeof(def)); } static OMX_AUDIO_AMRBANDMODETYPE pickModeFromBitRate( @@ -1650,6 +1897,27 @@ status_t ACodec::setupRawAudioFormat( mNode, OMX_IndexParamAudioPcm, &pcmParams, sizeof(pcmParams)); } +status_t ACodec::configureTunneledVideoPlayback( + int32_t audioHwSync, const sp<ANativeWindow> &nativeWindow) { + native_handle_t* sidebandHandle; + + status_t err = mOMX->configureVideoTunnelMode( + mNode, kPortIndexOutput, OMX_TRUE, audioHwSync, &sidebandHandle); + if (err != OK) { + ALOGE("configureVideoTunnelMode failed! (err %d).", err); + return err; + } + + err = native_window_set_sideband_stream(nativeWindow.get(), sidebandHandle); + if (err != OK) { + ALOGE("native_window_set_sideband_stream(%p) failed! (err %d).", + sidebandHandle, err); + return err; + } + + return OK; +} + status_t ACodec::setVideoPortFormatType( OMX_U32 portIndex, OMX_VIDEO_CODINGTYPE compressionFormat, @@ -1671,6 +1939,17 @@ status_t ACodec::setVideoPortFormatType( return err; } + // substitute back flexible color format to codec supported format + OMX_U32 flexibleEquivalent; + if (compressionFormat == OMX_VIDEO_CodingUnused && + isFlexibleColorFormat( + mOMX, mNode, format.eColorFormat, &flexibleEquivalent) && + colorFormat == flexibleEquivalent) { + ALOGI("[%s] using color format %#x in place of %#x", + mComponentName.c_str(), format.eColorFormat, colorFormat); + colorFormat = format.eColorFormat; + } + // The following assertion is violated by TI's video decoder. // CHECK_EQ(format.nIndex, index); @@ -1731,6 +2010,7 @@ static const struct VideoCodingMapEntry { OMX_VIDEO_CODINGTYPE mVideoCodingType; } kVideoCodingMapEntry[] = { { MEDIA_MIMETYPE_VIDEO_AVC, OMX_VIDEO_CodingAVC }, + { MEDIA_MIMETYPE_VIDEO_HEVC, OMX_VIDEO_CodingHEVC }, { MEDIA_MIMETYPE_VIDEO_MPEG4, OMX_VIDEO_CodingMPEG4 }, { MEDIA_MIMETYPE_VIDEO_H263, OMX_VIDEO_CodingH263 }, { MEDIA_MIMETYPE_VIDEO_MPEG2, OMX_VIDEO_CodingMPEG2 }, @@ -1771,7 +2051,13 @@ static status_t GetMimeTypeForVideoCoding( } status_t ACodec::setupVideoDecoder( - const char *mime, int32_t width, int32_t height) { + const char *mime, const sp<AMessage> &msg) { + int32_t width, height; + if (!msg->findInt32("width", &width) + || !msg->findInt32("height", &height)) { + return INVALID_OPERATION; + } + OMX_VIDEO_CODINGTYPE compressionFormat; status_t err = GetVideoCodingTypeFromMime(mime, &compressionFormat); @@ -1786,7 +2072,20 @@ status_t ACodec::setupVideoDecoder( return err; } - err = setSupportedOutputFormat(); + int32_t tmp; + if (msg->findInt32("color-format", &tmp)) { + OMX_COLOR_FORMATTYPE colorFormat = + static_cast<OMX_COLOR_FORMATTYPE>(tmp); + err = setVideoPortFormatType( + kPortIndexOutput, OMX_VIDEO_CodingUnused, colorFormat); + if (err != OK) { + ALOGW("[%s] does not support color format %d", + mComponentName.c_str(), colorFormat); + err = setSupportedOutputFormat(); + } + } else { + err = setSupportedOutputFormat(); + } if (err != OK) { return err; @@ -1877,6 +2176,7 @@ status_t ACodec::setupVideoEncoder(const char *mime, const sp<AMessage> &msg) { return INVALID_OPERATION; } frameRate = (float)tmp; + mTimePerFrameUs = (int64_t) (1000000.0f / frameRate); } video_def->xFramerate = (OMX_U32)(frameRate * 65536.0f); @@ -1951,6 +2251,10 @@ status_t ACodec::setupVideoEncoder(const char *mime, const sp<AMessage> &msg) { err = setupAVCEncoderParameters(msg); break; + case OMX_VIDEO_CodingHEVC: + err = setupHEVCEncoderParameters(msg); + break; + case OMX_VIDEO_CodingVP8: case OMX_VIDEO_CodingVP9: err = setupVPXEncoderParameters(msg); @@ -2009,7 +2313,6 @@ static OMX_U32 setPFramesSpacing(int32_t iFramesInterval, int32_t frameRate) { return 0; } OMX_U32 ret = frameRate * iFramesInterval; - CHECK(ret > 1); return ret; } @@ -2287,14 +2590,139 @@ status_t ACodec::setupAVCEncoderParameters(const sp<AMessage> &msg) { return configureBitrate(bitrate, bitrateMode); } +status_t ACodec::setupHEVCEncoderParameters(const sp<AMessage> &msg) { + int32_t bitrate, iFrameInterval; + if (!msg->findInt32("bitrate", &bitrate) + || !msg->findInt32("i-frame-interval", &iFrameInterval)) { + return INVALID_OPERATION; + } + + OMX_VIDEO_CONTROLRATETYPE bitrateMode = getBitrateMode(msg); + + float frameRate; + if (!msg->findFloat("frame-rate", &frameRate)) { + int32_t tmp; + if (!msg->findInt32("frame-rate", &tmp)) { + return INVALID_OPERATION; + } + frameRate = (float)tmp; + } + + OMX_VIDEO_PARAM_HEVCTYPE hevcType; + InitOMXParams(&hevcType); + hevcType.nPortIndex = kPortIndexOutput; + + status_t err = OK; + err = mOMX->getParameter( + mNode, (OMX_INDEXTYPE)OMX_IndexParamVideoHevc, &hevcType, sizeof(hevcType)); + if (err != OK) { + return err; + } + + int32_t profile; + if (msg->findInt32("profile", &profile)) { + int32_t level; + if (!msg->findInt32("level", &level)) { + return INVALID_OPERATION; + } + + err = verifySupportForProfileAndLevel(profile, level); + if (err != OK) { + return err; + } + + hevcType.eProfile = static_cast<OMX_VIDEO_HEVCPROFILETYPE>(profile); + hevcType.eLevel = static_cast<OMX_VIDEO_HEVCLEVELTYPE>(level); + } + + // TODO: Need OMX structure definition for setting iFrameInterval + + err = mOMX->setParameter( + mNode, (OMX_INDEXTYPE)OMX_IndexParamVideoHevc, &hevcType, sizeof(hevcType)); + if (err != OK) { + return err; + } + + return configureBitrate(bitrate, bitrateMode); +} + status_t ACodec::setupVPXEncoderParameters(const sp<AMessage> &msg) { int32_t bitrate; + int32_t iFrameInterval = 0; + size_t tsLayers = 0; + OMX_VIDEO_ANDROID_VPXTEMPORALLAYERPATTERNTYPE pattern = + OMX_VIDEO_VPXTemporalLayerPatternNone; + static const uint32_t kVp8LayerRateAlloction + [OMX_VIDEO_ANDROID_MAXVP8TEMPORALLAYERS] + [OMX_VIDEO_ANDROID_MAXVP8TEMPORALLAYERS] = { + {100, 100, 100}, // 1 layer + { 60, 100, 100}, // 2 layers {60%, 40%} + { 40, 60, 100}, // 3 layers {40%, 20%, 40%} + }; if (!msg->findInt32("bitrate", &bitrate)) { return INVALID_OPERATION; } + msg->findInt32("i-frame-interval", &iFrameInterval); OMX_VIDEO_CONTROLRATETYPE bitrateMode = getBitrateMode(msg); + float frameRate; + if (!msg->findFloat("frame-rate", &frameRate)) { + int32_t tmp; + if (!msg->findInt32("frame-rate", &tmp)) { + return INVALID_OPERATION; + } + frameRate = (float)tmp; + } + + AString tsSchema; + if (msg->findString("ts-schema", &tsSchema)) { + if (tsSchema == "webrtc.vp8.1-layer") { + pattern = OMX_VIDEO_VPXTemporalLayerPatternWebRTC; + tsLayers = 1; + } else if (tsSchema == "webrtc.vp8.2-layer") { + pattern = OMX_VIDEO_VPXTemporalLayerPatternWebRTC; + tsLayers = 2; + } else if (tsSchema == "webrtc.vp8.3-layer") { + pattern = OMX_VIDEO_VPXTemporalLayerPatternWebRTC; + tsLayers = 3; + } else { + ALOGW("Unsupported ts-schema [%s]", tsSchema.c_str()); + } + } + + OMX_VIDEO_PARAM_ANDROID_VP8ENCODERTYPE vp8type; + InitOMXParams(&vp8type); + vp8type.nPortIndex = kPortIndexOutput; + status_t err = mOMX->getParameter( + mNode, (OMX_INDEXTYPE)OMX_IndexParamVideoAndroidVp8Encoder, + &vp8type, sizeof(vp8type)); + + if (err == OK) { + if (iFrameInterval > 0) { + vp8type.nKeyFrameInterval = setPFramesSpacing(iFrameInterval, frameRate); + } + vp8type.eTemporalPattern = pattern; + vp8type.nTemporalLayerCount = tsLayers; + if (tsLayers > 0) { + for (size_t i = 0; i < OMX_VIDEO_ANDROID_MAXVP8TEMPORALLAYERS; i++) { + vp8type.nTemporalLayerBitrateRatio[i] = + kVp8LayerRateAlloction[tsLayers - 1][i]; + } + } + if (bitrateMode == OMX_Video_ControlRateConstant) { + vp8type.nMinQuantizer = 2; + vp8type.nMaxQuantizer = 63; + } + + err = mOMX->setParameter( + mNode, (OMX_INDEXTYPE)OMX_IndexParamVideoAndroidVp8Encoder, + &vp8type, sizeof(vp8type)); + if (err != OK) { + ALOGW("Extended VP8 parameters set failed: %d", err); + } + } + return configureBitrate(bitrate, bitrateMode); } @@ -2496,79 +2924,276 @@ void ACodec::processDeferredMessages() { } } -void ACodec::sendFormatChange(const sp<AMessage> &reply) { - sp<AMessage> notify = mNotify->dup(); - notify->setInt32("what", kWhatOutputFormatChanged); +// static +bool ACodec::describeDefaultColorFormat(DescribeColorFormatParams ¶ms) { + MediaImage &image = params.sMediaImage; + memset(&image, 0, sizeof(image)); + + image.mType = MediaImage::MEDIA_IMAGE_TYPE_UNKNOWN; + image.mNumPlanes = 0; + + const OMX_COLOR_FORMATTYPE fmt = params.eColorFormat; + // we need stride and slice-height to be non-zero + if (params.nStride == 0 || params.nSliceHeight == 0) { + ALOGW("cannot describe color format 0x%x = %d with stride=%u and sliceHeight=%u", + fmt, fmt, params.nStride, params.nSliceHeight); + return false; + } + + image.mWidth = params.nFrameWidth; + image.mHeight = params.nFrameHeight; + + // only supporting YUV420 + if (fmt != OMX_COLOR_FormatYUV420Planar && + fmt != OMX_COLOR_FormatYUV420PackedPlanar && + fmt != OMX_COLOR_FormatYUV420SemiPlanar && + fmt != OMX_COLOR_FormatYUV420PackedSemiPlanar) { + ALOGW("do not know color format 0x%x = %d", fmt, fmt); + return false; + } + // set-up YUV format + image.mType = MediaImage::MEDIA_IMAGE_TYPE_YUV; + image.mNumPlanes = 3; + image.mBitDepth = 8; + image.mPlane[image.Y].mOffset = 0; + image.mPlane[image.Y].mColInc = 1; + image.mPlane[image.Y].mRowInc = params.nStride; + image.mPlane[image.Y].mHorizSubsampling = 1; + image.mPlane[image.Y].mVertSubsampling = 1; + + switch (fmt) { + case OMX_COLOR_FormatYUV420Planar: // used for YV12 + case OMX_COLOR_FormatYUV420PackedPlanar: + image.mPlane[image.U].mOffset = params.nStride * params.nSliceHeight; + image.mPlane[image.U].mColInc = 1; + image.mPlane[image.U].mRowInc = params.nStride / 2; + image.mPlane[image.U].mHorizSubsampling = 2; + image.mPlane[image.U].mVertSubsampling = 2; + + image.mPlane[image.V].mOffset = image.mPlane[image.U].mOffset + + (params.nStride * params.nSliceHeight / 4); + image.mPlane[image.V].mColInc = 1; + image.mPlane[image.V].mRowInc = params.nStride / 2; + image.mPlane[image.V].mHorizSubsampling = 2; + image.mPlane[image.V].mVertSubsampling = 2; + break; + + case OMX_COLOR_FormatYUV420SemiPlanar: + // FIXME: NV21 for sw-encoder, NV12 for decoder and hw-encoder + case OMX_COLOR_FormatYUV420PackedSemiPlanar: + // NV12 + image.mPlane[image.U].mOffset = params.nStride * params.nSliceHeight; + image.mPlane[image.U].mColInc = 2; + image.mPlane[image.U].mRowInc = params.nStride; + image.mPlane[image.U].mHorizSubsampling = 2; + image.mPlane[image.U].mVertSubsampling = 2; + + image.mPlane[image.V].mOffset = image.mPlane[image.U].mOffset + 1; + image.mPlane[image.V].mColInc = 2; + image.mPlane[image.V].mRowInc = params.nStride; + image.mPlane[image.V].mHorizSubsampling = 2; + image.mPlane[image.V].mVertSubsampling = 2; + break; + + default: + TRESPASS(); + } + return true; +} + +// static +bool ACodec::describeColorFormat( + const sp<IOMX> &omx, IOMX::node_id node, + DescribeColorFormatParams &describeParams) +{ + OMX_INDEXTYPE describeColorFormatIndex; + if (omx->getExtensionIndex( + node, "OMX.google.android.index.describeColorFormat", + &describeColorFormatIndex) != OK || + omx->getParameter( + node, describeColorFormatIndex, + &describeParams, sizeof(describeParams)) != OK) { + return describeDefaultColorFormat(describeParams); + } + return describeParams.sMediaImage.mType != + MediaImage::MEDIA_IMAGE_TYPE_UNKNOWN; +} + +// static +bool ACodec::isFlexibleColorFormat( + const sp<IOMX> &omx, IOMX::node_id node, + uint32_t colorFormat, OMX_U32 *flexibleEquivalent) { + DescribeColorFormatParams describeParams; + InitOMXParams(&describeParams); + describeParams.eColorFormat = (OMX_COLOR_FORMATTYPE)colorFormat; + // reasonable dummy values + describeParams.nFrameWidth = 128; + describeParams.nFrameHeight = 128; + describeParams.nStride = 128; + describeParams.nSliceHeight = 128; + + CHECK(flexibleEquivalent != NULL); + + if (!describeColorFormat(omx, node, describeParams)) { + return false; + } + + const MediaImage &img = describeParams.sMediaImage; + if (img.mType == MediaImage::MEDIA_IMAGE_TYPE_YUV) { + if (img.mNumPlanes != 3 || + img.mPlane[img.Y].mHorizSubsampling != 1 || + img.mPlane[img.Y].mVertSubsampling != 1) { + return false; + } + + // YUV 420 + if (img.mPlane[img.U].mHorizSubsampling == 2 + && img.mPlane[img.U].mVertSubsampling == 2 + && img.mPlane[img.V].mHorizSubsampling == 2 + && img.mPlane[img.V].mVertSubsampling == 2) { + // possible flexible YUV420 format + if (img.mBitDepth <= 8) { + *flexibleEquivalent = OMX_COLOR_FormatYUV420Flexible; + return true; + } + } + } + return false; +} + +status_t ACodec::getPortFormat(OMX_U32 portIndex, sp<AMessage> ¬ify) { + // TODO: catch errors an return them instead of using CHECK OMX_PARAM_PORTDEFINITIONTYPE def; InitOMXParams(&def); - def.nPortIndex = kPortIndexOutput; + def.nPortIndex = portIndex; CHECK_EQ(mOMX->getParameter( mNode, OMX_IndexParamPortDefinition, &def, sizeof(def)), (status_t)OK); - CHECK_EQ((int)def.eDir, (int)OMX_DirOutput); + CHECK_EQ((int)def.eDir, + (int)(portIndex == kPortIndexOutput ? OMX_DirOutput : OMX_DirInput)); switch (def.eDomain) { case OMX_PortDomainVideo: { OMX_VIDEO_PORTDEFINITIONTYPE *videoDef = &def.format.video; + switch ((int)videoDef->eCompressionFormat) { + case OMX_VIDEO_CodingUnused: + { + CHECK(mIsEncoder ^ (portIndex == kPortIndexOutput)); + notify->setString("mime", MEDIA_MIMETYPE_VIDEO_RAW); + + notify->setInt32("stride", videoDef->nStride); + notify->setInt32("slice-height", videoDef->nSliceHeight); + notify->setInt32("color-format", videoDef->eColorFormat); + + DescribeColorFormatParams describeParams; + InitOMXParams(&describeParams); + describeParams.eColorFormat = videoDef->eColorFormat; + describeParams.nFrameWidth = videoDef->nFrameWidth; + describeParams.nFrameHeight = videoDef->nFrameHeight; + describeParams.nStride = videoDef->nStride; + describeParams.nSliceHeight = videoDef->nSliceHeight; + + if (describeColorFormat(mOMX, mNode, describeParams)) { + notify->setBuffer( + "image-data", + ABuffer::CreateAsCopy( + &describeParams.sMediaImage, + sizeof(describeParams.sMediaImage))); + } - AString mime; - if (!mIsEncoder) { - notify->setString("mime", MEDIA_MIMETYPE_VIDEO_RAW); - } else if (GetMimeTypeForVideoCoding( - videoDef->eCompressionFormat, &mime) != OK) { - notify->setString("mime", "application/octet-stream"); - } else { - notify->setString("mime", mime.c_str()); - } - - notify->setInt32("width", videoDef->nFrameWidth); - notify->setInt32("height", videoDef->nFrameHeight); + OMX_CONFIG_RECTTYPE rect; + InitOMXParams(&rect); + rect.nPortIndex = kPortIndexOutput; + + if (mOMX->getConfig( + mNode, OMX_IndexConfigCommonOutputCrop, + &rect, sizeof(rect)) != OK) { + rect.nLeft = 0; + rect.nTop = 0; + rect.nWidth = videoDef->nFrameWidth; + rect.nHeight = videoDef->nFrameHeight; + } - if (!mIsEncoder) { - notify->setInt32("stride", videoDef->nStride); - notify->setInt32("slice-height", videoDef->nSliceHeight); - notify->setInt32("color-format", videoDef->eColorFormat); - - OMX_CONFIG_RECTTYPE rect; - InitOMXParams(&rect); - rect.nPortIndex = kPortIndexOutput; - - if (mOMX->getConfig( - mNode, OMX_IndexConfigCommonOutputCrop, - &rect, sizeof(rect)) != OK) { - rect.nLeft = 0; - rect.nTop = 0; - rect.nWidth = videoDef->nFrameWidth; - rect.nHeight = videoDef->nFrameHeight; - } + CHECK_GE(rect.nLeft, 0); + CHECK_GE(rect.nTop, 0); + CHECK_GE(rect.nWidth, 0u); + CHECK_GE(rect.nHeight, 0u); + CHECK_LE(rect.nLeft + rect.nWidth - 1, videoDef->nFrameWidth); + CHECK_LE(rect.nTop + rect.nHeight - 1, videoDef->nFrameHeight); - CHECK_GE(rect.nLeft, 0); - CHECK_GE(rect.nTop, 0); - CHECK_GE(rect.nWidth, 0u); - CHECK_GE(rect.nHeight, 0u); - CHECK_LE(rect.nLeft + rect.nWidth - 1, videoDef->nFrameWidth); - CHECK_LE(rect.nTop + rect.nHeight - 1, videoDef->nFrameHeight); - - notify->setRect( - "crop", - rect.nLeft, - rect.nTop, - rect.nLeft + rect.nWidth - 1, - rect.nTop + rect.nHeight - 1); - - if (mNativeWindow != NULL) { - reply->setRect( + notify->setRect( "crop", rect.nLeft, rect.nTop, - rect.nLeft + rect.nWidth, - rect.nTop + rect.nHeight); + rect.nLeft + rect.nWidth - 1, + rect.nTop + rect.nHeight - 1); + + break; + } + + case OMX_VIDEO_CodingVP8: + case OMX_VIDEO_CodingVP9: + { + OMX_VIDEO_PARAM_ANDROID_VP8ENCODERTYPE vp8type; + InitOMXParams(&vp8type); + vp8type.nPortIndex = kPortIndexOutput; + status_t err = mOMX->getParameter( + mNode, + (OMX_INDEXTYPE)OMX_IndexParamVideoAndroidVp8Encoder, + &vp8type, + sizeof(vp8type)); + + if (err == OK) { + AString tsSchema = "none"; + if (vp8type.eTemporalPattern + == OMX_VIDEO_VPXTemporalLayerPatternWebRTC) { + switch (vp8type.nTemporalLayerCount) { + case 1: + { + tsSchema = "webrtc.vp8.1-layer"; + break; + } + case 2: + { + tsSchema = "webrtc.vp8.2-layer"; + break; + } + case 3: + { + tsSchema = "webrtc.vp8.3-layer"; + break; + } + default: + { + break; + } + } + } + notify->setString("ts-schema", tsSchema); + } + // Fall through to set up mime. + } + + default: + { + CHECK(mIsEncoder ^ (portIndex == kPortIndexInput)); + AString mime; + if (GetMimeTypeForVideoCoding( + videoDef->eCompressionFormat, &mime) != OK) { + notify->setString("mime", "application/octet-stream"); + } else { + notify->setString("mime", mime.c_str()); + } + break; } } + + notify->setInt32("width", videoDef->nFrameWidth); + notify->setInt32("height", videoDef->nFrameHeight); break; } @@ -2576,12 +3201,12 @@ void ACodec::sendFormatChange(const sp<AMessage> &reply) { { OMX_AUDIO_PORTDEFINITIONTYPE *audioDef = &def.format.audio; - switch (audioDef->eEncoding) { + switch ((int)audioDef->eEncoding) { case OMX_AUDIO_CodingPCM: { OMX_AUDIO_PARAM_PCMMODETYPE params; InitOMXParams(¶ms); - params.nPortIndex = kPortIndexOutput; + params.nPortIndex = portIndex; CHECK_EQ(mOMX->getParameter( mNode, OMX_IndexParamAudioPcm, @@ -2601,20 +3226,6 @@ void ACodec::sendFormatChange(const sp<AMessage> &reply) { notify->setString("mime", MEDIA_MIMETYPE_AUDIO_RAW); notify->setInt32("channel-count", params.nChannels); notify->setInt32("sample-rate", params.nSamplingRate); - if (mEncoderDelay + mEncoderPadding) { - size_t frameSize = params.nChannels * sizeof(int16_t); - if (mSkipCutBuffer != NULL) { - size_t prevbufsize = mSkipCutBuffer->size(); - if (prevbufsize != 0) { - ALOGW("Replacing SkipCutBuffer holding %d " - "bytes", - prevbufsize); - } - } - mSkipCutBuffer = new SkipCutBuffer( - mEncoderDelay * frameSize, - mEncoderPadding * frameSize); - } if (mChannelMaskPresent) { notify->setInt32("channel-mask", mChannelMask); @@ -2626,7 +3237,7 @@ void ACodec::sendFormatChange(const sp<AMessage> &reply) { { OMX_AUDIO_PARAM_AACPROFILETYPE params; InitOMXParams(¶ms); - params.nPortIndex = kPortIndexOutput; + params.nPortIndex = portIndex; CHECK_EQ(mOMX->getParameter( mNode, OMX_IndexParamAudioAac, @@ -2643,7 +3254,7 @@ void ACodec::sendFormatChange(const sp<AMessage> &reply) { { OMX_AUDIO_PARAM_AMRTYPE params; InitOMXParams(¶ms); - params.nPortIndex = kPortIndexOutput; + params.nPortIndex = portIndex; CHECK_EQ(mOMX->getParameter( mNode, OMX_IndexParamAudioAmr, @@ -2669,7 +3280,7 @@ void ACodec::sendFormatChange(const sp<AMessage> &reply) { { OMX_AUDIO_PARAM_FLACTYPE params; InitOMXParams(¶ms); - params.nPortIndex = kPortIndexOutput; + params.nPortIndex = portIndex; CHECK_EQ(mOMX->getParameter( mNode, OMX_IndexParamAudioFlac, @@ -2682,7 +3293,78 @@ void ACodec::sendFormatChange(const sp<AMessage> &reply) { break; } + case OMX_AUDIO_CodingMP3: + { + OMX_AUDIO_PARAM_MP3TYPE params; + InitOMXParams(¶ms); + params.nPortIndex = portIndex; + + CHECK_EQ(mOMX->getParameter( + mNode, OMX_IndexParamAudioMp3, + ¶ms, sizeof(params)), + (status_t)OK); + + notify->setString("mime", MEDIA_MIMETYPE_AUDIO_MPEG); + notify->setInt32("channel-count", params.nChannels); + notify->setInt32("sample-rate", params.nSampleRate); + break; + } + + case OMX_AUDIO_CodingVORBIS: + { + OMX_AUDIO_PARAM_VORBISTYPE params; + InitOMXParams(¶ms); + params.nPortIndex = portIndex; + + CHECK_EQ(mOMX->getParameter( + mNode, OMX_IndexParamAudioVorbis, + ¶ms, sizeof(params)), + (status_t)OK); + + notify->setString("mime", MEDIA_MIMETYPE_AUDIO_VORBIS); + notify->setInt32("channel-count", params.nChannels); + notify->setInt32("sample-rate", params.nSampleRate); + break; + } + + case OMX_AUDIO_CodingAndroidAC3: + { + OMX_AUDIO_PARAM_ANDROID_AC3TYPE params; + InitOMXParams(¶ms); + params.nPortIndex = portIndex; + + CHECK_EQ((status_t)OK, mOMX->getParameter( + mNode, + (OMX_INDEXTYPE)OMX_IndexParamAudioAndroidAc3, + ¶ms, + sizeof(params))); + + notify->setString("mime", MEDIA_MIMETYPE_AUDIO_AC3); + notify->setInt32("channel-count", params.nChannels); + notify->setInt32("sample-rate", params.nSampleRate); + break; + } + + case OMX_AUDIO_CodingAndroidOPUS: + { + OMX_AUDIO_PARAM_ANDROID_OPUSTYPE params; + InitOMXParams(¶ms); + params.nPortIndex = portIndex; + + CHECK_EQ((status_t)OK, mOMX->getParameter( + mNode, + (OMX_INDEXTYPE)OMX_IndexParamAudioAndroidOpus, + ¶ms, + sizeof(params))); + + notify->setString("mime", MEDIA_MIMETYPE_AUDIO_OPUS); + notify->setInt32("channel-count", params.nChannels); + notify->setInt32("sample-rate", params.nSampleRate); + break; + } + default: + ALOGE("UNKNOWN AUDIO CODING: %d\n", audioDef->eEncoding); TRESPASS(); } break; @@ -2692,6 +3374,43 @@ void ACodec::sendFormatChange(const sp<AMessage> &reply) { TRESPASS(); } + return OK; +} + +void ACodec::sendFormatChange(const sp<AMessage> &reply) { + sp<AMessage> notify = mNotify->dup(); + notify->setInt32("what", kWhatOutputFormatChanged); + + CHECK_EQ(getPortFormat(kPortIndexOutput, notify), (status_t)OK); + + AString mime; + CHECK(notify->findString("mime", &mime)); + + int32_t left, top, right, bottom; + if (mime == MEDIA_MIMETYPE_VIDEO_RAW && + mNativeWindow != NULL && + notify->findRect("crop", &left, &top, &right, &bottom)) { + // notify renderer of the crop change + // NOTE: native window uses extended right-bottom coordinate + reply->setRect("crop", left, top, right + 1, bottom + 1); + } else if (mime == MEDIA_MIMETYPE_AUDIO_RAW && + (mEncoderDelay || mEncoderPadding)) { + int32_t channelCount; + CHECK(notify->findInt32("channel-count", &channelCount)); + size_t frameSize = channelCount * sizeof(int16_t); + if (mSkipCutBuffer != NULL) { + size_t prevbufsize = mSkipCutBuffer->size(); + if (prevbufsize != 0) { + ALOGW("Replacing SkipCutBuffer holding %d " + "bytes", + prevbufsize); + } + } + mSkipCutBuffer = new SkipCutBuffer( + mEncoderDelay * frameSize, + mEncoderPadding * frameSize); + } + notify->post(); mSentFormat = true; @@ -2699,9 +3418,19 @@ void ACodec::sendFormatChange(const sp<AMessage> &reply) { void ACodec::signalError(OMX_ERRORTYPE error, status_t internalError) { sp<AMessage> notify = mNotify->dup(); - notify->setInt32("what", ACodec::kWhatError); - notify->setInt32("omx-error", error); + notify->setInt32("what", CodecBase::kWhatError); + ALOGE("signalError(omxError %#x, internalError %d)", error, internalError); + + if (internalError == UNKNOWN_ERROR) { // find better error code + const status_t omxStatus = statusFromOMXError(error); + if (omxStatus != 0) { + internalError = omxStatus; + } else { + ALOGW("Invalid OMX error %#x", error); + } + } notify->setInt32("err", internalError); + notify->setInt32("actionCode", ACTION_CODE_FATAL); // could translate from OMX error. notify->post(); } @@ -2898,7 +3627,8 @@ ACodec::BaseState::BaseState(ACodec *codec, const sp<AState> &parentState) mCodec(codec) { } -ACodec::BaseState::PortMode ACodec::BaseState::getPortMode(OMX_U32 portIndex) { +ACodec::BaseState::PortMode ACodec::BaseState::getPortMode( + OMX_U32 /* portIndex */) { return KEEP_BUFFERS; } @@ -2924,6 +3654,7 @@ bool ACodec::BaseState::onMessageReceived(const sp<AMessage> &msg) { case ACodec::kWhatCreateInputSurface: case ACodec::kWhatSignalEndOfInputStream: { + // This may result in an app illegal state exception. ALOGE("Message 0x%x was not handled", msg->what()); mCodec->signalError(OMX_ErrorUndefined, INVALID_OPERATION); return true; @@ -2931,6 +3662,7 @@ bool ACodec::BaseState::onMessageReceived(const sp<AMessage> &msg) { case ACodec::kWhatOMXDied: { + // This will result in kFlagSawMediaServerDie handling in MediaCodec. ALOGE("OMX/mediaserver died, signalling error!"); mCodec->signalError(OMX_ErrorResourcesLost, DEAD_OBJECT); break; @@ -2947,6 +3679,14 @@ bool ACodec::BaseState::onOMXMessage(const sp<AMessage> &msg) { int32_t type; CHECK(msg->findInt32("type", &type)); + // there is a possibility that this is an outstanding message for a + // codec that we have already destroyed + if (mCodec->mNode == NULL) { + ALOGI("ignoring message as already freed component: %s", + msg->debugString().c_str()); + return true; + } + IOMX::node_id nodeID; CHECK(msg->findInt32("node", (int32_t*)&nodeID)); CHECK_EQ(nodeID, mCodec->mNode); @@ -2991,23 +3731,17 @@ bool ACodec::BaseState::onOMXMessage(const sp<AMessage> &msg) { int32_t rangeOffset, rangeLength, flags; int64_t timeUs; - void *platformPrivate; - void *dataPtr; CHECK(msg->findInt32("range_offset", &rangeOffset)); CHECK(msg->findInt32("range_length", &rangeLength)); CHECK(msg->findInt32("flags", &flags)); CHECK(msg->findInt64("timestamp", &timeUs)); - CHECK(msg->findPointer("platform_private", &platformPrivate)); - CHECK(msg->findPointer("data_ptr", &dataPtr)); return onOMXFillBufferDone( bufferID, (size_t)rangeOffset, (size_t)rangeLength, (OMX_U32)flags, - timeUs, - platformPrivate, - dataPtr); + timeUs); } default: @@ -3027,7 +3761,13 @@ bool ACodec::BaseState::onOMXEvent( ALOGE("[%s] ERROR(0x%08lx)", mCodec->mComponentName.c_str(), data1); - mCodec->signalError((OMX_ERRORTYPE)data1); + // verify OMX component sends back an error we expect. + OMX_ERRORTYPE omxError = (OMX_ERRORTYPE)data1; + if (!isOMXError(omxError)) { + ALOGW("Invalid OMX error %#x", omxError); + omxError = OMX_ErrorUndefined; + } + mCodec->signalError(omxError); return true; } @@ -3089,7 +3829,7 @@ void ACodec::BaseState::postFillThisBuffer(BufferInfo *info) { CHECK_EQ((int)info->mStatus, (int)BufferInfo::OWNED_BY_US); sp<AMessage> notify = mCodec->mNotify->dup(); - notify->setInt32("what", ACodec::kWhatFillThisBuffer); + notify->setInt32("what", CodecBase::kWhatFillThisBuffer); notify->setInt32("buffer-id", info->mBufferID); info->mData->meta()->clear(); @@ -3211,8 +3951,7 @@ void ACodec::BaseState::onInputBufferFilled(const sp<AMessage> &msg) { (outputMode == FREE_BUFFERS ? "FREE" : outputMode == KEEP_BUFFERS ? "KEEP" : "RESUBMIT")); if (outputMode == RESUBMIT_BUFFERS) { - CHECK_EQ(mCodec->submitOutputMetaDataBuffer(), - (status_t)OK); + mCodec->submitOutputMetaDataBuffer(); } } @@ -3305,10 +4044,8 @@ bool ACodec::BaseState::onOMXFillBufferDone( IOMX::buffer_id bufferID, size_t rangeOffset, size_t rangeLength, OMX_U32 flags, - int64_t timeUs, - void *platformPrivate, - void *dataPtr) { - ALOGV("[%s] onOMXFillBufferDone %p time %lld us, flags = 0x%08lx", + int64_t timeUs) { + ALOGV("[%s] onOMXFillBufferDone %u time %" PRId64 " us, flags = 0x%08x", mCodec->mComponentName.c_str(), bufferID, timeUs, flags); ssize_t index; @@ -3359,7 +4096,7 @@ bool ACodec::BaseState::onOMXFillBufferDone( sp<AMessage> reply = new AMessage(kWhatOutputBufferDrained, mCodec->id()); - if (!mCodec->mSentFormat) { + if (!mCodec->mSentFormat && rangeLength > 0) { mCodec->sendFormatChange(reply); } @@ -3386,7 +4123,7 @@ bool ACodec::BaseState::onOMXFillBufferDone( info->mData->meta()->setInt64("timeUs", timeUs); sp<AMessage> notify = mCodec->mNotify->dup(); - notify->setInt32("what", ACodec::kWhatDrainThisBuffer); + notify->setInt32("what", CodecBase::kWhatDrainThisBuffer); notify->setInt32("buffer-id", info->mBufferID); notify->setBuffer("buffer", info->mData); notify->setInt32("flags", flags); @@ -3403,7 +4140,7 @@ bool ACodec::BaseState::onOMXFillBufferDone( ALOGV("[%s] saw output EOS", mCodec->mComponentName.c_str()); sp<AMessage> notify = mCodec->mNotify->dup(); - notify->setInt32("what", ACodec::kWhatEOS); + notify->setInt32("what", CodecBase::kWhatEOS); notify->setInt32("err", mCodec->mInputEOSResult); notify->post(); @@ -3447,13 +4184,32 @@ void ACodec::BaseState::onOutputBufferDrained(const sp<AMessage> &msg) { ATRACE_NAME("render"); // The client wants this buffer to be rendered. + int64_t timestampNs = 0; + if (!msg->findInt64("timestampNs", ×tampNs)) { + // TODO: it seems like we should use the timestamp + // in the (media)buffer as it potentially came from + // an input surface, but we did not propagate it prior to + // API 20. Perhaps check for target SDK version. +#if 0 + if (info->mData->meta()->findInt64("timeUs", ×tampNs)) { + ALOGV("using buffer PTS of %" PRId64, timestampNs); + timestampNs *= 1000; + } +#endif + } + status_t err; + err = native_window_set_buffers_timestamp(mCodec->mNativeWindow.get(), timestampNs); + if (err != OK) { + ALOGW("failed to set buffer timestamp: %d", err); + } + if ((err = mCodec->mNativeWindow->queueBuffer( mCodec->mNativeWindow.get(), info->mGraphicBuffer.get(), -1)) == OK) { info->mStatus = BufferInfo::OWNED_BY_NATIVE_WINDOW; } else { - mCodec->signalError(OMX_ErrorUndefined, err); + mCodec->signalError(OMX_ErrorUndefined, makeNoSideEffectStatus(err)); info->mStatus = BufferInfo::OWNED_BY_US; } } else { @@ -3561,10 +4317,11 @@ bool ACodec::UninitializedState::onMessageReceived(const sp<AMessage> &msg) { int32_t keepComponentAllocated; CHECK(msg->findInt32( "keepComponentAllocated", &keepComponentAllocated)); - CHECK(!keepComponentAllocated); + ALOGW_IF(keepComponentAllocated, + "cannot keep component allocated on shutdown in Uninitialized state"); sp<AMessage> notify = mCodec->mNotify->dup(); - notify->setInt32("what", ACodec::kWhatShutdownCompleted); + notify->setInt32("what", CodecBase::kWhatShutdownCompleted); notify->post(); handled = true; @@ -3574,7 +4331,7 @@ bool ACodec::UninitializedState::onMessageReceived(const sp<AMessage> &msg) { case ACodec::kWhatFlush: { sp<AMessage> notify = mCodec->mNotify->dup(); - notify->setInt32("what", ACodec::kWhatFlushCompleted); + notify->setInt32("what", CodecBase::kWhatFlushCompleted); notify->post(); handled = true; @@ -3621,6 +4378,7 @@ bool ACodec::UninitializedState::onAllocateComponent(const sp<AMessage> &msg) { AString componentName; uint32_t quirks = 0; + int32_t encoder = false; if (msg->findString("componentName", &componentName)) { ssize_t index = matchingCodecs.add(); OMXCodec::CodecNameAndQuirks *entry = &matchingCodecs.editItemAt(index); @@ -3633,7 +4391,6 @@ bool ACodec::UninitializedState::onAllocateComponent(const sp<AMessage> &msg) { } else { CHECK(msg->findString("mime", &mime)); - int32_t encoder; if (!msg->findInt32("encoder", &encoder)) { encoder = false; } @@ -3662,6 +4419,8 @@ bool ACodec::UninitializedState::onAllocateComponent(const sp<AMessage> &msg) { if (err == OK) { break; + } else { + ALOGW("Allocating component '%s' failed, try next one.", componentName.c_str()); } node = NULL; @@ -3669,10 +4428,10 @@ bool ACodec::UninitializedState::onAllocateComponent(const sp<AMessage> &msg) { if (node == NULL) { if (!mime.empty()) { - ALOGE("Unable to instantiate a decoder for type '%s'.", - mime.c_str()); + ALOGE("Unable to instantiate a %scoder for type '%s'.", + encoder ? "en" : "de", mime.c_str()); } else { - ALOGE("Unable to instantiate decoder '%s'.", componentName.c_str()); + ALOGE("Unable to instantiate codec '%s'.", componentName.c_str()); } mCodec->signalError(OMX_ErrorComponentNotFound); @@ -3696,7 +4455,7 @@ bool ACodec::UninitializedState::onAllocateComponent(const sp<AMessage> &msg) { { sp<AMessage> notify = mCodec->mNotify->dup(); - notify->setInt32("what", ACodec::kWhatComponentAllocated); + notify->setInt32("what", CodecBase::kWhatComponentAllocated); notify->setString("componentName", mCodec->mComponentName.c_str()); notify->post(); } @@ -3723,7 +4482,8 @@ void ACodec::LoadedState::stateEntered() { mCodec->mDequeueCounter = 0; mCodec->mMetaDataBuffersToSubmit = 0; mCodec->mRepeatFrameDelayUs = -1ll; - mCodec->mIsConfiguredForAdaptivePlayback = false; + mCodec->mInputFormat.clear(); + mCodec->mOutputFormat.clear(); if (mCodec->mShutdownInProgress) { bool keepComponentAllocated = mCodec->mKeepComponentAllocated; @@ -3733,6 +4493,7 @@ void ACodec::LoadedState::stateEntered() { onShutdown(keepComponentAllocated); } + mCodec->mExplicitShutdown = false; } void ACodec::LoadedState::onShutdown(bool keepComponentAllocated) { @@ -3742,9 +4503,12 @@ void ACodec::LoadedState::onShutdown(bool keepComponentAllocated) { mCodec->changeState(mCodec->mUninitializedState); } - sp<AMessage> notify = mCodec->mNotify->dup(); - notify->setInt32("what", ACodec::kWhatShutdownCompleted); - notify->post(); + if (mCodec->mExplicitShutdown) { + sp<AMessage> notify = mCodec->mNotify->dup(); + notify->setInt32("what", CodecBase::kWhatShutdownCompleted); + notify->post(); + mCodec->mExplicitShutdown = false; + } } bool ACodec::LoadedState::onMessageReceived(const sp<AMessage> &msg) { @@ -3778,6 +4542,7 @@ bool ACodec::LoadedState::onMessageReceived(const sp<AMessage> &msg) { CHECK(msg->findInt32( "keepComponentAllocated", &keepComponentAllocated)); + mCodec->mExplicitShutdown = true; onShutdown(keepComponentAllocated); handled = true; @@ -3787,7 +4552,7 @@ bool ACodec::LoadedState::onMessageReceived(const sp<AMessage> &msg) { case ACodec::kWhatFlush: { sp<AMessage> notify = mCodec->mNotify->dup(); - notify->setInt32("what", ACodec::kWhatFlushCompleted); + notify->setInt32("what", CodecBase::kWhatFlushCompleted); notify->post(); handled = true; @@ -3816,7 +4581,7 @@ bool ACodec::LoadedState::onConfigureComponent( ALOGE("[%s] configureCodec returning error %d", mCodec->mComponentName.c_str(), err); - mCodec->signalError(OMX_ErrorUndefined, err); + mCodec->signalError(OMX_ErrorUndefined, makeNoSideEffectStatus(err)); return false; } @@ -3836,7 +4601,9 @@ bool ACodec::LoadedState::onConfigureComponent( { sp<AMessage> notify = mCodec->mNotify->dup(); - notify->setInt32("what", ACodec::kWhatComponentConfigured); + notify->setInt32("what", CodecBase::kWhatComponentConfigured); + notify->setMessage("input-format", mCodec->mInputFormat); + notify->setMessage("output-format", mCodec->mOutputFormat); notify->post(); } @@ -3844,11 +4611,11 @@ bool ACodec::LoadedState::onConfigureComponent( } void ACodec::LoadedState::onCreateInputSurface( - const sp<AMessage> &msg) { + const sp<AMessage> & /* msg */) { ALOGV("onCreateInputSurface"); sp<AMessage> notify = mCodec->mNotify->dup(); - notify->setInt32("what", ACodec::kWhatInputSurfaceCreated); + notify->setInt32("what", CodecBase::kWhatInputSurfaceCreated); sp<IGraphicBufferProducer> bufferProducer; status_t err; @@ -3872,7 +4639,7 @@ void ACodec::LoadedState::onCreateInputSurface( } } - if (err == OK && mCodec->mMaxPtsGapUs > 0l) { + if (err == OK && mCodec->mMaxPtsGapUs > 0ll) { err = mCodec->mOMX->setInternalOption( mCodec->mNode, kPortIndexInput, @@ -3882,6 +4649,41 @@ void ACodec::LoadedState::onCreateInputSurface( if (err != OK) { ALOGE("[%s] Unable to configure max timestamp gap (err %d)", + mCodec->mComponentName.c_str(), + err); + } + } + + if (err == OK && mCodec->mTimePerCaptureUs > 0ll + && mCodec->mTimePerFrameUs > 0ll) { + int64_t timeLapse[2]; + timeLapse[0] = mCodec->mTimePerFrameUs; + timeLapse[1] = mCodec->mTimePerCaptureUs; + err = mCodec->mOMX->setInternalOption( + mCodec->mNode, + kPortIndexInput, + IOMX::INTERNAL_OPTION_TIME_LAPSE, + &timeLapse[0], + sizeof(timeLapse)); + + if (err != OK) { + ALOGE("[%s] Unable to configure time lapse (err %d)", + mCodec->mComponentName.c_str(), + err); + } + } + + if (err == OK && mCodec->mCreateInputBuffersSuspended) { + bool suspend = true; + err = mCodec->mOMX->setInternalOption( + mCodec->mNode, + kPortIndexInput, + IOMX::INTERNAL_OPTION_SUSPEND, + &suspend, + sizeof(suspend)); + + if (err != OK) { + ALOGE("[%s] Unable to configure option to suspend (err %d)", mCodec->mComponentName.c_str(), err); } @@ -3926,7 +4728,7 @@ void ACodec::LoadedToIdleState::stateEntered() { "(error 0x%08x)", err); - mCodec->signalError(OMX_ErrorUndefined, err); + mCodec->signalError(OMX_ErrorUndefined, makeNoSideEffectStatus(err)); mCodec->changeState(mCodec->mLoadedState); } @@ -3944,6 +4746,7 @@ status_t ACodec::LoadedToIdleState::allocateBuffers() { bool ACodec::LoadedToIdleState::onMessageReceived(const sp<AMessage> &msg) { switch (msg->what()) { + case kWhatSetParameters: case kWhatShutdown: { mCodec->deferMessage(msg); @@ -3966,7 +4769,7 @@ bool ACodec::LoadedToIdleState::onMessageReceived(const sp<AMessage> &msg) { { // We haven't even started yet, so we're flushed alright... sp<AMessage> notify = mCodec->mNotify->dup(); - notify->setInt32("what", ACodec::kWhatFlushCompleted); + notify->setInt32("what", CodecBase::kWhatFlushCompleted); notify->post(); return true; } @@ -4010,6 +4813,7 @@ void ACodec::IdleToExecutingState::stateEntered() { bool ACodec::IdleToExecutingState::onMessageReceived(const sp<AMessage> &msg) { switch (msg->what()) { + case kWhatSetParameters: case kWhatShutdown: { mCodec->deferMessage(msg); @@ -4026,7 +4830,7 @@ bool ACodec::IdleToExecutingState::onMessageReceived(const sp<AMessage> &msg) { { // We haven't even started yet, so we're flushed alright... sp<AMessage> notify = mCodec->mNotify->dup(); - notify->setInt32("what", ACodec::kWhatFlushCompleted); + notify->setInt32("what", CodecBase::kWhatFlushCompleted); notify->post(); return true; @@ -4070,7 +4874,7 @@ ACodec::ExecutingState::ExecutingState(ACodec *codec) } ACodec::BaseState::PortMode ACodec::ExecutingState::getPortMode( - OMX_U32 portIndex) { + OMX_U32 /* portIndex */) { return RESUBMIT_BUFFERS; } @@ -4132,11 +4936,14 @@ void ACodec::ExecutingState::resume() { submitOutputBuffers(); - // Post the first input buffer. + // Post all available input buffers CHECK_GT(mCodec->mBuffers[kPortIndexInput].size(), 0u); - BufferInfo *info = &mCodec->mBuffers[kPortIndexInput].editItemAt(0); - - postFillThisBuffer(info); + for (size_t i = 0; i < mCodec->mBuffers[kPortIndexInput].size(); i++) { + BufferInfo *info = &mCodec->mBuffers[kPortIndexInput].editItemAt(i); + if (info->mStatus == BufferInfo::OWNED_BY_US) { + postFillThisBuffer(info); + } + } mActive = true; } @@ -4158,6 +4965,7 @@ bool ACodec::ExecutingState::onMessageReceived(const sp<AMessage> &msg) { "keepComponentAllocated", &keepComponentAllocated)); mCodec->mShutdownInProgress = true; + mCodec->mExplicitShutdown = true; mCodec->mKeepComponentAllocated = keepComponentAllocated; mActive = false; @@ -4279,6 +5087,22 @@ status_t ACodec::setParameters(const sp<AMessage> ¶ms) { } } + int64_t skipFramesBeforeUs; + if (params->findInt64("skip-frames-before", &skipFramesBeforeUs)) { + status_t err = + mOMX->setInternalOption( + mNode, + kPortIndexInput, + IOMX::INTERNAL_OPTION_START_TIME, + &skipFramesBeforeUs, + sizeof(skipFramesBeforeUs)); + + if (err != OK) { + ALOGE("Failed to set parameter 'skip-frames-before' (err %d)", err); + return err; + } + } + int32_t dropInputFrames; if (params->findInt32("drop-input-frames", &dropInputFrames)) { bool suspend = dropInputFrames != 0; @@ -4312,7 +5136,7 @@ status_t ACodec::setParameters(const sp<AMessage> ¶ms) { void ACodec::onSignalEndOfInputStream() { sp<AMessage> notify = mNotify->dup(); - notify->setInt32("what", ACodec::kWhatSignaledInputEOS); + notify->setInt32("what", CodecBase::kWhatSignaledInputEOS); status_t err = mOMX->signalEndOfInputStream(mNode); if (err != OK) { @@ -4432,7 +5256,7 @@ bool ACodec::OutputPortSettingsChangedState::onOMXEvent( "port reconfiguration (error 0x%08x)", err); - mCodec->signalError(OMX_ErrorUndefined, err); + mCodec->signalError(OMX_ErrorUndefined, makeNoSideEffectStatus(err)); // This is technically not correct, but appears to be // the only way to free the component instance. @@ -4742,7 +5566,7 @@ void ACodec::FlushingState::changeStateIfWeOwnAllBuffers() { mCodec->waitUntilAllPossibleNativeWindowBuffersAreReturnedToUs(); sp<AMessage> notify = mCodec->mNotify->dup(); - notify->setInt32("what", ACodec::kWhatFlushCompleted); + notify->setInt32("what", CodecBase::kWhatFlushCompleted); notify->post(); mCodec->mPortEOS[kPortIndexInput] = |