/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim:set ts=2 sw=2 sts=2 et cindent: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mozilla/Types.h" #include "MPAPI.h" #include "android/log.h" #define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "OmxPlugin" , ## args) using namespace MPAPI; namespace android { // MediaStreamSource is a DataSource that reads from a MPAPI media stream. class MediaStreamSource : public DataSource { PluginHost *mPluginHost; public: MediaStreamSource(PluginHost *aPluginHost, Decoder *aDecoder); virtual status_t initCheck() const; virtual ssize_t readAt(off64_t offset, void *data, size_t size); virtual ssize_t readAt(off_t offset, void *data, size_t size) { return readAt(static_cast(offset), data, size); } virtual status_t getSize(off_t *size) { off64_t size64; status_t status = getSize(&size64); *size = size64; return status; } virtual status_t getSize(off64_t *size); virtual uint32_t flags() { return kWantsPrefetching; } virtual ~MediaStreamSource(); private: Decoder *mDecoder; MediaStreamSource(const MediaStreamSource &); MediaStreamSource &operator=(const MediaStreamSource &); }; MediaStreamSource::MediaStreamSource(PluginHost *aPluginHost, Decoder *aDecoder) : mPluginHost(aPluginHost) { mDecoder = aDecoder; } MediaStreamSource::~MediaStreamSource() { } status_t MediaStreamSource::initCheck() const { return OK; } ssize_t MediaStreamSource::readAt(off64_t offset, void *data, size_t size) { char *ptr = reinterpret_cast(data); size_t todo = size; while (todo > 0) { uint32_t bytesRead; if (!mPluginHost->Read(mDecoder, ptr, offset, todo, &bytesRead)) { return ERROR_IO; } offset += bytesRead; todo -= bytesRead; ptr += bytesRead; } return size; } status_t MediaStreamSource::getSize(off64_t *size) { uint64_t length = mPluginHost->GetLength(mDecoder); if (length == static_cast(-1)) return ERROR_UNSUPPORTED; *size = length; return OK; } } // namespace android using namespace android; class OmxDecoder { PluginHost *mPluginHost; Decoder *mDecoder; sp mVideoTrack; sp mVideoSource; sp mAudioTrack; sp mAudioSource; int32_t mVideoWidth; int32_t mVideoHeight; int32_t mVideoColorFormat; int32_t mVideoStride; int32_t mVideoSliceHeight; int32_t mVideoRotation; int32_t mAudioChannels; int32_t mAudioSampleRate; int64_t mDurationUs; MediaBuffer *mVideoBuffer; VideoFrame mVideoFrame; MediaBuffer *mAudioBuffer; AudioFrame mAudioFrame; // 'true' if a read from the audio stream was done while reading the metadata bool mAudioMetadataRead; void ReleaseVideoBuffer(); void ReleaseAudioBuffer(); void PlanarYUV420Frame(VideoFrame *aFrame, int64_t aTimeUs, void *aData, size_t aSize, bool aKeyFrame); void CbYCrYFrame(VideoFrame *aFrame, int64_t aTimeUs, void *aData, size_t aSize, bool aKeyFrame); void SemiPlanarYUV420Frame(VideoFrame *aFrame, int64_t aTimeUs, void *aData, size_t aSize, bool aKeyFrame); void SemiPlanarYVU420Frame(VideoFrame *aFrame, int64_t aTimeUs, void *aData, size_t aSize, bool aKeyFrame); bool ToVideoFrame(VideoFrame *aFrame, int64_t aTimeUs, void *aData, size_t aSize, bool aKeyFrame); bool ToAudioFrame(AudioFrame *aFrame, int64_t aTimeUs, void *aData, size_t aDataOffset, size_t aSize, int32_t aAudioChannels, int32_t aAudioSampleRate); public: OmxDecoder(PluginHost *aPluginHost, Decoder *aDecoder); ~OmxDecoder(); bool Init(); bool SetVideoFormat(); bool SetAudioFormat(); void GetDuration(int64_t *durationUs) { *durationUs = mDurationUs; } void GetVideoParameters(int32_t *width, int32_t *height) { *width = mVideoWidth; *height = mVideoHeight; } void GetAudioParameters(int32_t *numChannels, int32_t *sampleRate) { *numChannels = mAudioChannels; *sampleRate = mAudioSampleRate; } bool HasVideo() { return mVideoSource != NULL; } bool HasAudio() { return mAudioSource != NULL; } bool ReadVideo(VideoFrame *aFrame, int64_t aSeekTimeUs); bool ReadAudio(AudioFrame *aFrame, int64_t aSeekTimeUs); }; OmxDecoder::OmxDecoder(PluginHost *aPluginHost, Decoder *aDecoder) : mPluginHost(aPluginHost), mDecoder(aDecoder), mVideoWidth(0), mVideoHeight(0), mVideoColorFormat(0), mVideoStride(0), mVideoSliceHeight(0), mVideoRotation(0), mAudioChannels(-1), mAudioSampleRate(-1), mDurationUs(-1), mVideoBuffer(NULL), mAudioBuffer(NULL), mAudioMetadataRead(false) { } OmxDecoder::~OmxDecoder() { ReleaseVideoBuffer(); ReleaseAudioBuffer(); if (mVideoSource.get()) { mVideoSource->stop(); } if (mAudioSource.get()) { mAudioSource->stop(); } } class AutoStopMediaSource { sp mMediaSource; public: AutoStopMediaSource(sp aMediaSource) : mMediaSource(aMediaSource) { } ~AutoStopMediaSource() { mMediaSource->stop(); } }; static sp sOMX = NULL; static sp GetOMX() { if(sOMX.get() == NULL) { sOMX = new OMX; } return sOMX; } bool OmxDecoder::Init() { //register sniffers, if they are not registered in this process. DataSource::RegisterDefaultSniffers(); sp dataSource = new MediaStreamSource(mPluginHost, mDecoder); if (dataSource->initCheck()) { return false; } mPluginHost->SetMetaDataReadMode(mDecoder); sp extractor = MediaExtractor::Create(dataSource); if (extractor == NULL) { return false; } ssize_t audioTrackIndex = -1; ssize_t videoTrackIndex = -1; const char *audioMime = NULL; for (size_t i = 0; i < extractor->countTracks(); ++i) { sp meta = extractor->getTrackMetaData(i); int32_t bitRate; if (!meta->findInt32(kKeyBitRate, &bitRate)) bitRate = 0; const char *mime; if (!meta->findCString(kKeyMIMEType, &mime)) { continue; } if (videoTrackIndex == -1 && !strncasecmp(mime, "video/", 6)) { videoTrackIndex = i; } else if (audioTrackIndex == -1 && !strncasecmp(mime, "audio/", 6)) { audioTrackIndex = i; audioMime = mime; } } if (videoTrackIndex == -1 && audioTrackIndex == -1) { return false; } mPluginHost->SetPlaybackReadMode(mDecoder); int64_t totalDurationUs = 0; sp videoTrack; sp videoSource; if (videoTrackIndex != -1 && (videoTrack = extractor->getTrack(videoTrackIndex)) != NULL) { videoSource = OMXCodec::Create(GetOMX(), videoTrack->getFormat(), false, // decoder videoTrack, NULL, 0); // flags (prefer hw codecs) if (videoSource == NULL) { return false; } if (videoSource->start() != OK) { return false; } int64_t durationUs; if (videoTrack->getFormat()->findInt64(kKeyDuration, &durationUs)) { if (durationUs > totalDurationUs) totalDurationUs = durationUs; } } sp audioTrack; sp audioSource; if (audioTrackIndex != -1 && (audioTrack = extractor->getTrack(audioTrackIndex)) != NULL) { if (!strcasecmp(audioMime, "audio/raw")) { audioSource = audioTrack; } else { audioSource = OMXCodec::Create(GetOMX(), audioTrack->getFormat(), false, // decoder audioTrack); } if (audioSource == NULL) { return false; } if (audioSource->start() != OK) { return false; } int64_t durationUs; if (audioTrack->getFormat()->findInt64(kKeyDuration, &durationUs)) { if (durationUs > totalDurationUs) totalDurationUs = durationUs; } } // set decoder state mVideoTrack = videoTrack; mVideoSource = videoSource; mAudioTrack = audioTrack; mAudioSource = audioSource; mDurationUs = totalDurationUs; if (mVideoSource.get() && !SetVideoFormat()) return false; // To reliably get the channel and sample rate data we need to read from the // audio source until we get a INFO_FORMAT_CHANGE status if (mAudioSource.get()) { if (mAudioSource->read(&mAudioBuffer) != INFO_FORMAT_CHANGED) { sp meta = mAudioSource->getFormat(); if (!meta->findInt32(kKeyChannelCount, &mAudioChannels) || !meta->findInt32(kKeySampleRate, &mAudioSampleRate)) { return false; } mAudioMetadataRead = true; } else if (!SetAudioFormat()) { return false; } } return true; } bool OmxDecoder::SetVideoFormat() { const char *componentName; if (!mVideoSource->getFormat()->findInt32(kKeyWidth, &mVideoWidth) || !mVideoSource->getFormat()->findInt32(kKeyHeight, &mVideoHeight) || !mVideoSource->getFormat()->findCString(kKeyDecoderComponent, &componentName) || !mVideoSource->getFormat()->findInt32(kKeyColorFormat, &mVideoColorFormat) ) { return false; } if (!mVideoSource->getFormat()->findInt32(kKeyStride, &mVideoStride)) { mVideoStride = mVideoWidth; LOG("stride not available, assuming width"); } if (!mVideoSource->getFormat()->findInt32(kKeySliceHeight, &mVideoSliceHeight)) { mVideoSliceHeight = mVideoHeight; LOG("slice height not available, assuming height"); } if (!mVideoSource->getFormat()->findInt32(kKeyRotation, &mVideoRotation)) { mVideoRotation = 0; LOG("rotation not available, assuming 0"); } LOG("width: %d height: %d component: %s format: %d stride: %d sliceHeight: %d rotation: %d", mVideoWidth, mVideoHeight, componentName, mVideoColorFormat, mVideoStride, mVideoSliceHeight, mVideoRotation); return true; } bool OmxDecoder::SetAudioFormat() { // If the format changed, update our cached info. if (!mAudioSource->getFormat()->findInt32(kKeyChannelCount, &mAudioChannels) || !mAudioSource->getFormat()->findInt32(kKeySampleRate, &mAudioSampleRate)) { return false; } LOG("channelCount: %d sampleRate: %d", mAudioChannels, mAudioSampleRate); return true; } void OmxDecoder::ReleaseVideoBuffer() { if (mVideoBuffer) { mVideoBuffer->release(); mVideoBuffer = NULL; } } void OmxDecoder::ReleaseAudioBuffer() { if (mAudioBuffer) { mAudioBuffer->release(); mAudioBuffer = NULL; } } void OmxDecoder::PlanarYUV420Frame(VideoFrame *aFrame, int64_t aTimeUs, void *aData, size_t aSize, bool aKeyFrame) { void *y = aData; void *u = static_cast(y) + mVideoStride * mVideoSliceHeight; void *v = static_cast(u) + mVideoStride/2 * mVideoSliceHeight/2; aFrame->Set(aTimeUs, aKeyFrame, aData, aSize, mVideoStride, mVideoSliceHeight, mVideoRotation, y, mVideoStride, mVideoWidth, mVideoHeight, 0, 0, u, mVideoStride/2, mVideoWidth/2, mVideoHeight/2, 0, 0, v, mVideoStride/2, mVideoWidth/2, mVideoHeight/2, 0, 0); } void OmxDecoder::CbYCrYFrame(VideoFrame *aFrame, int64_t aTimeUs, void *aData, size_t aSize, bool aKeyFrame) { aFrame->Set(aTimeUs, aKeyFrame, aData, aSize, mVideoStride, mVideoSliceHeight, mVideoRotation, aData, mVideoStride, mVideoWidth, mVideoHeight, 1, 1, aData, mVideoStride, mVideoWidth/2, mVideoHeight/2, 0, 3, aData, mVideoStride, mVideoWidth/2, mVideoHeight/2, 2, 3); } void OmxDecoder::SemiPlanarYUV420Frame(VideoFrame *aFrame, int64_t aTimeUs, void *aData, size_t aSize, bool aKeyFrame) { void *y = aData; void *uv = static_cast(y) + (mVideoStride * mVideoSliceHeight); aFrame->Set(aTimeUs, aKeyFrame, aData, aSize, mVideoStride, mVideoSliceHeight, mVideoRotation, y, mVideoStride, mVideoWidth, mVideoHeight, 0, 0, uv, mVideoStride, mVideoWidth/2, mVideoHeight/2, 0, 1, uv, mVideoStride, mVideoWidth/2, mVideoHeight/2, 1, 1); } void OmxDecoder::SemiPlanarYVU420Frame(VideoFrame *aFrame, int64_t aTimeUs, void *aData, size_t aSize, bool aKeyFrame) { SemiPlanarYUV420Frame(aFrame, aTimeUs, aData, aSize, aKeyFrame); aFrame->Cb.mOffset = 1; aFrame->Cr.mOffset = 0; } bool OmxDecoder::ToVideoFrame(VideoFrame *aFrame, int64_t aTimeUs, void *aData, size_t aSize, bool aKeyFrame) { const int OMX_QCOM_COLOR_FormatYVU420SemiPlanar = 0x7FA30C00; switch (mVideoColorFormat) { case OMX_COLOR_FormatYUV420Planar: PlanarYUV420Frame(aFrame, aTimeUs, aData, aSize, aKeyFrame); break; case OMX_COLOR_FormatCbYCrY: CbYCrYFrame(aFrame, aTimeUs, aData, aSize, aKeyFrame); break; case OMX_COLOR_FormatYUV420SemiPlanar: SemiPlanarYUV420Frame(aFrame, aTimeUs, aData, aSize, aKeyFrame); break; case OMX_QCOM_COLOR_FormatYVU420SemiPlanar: SemiPlanarYVU420Frame(aFrame, aTimeUs, aData, aSize, aKeyFrame); break; default: return false; } return true; } bool OmxDecoder::ToAudioFrame(AudioFrame *aFrame, int64_t aTimeUs, void *aData, size_t aDataOffset, size_t aSize, int32_t aAudioChannels, int32_t aAudioSampleRate) { aFrame->Set(aTimeUs, reinterpret_cast(aData) + aDataOffset, aSize, aAudioChannels, aAudioSampleRate); return true; } bool OmxDecoder::ReadVideo(VideoFrame *aFrame, int64_t aSeekTimeUs) { if (!mVideoSource.get()) return false; ReleaseVideoBuffer(); status_t err; if (aSeekTimeUs != -1) { MediaSource::ReadOptions options; options.setSeekTo(aSeekTimeUs); err = mVideoSource->read(&mVideoBuffer, &options); } else { err = mVideoSource->read(&mVideoBuffer); } if (err == OK && mVideoBuffer->range_length() > 0) { int64_t timeUs; int32_t unreadable; int32_t keyFrame; if (!mVideoBuffer->meta_data()->findInt64(kKeyTime, &timeUs) ) { LOG("no key time"); return false; } if (!mVideoBuffer->meta_data()->findInt32(kKeyIsSyncFrame, &keyFrame)) { keyFrame = 0; } if (!mVideoBuffer->meta_data()->findInt32(kKeyIsUnreadable, &unreadable)) { unreadable = 0; } LOG("data: %p size: %u offset: %u length: %u unreadable: %d", mVideoBuffer->data(), mVideoBuffer->size(), mVideoBuffer->range_offset(), mVideoBuffer->range_length(), unreadable); char *data = reinterpret_cast(mVideoBuffer->data()) + mVideoBuffer->range_offset(); size_t length = mVideoBuffer->range_length(); if (unreadable) { LOG("video frame is unreadable"); } if (!ToVideoFrame(aFrame, timeUs, data, length, keyFrame)) { return false; } } else if (err == INFO_FORMAT_CHANGED) { // If the format changed, update our cached info. return SetVideoFormat(); } else if (err == ERROR_END_OF_STREAM) { return false; } return true; } bool OmxDecoder::ReadAudio(AudioFrame *aFrame, int64_t aSeekTimeUs) { status_t err; if (mAudioMetadataRead && aSeekTimeUs == -1) { // Use the data read into the buffer during metadata time err = OK; } else { ReleaseAudioBuffer(); if (aSeekTimeUs != -1) { MediaSource::ReadOptions options; options.setSeekTo(aSeekTimeUs); err = mAudioSource->read(&mAudioBuffer, &options); } else { err = mAudioSource->read(&mAudioBuffer); } } mAudioMetadataRead = false; aSeekTimeUs = -1; if (err == OK && mAudioBuffer->range_length() != 0) { int64_t timeUs; if (!mAudioBuffer->meta_data()->findInt64(kKeyTime, &timeUs)) return false; return ToAudioFrame(aFrame, timeUs, mAudioBuffer->data(), mAudioBuffer->range_offset(), mAudioBuffer->range_length(), mAudioChannels, mAudioSampleRate); } else if (err == INFO_FORMAT_CHANGED && !SetAudioFormat()) { // If the format changed, update our cached info. return false; } else if (err == ERROR_END_OF_STREAM) return false; return true; } static OmxDecoder *cast(Decoder *decoder) { return reinterpret_cast(decoder->mPrivate); } static void GetDuration(Decoder *aDecoder, int64_t *durationUs) { cast(aDecoder)->GetDuration(durationUs); } static void GetVideoParameters(Decoder *aDecoder, int32_t *width, int32_t *height) { cast(aDecoder)->GetVideoParameters(width, height); } static void GetAudioParameters(Decoder *aDecoder, int32_t *numChannels, int32_t *sampleRate) { cast(aDecoder)->GetAudioParameters(numChannels, sampleRate); } static bool HasVideo(Decoder *aDecoder) { return cast(aDecoder)->HasVideo(); } static bool HasAudio(Decoder *aDecoder) { return cast(aDecoder)->HasAudio(); } static bool ReadVideo(Decoder *aDecoder, VideoFrame *aFrame, int64_t aSeekTimeUs) { return cast(aDecoder)->ReadVideo(aFrame, aSeekTimeUs); } static bool ReadAudio(Decoder *aDecoder, AudioFrame *aFrame, int64_t aSeekTimeUs) { return cast(aDecoder)->ReadAudio(aFrame, aSeekTimeUs); } static void DestroyDecoder(Decoder *aDecoder) { if (aDecoder->mPrivate) delete reinterpret_cast(aDecoder->mPrivate); } static bool Match(const char *aMimeChars, size_t aMimeLen, const char *aNeedle) { return !strncmp(aMimeChars, aNeedle, aMimeLen); } static const char* const gCodecs[] = { "avc", "mp3", "mp4v", "mp4a", NULL }; static bool CanDecode(const char *aMimeChars, size_t aMimeLen, const char* const**aCodecs) { if (!Match(aMimeChars, aMimeLen, "video/mp4") && !Match(aMimeChars, aMimeLen, "audio/mp4") && !Match(aMimeChars, aMimeLen, "audio/mpeg") && !Match(aMimeChars, aMimeLen, "application/octet-stream")) { // file urls return false; } *aCodecs = gCodecs; return true; } static bool CreateDecoder(PluginHost *aPluginHost, Decoder *aDecoder, const char *aMimeChars, size_t aMimeLen) { OmxDecoder *omx = new OmxDecoder(aPluginHost, aDecoder); if (!omx || !omx->Init()) return false; aDecoder->mPrivate = omx; aDecoder->GetDuration = GetDuration; aDecoder->GetVideoParameters = GetVideoParameters; aDecoder->GetAudioParameters = GetAudioParameters; aDecoder->HasVideo = HasVideo; aDecoder->HasAudio = HasAudio; aDecoder->ReadVideo = ReadVideo; aDecoder->ReadAudio = ReadAudio; aDecoder->DestroyDecoder = DestroyDecoder; return true; } // Export the manifest so MPAPI can find our entry points. Manifest MOZ_EXPORT_DATA(MPAPI_MANIFEST) { CanDecode, CreateDecoder };