mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-24 13:21:05 +00:00
Bug 1311214 - Remove b2g OMX encoder/decoder; r=cpearce
This commit is contained in:
parent
be86e8df54
commit
d5d66e16c9
@ -542,7 +542,6 @@ pref("dom.webapps.useCurrentProfile", true);
|
||||
// Enable system message
|
||||
pref("dom.sysmsg.enabled", true);
|
||||
pref("media.plugins.enabled", false);
|
||||
pref("media.omx.enabled", true);
|
||||
pref("media.rtsp.enabled", true);
|
||||
pref("media.rtsp.video.enabled", true);
|
||||
|
||||
|
@ -24,12 +24,6 @@
|
||||
#include "AndroidMediaReader.h"
|
||||
#include "AndroidMediaPluginHost.h"
|
||||
#endif
|
||||
#ifdef MOZ_OMX_DECODER
|
||||
#include "MediaOmxDecoder.h"
|
||||
#include "MediaOmxReader.h"
|
||||
#include "nsIPrincipal.h"
|
||||
#include "mozilla/dom/HTMLMediaElement.h"
|
||||
#endif
|
||||
#ifdef MOZ_DIRECTSHOW
|
||||
#include "DirectShowDecoder.h"
|
||||
#include "DirectShowReader.h"
|
||||
@ -146,89 +140,6 @@ IsHttpLiveStreamingType(const nsACString& aType)
|
||||
return CodecListContains(gHttpLiveStreamingTypes, aType);
|
||||
}
|
||||
|
||||
#ifdef MOZ_OMX_DECODER
|
||||
static const char* const gOmxTypes[] = {
|
||||
"audio/mpeg",
|
||||
"audio/mp4",
|
||||
"audio/amr",
|
||||
"audio/3gpp",
|
||||
"audio/flac",
|
||||
"video/mp4",
|
||||
"video/x-m4v",
|
||||
"video/3gpp",
|
||||
"video/3gpp2",
|
||||
"video/quicktime",
|
||||
#ifdef MOZ_OMX_WEBM_DECODER
|
||||
"video/webm",
|
||||
"audio/webm",
|
||||
#endif
|
||||
"audio/x-matroska",
|
||||
"video/mp2t",
|
||||
"video/avi",
|
||||
"video/x-matroska",
|
||||
nullptr
|
||||
};
|
||||
|
||||
static const char* const gB2GOnlyTypes[] = {
|
||||
"audio/3gpp",
|
||||
"audio/amr",
|
||||
"audio/x-matroska",
|
||||
"video/mp2t",
|
||||
"video/avi",
|
||||
"video/x-matroska",
|
||||
nullptr
|
||||
};
|
||||
|
||||
static bool
|
||||
IsOmxSupportedType(const nsACString& aType)
|
||||
{
|
||||
if (!MediaDecoder::IsOmxEnabled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return CodecListContains(gOmxTypes, aType);
|
||||
}
|
||||
|
||||
static bool
|
||||
IsB2GSupportOnlyType(const nsACString& aType)
|
||||
{
|
||||
return CodecListContains(gB2GOnlyTypes, aType);
|
||||
}
|
||||
|
||||
static char const *const gH264Codecs[9] = {
|
||||
"avc1.42E01E", // H.264 Constrained Baseline Profile Level 3.0
|
||||
"avc1.42001E", // H.264 Baseline Profile Level 3.0
|
||||
"avc1.58A01E", // H.264 Extended Profile Level 3.0
|
||||
"avc1.4D401E", // H.264 Main Profile Level 3.0
|
||||
"avc1.64001E", // H.264 High Profile Level 3.0
|
||||
"avc1.64001F", // H.264 High Profile Level 3.1
|
||||
"mp4v.20.3", // 3GPP
|
||||
"mp4a.40.2", // AAC-LC
|
||||
nullptr
|
||||
};
|
||||
|
||||
static char const *const gMpegAudioCodecs[2] = {
|
||||
"mp3", // MP3
|
||||
nullptr
|
||||
};
|
||||
|
||||
#ifdef MOZ_OMX_WEBM_DECODER
|
||||
static char const *const gOMXWebMCodecs[] = {
|
||||
"vorbis",
|
||||
"vp8",
|
||||
"vp8.0",
|
||||
// Since Android KK, VP9 SW decoder is supported.
|
||||
// http://developer.android.com/guide/appendix/media-formats.html
|
||||
#if ANDROID_VERSION > 18
|
||||
"vp9",
|
||||
"vp9.0",
|
||||
#endif
|
||||
nullptr
|
||||
};
|
||||
#endif //MOZ_OMX_WEBM_DECODER
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef MOZ_ANDROID_OMX
|
||||
static bool
|
||||
IsAndroidMediaType(const nsACString& aType)
|
||||
@ -283,11 +194,7 @@ static bool
|
||||
IsMP3SupportedType(const nsACString& aType,
|
||||
const nsAString& aCodecs = EmptyString())
|
||||
{
|
||||
#ifdef MOZ_OMX_DECODER
|
||||
return false;
|
||||
#else
|
||||
return MP3Decoder::CanHandleMediaType(aType, aCodecs);
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool
|
||||
@ -364,20 +271,6 @@ CanHandleCodecsType(const MediaContentType& aType,
|
||||
if (IsFlacSupportedType(aType.GetMIMEType(), aType.GetCodecs())) {
|
||||
return CANPLAY_YES;
|
||||
}
|
||||
#ifdef MOZ_OMX_DECODER
|
||||
if (IsOmxSupportedType(aType.GetMIMEType())) {
|
||||
if (aType.GetMIMEType().EqualsASCII("audio/mpeg")) {
|
||||
codecList = gMpegAudioCodecs;
|
||||
#ifdef MOZ_OMX_WEBM_DECODER
|
||||
} else if (aType.GetMIMEType().EqualsASCII("audio/webm") ||
|
||||
aType.GetMIMEType().EqualsASCII("video/webm")) {
|
||||
codecList = gOMXWebMCodecs;
|
||||
#endif
|
||||
} else {
|
||||
codecList = gH264Codecs;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#ifdef MOZ_DIRECTSHOW
|
||||
DirectShowDecoder::GetSupportedCodecs(aType.GetMIMEType(), &codecList);
|
||||
#endif
|
||||
@ -451,11 +344,6 @@ CanHandleMediaType(const MediaContentType& aType,
|
||||
if (IsFlacSupportedType(aType.GetMIMEType())) {
|
||||
return CANPLAY_MAYBE;
|
||||
}
|
||||
#ifdef MOZ_OMX_DECODER
|
||||
if (IsOmxSupportedType(aType.GetMIMEType())) {
|
||||
return CANPLAY_MAYBE;
|
||||
}
|
||||
#endif
|
||||
#ifdef MOZ_DIRECTSHOW
|
||||
if (DirectShowDecoder::GetSupportedCodecs(aType.GetMIMEType(), nullptr)) {
|
||||
return CANPLAY_MAYBE;
|
||||
@ -547,27 +435,6 @@ InstantiateDecoder(const nsACString& aType,
|
||||
decoder = new FlacDecoder(aOwner);
|
||||
return decoder.forget();
|
||||
}
|
||||
#ifdef MOZ_OMX_DECODER
|
||||
if (IsOmxSupportedType(aType)) {
|
||||
// we are discouraging Web and App developers from using those formats in
|
||||
// gB2GOnlyTypes, thus we only allow them to be played on WebApps.
|
||||
if (IsB2GSupportOnlyType(aType)) {
|
||||
dom::HTMLMediaElement* element = aOwner->GetMediaElement();
|
||||
if (!element) {
|
||||
return nullptr;
|
||||
}
|
||||
nsIPrincipal* principal = element->NodePrincipal();
|
||||
if (!principal) {
|
||||
return nullptr;
|
||||
}
|
||||
if (principal->GetAppStatus() < nsIPrincipal::APP_STATUS_PRIVILEGED) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
decoder = new MediaOmxDecoder(aOwner);
|
||||
return decoder.forget();
|
||||
}
|
||||
#endif
|
||||
#ifdef MOZ_ANDROID_OMX
|
||||
if (MediaDecoder::IsAndroidMediaPluginEnabled() &&
|
||||
EnsureAndroidMediaPluginHost()->FindDecoder(aType, nullptr)) {
|
||||
@ -642,11 +509,6 @@ MediaDecoderReader* DecoderTraits::CreateReader(const nsACString& aType, Abstrac
|
||||
if (IsWaveType(aType)) {
|
||||
decoderReader = new WaveReader(aDecoder);
|
||||
} else
|
||||
#ifdef MOZ_OMX_DECODER
|
||||
if (IsOmxSupportedType(aType)) {
|
||||
decoderReader = new MediaOmxReader(aDecoder);
|
||||
} else
|
||||
#endif
|
||||
#ifdef MOZ_ANDROID_OMX
|
||||
if (MediaDecoder::IsAndroidMediaPluginEnabled() &&
|
||||
EnsureAndroidMediaPluginHost()->FindDecoder(aType, nullptr)) {
|
||||
@ -680,13 +542,6 @@ bool DecoderTraits::IsSupportedInVideoDocument(const nsACString& aType)
|
||||
|
||||
return
|
||||
IsOggSupportedType(aType) ||
|
||||
#ifdef MOZ_OMX_DECODER
|
||||
// We support the formats in gB2GOnlyTypes only inside WebApps on firefoxOS
|
||||
// but not in general web content. Ensure we dont create a VideoDocument
|
||||
// when accessing those format URLs directly.
|
||||
(IsOmxSupportedType(aType) &&
|
||||
!IsB2GSupportOnlyType(aType)) ||
|
||||
#endif
|
||||
IsWebMSupportedType(aType) ||
|
||||
#ifdef MOZ_ANDROID_OMX
|
||||
(MediaDecoder::IsAndroidMediaPluginEnabled() && IsAndroidMediaType(aType)) ||
|
||||
|
@ -6,10 +6,6 @@
|
||||
|
||||
#include "MediaData.h"
|
||||
#include "MediaInfo.h"
|
||||
#ifdef MOZ_OMX_DECODER
|
||||
#include "GrallocImages.h"
|
||||
#include "mozilla/layers/TextureClient.h"
|
||||
#endif
|
||||
#include "VideoUtils.h"
|
||||
#include "ImageContainer.h"
|
||||
|
||||
@ -384,52 +380,6 @@ VideoData::CreateFromImage(const VideoInfo& aInfo,
|
||||
return v.forget();
|
||||
}
|
||||
|
||||
#ifdef MOZ_OMX_DECODER
|
||||
/* static */
|
||||
already_AddRefed<VideoData>
|
||||
VideoData::CreateAndCopyIntoTextureClient(const VideoInfo& aInfo,
|
||||
int64_t aOffset,
|
||||
int64_t aTime,
|
||||
int64_t aDuration,
|
||||
mozilla::layers::TextureClient* aBuffer,
|
||||
bool aKeyframe,
|
||||
int64_t aTimecode,
|
||||
const IntRect& aPicture)
|
||||
{
|
||||
// The following situations could be triggered by invalid input
|
||||
if (aPicture.width <= 0 || aPicture.height <= 0) {
|
||||
NS_WARNING("Empty picture rect");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Ensure the picture size specified in the headers can be extracted out of
|
||||
// the frame we've been supplied without indexing out of bounds.
|
||||
CheckedUint32 xLimit = aPicture.x + CheckedUint32(aPicture.width);
|
||||
CheckedUint32 yLimit = aPicture.y + CheckedUint32(aPicture.height);
|
||||
if (!xLimit.isValid() || !yLimit.isValid())
|
||||
{
|
||||
// The specified picture dimensions can't be contained inside the video
|
||||
// frame, we'll stomp memory if we try to copy it. Fail.
|
||||
NS_WARNING("Overflowing picture rect");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RefPtr<VideoData> v(new VideoData(aOffset,
|
||||
aTime,
|
||||
aDuration,
|
||||
aKeyframe,
|
||||
aTimecode,
|
||||
aInfo.mDisplay,
|
||||
0));
|
||||
|
||||
RefPtr<layers::GrallocImage> image = new layers::GrallocImage();
|
||||
image->AdoptData(aBuffer, aPicture.Size());
|
||||
v->mImage = image;
|
||||
|
||||
return v.forget();
|
||||
}
|
||||
#endif // MOZ_OMX_DECODER
|
||||
|
||||
MediaRawData::MediaRawData()
|
||||
: MediaData(RAW_DATA, 0)
|
||||
, mCrypto(mCryptoInternal)
|
||||
|
@ -1736,14 +1736,6 @@ MediaDecoder::IsWebMEnabled()
|
||||
return Preferences::GetBool("media.webm.enabled");
|
||||
}
|
||||
|
||||
#ifdef MOZ_OMX_DECODER
|
||||
bool
|
||||
MediaDecoder::IsOmxEnabled()
|
||||
{
|
||||
return Preferences::GetBool("media.omx.enabled", false);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef MOZ_ANDROID_OMX
|
||||
bool
|
||||
MediaDecoder::IsAndroidMediaPluginEnabled()
|
||||
|
@ -456,10 +456,6 @@ private:
|
||||
static bool IsWaveEnabled();
|
||||
static bool IsWebMEnabled();
|
||||
|
||||
#ifdef MOZ_OMX_DECODER
|
||||
static bool IsOmxEnabled();
|
||||
#endif
|
||||
|
||||
#ifdef MOZ_ANDROID_OMX
|
||||
static bool IsAndroidMediaPluginEnabled();
|
||||
#endif
|
||||
|
@ -1328,17 +1328,6 @@ MediaRecorder::IsTypeSupported(const nsAString& aMIMEType)
|
||||
codeclist = gWebMVideoEncoderCodecs;
|
||||
}
|
||||
#endif
|
||||
#ifdef MOZ_OMX_ENCODER
|
||||
// We're working on MP4 encoder support for desktop
|
||||
else if (mimeType.EqualsLiteral(VIDEO_MP4) ||
|
||||
mimeType.EqualsLiteral(AUDIO_3GPP) ||
|
||||
mimeType.EqualsLiteral(AUDIO_3GPP2)) {
|
||||
if (MediaEncoder::IsOMXEncoderEnabled()) {
|
||||
// XXX check codecs for MP4/3GPP
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// codecs don't matter if we don't support the container
|
||||
if (!codeclist) {
|
||||
|
@ -19,10 +19,6 @@
|
||||
#include "VP8TrackEncoder.h"
|
||||
#include "WebMWriter.h"
|
||||
#endif
|
||||
#ifdef MOZ_OMX_ENCODER
|
||||
#include "OmxTrackEncoder.h"
|
||||
#include "ISOMediaWriter.h"
|
||||
#endif
|
||||
|
||||
#ifdef LOG
|
||||
#undef LOG
|
||||
@ -180,37 +176,6 @@ MediaEncoder::CreateEncoder(const nsAString& aMIMEType, uint32_t aAudioBitrate,
|
||||
mimeType = NS_LITERAL_STRING(VIDEO_WEBM);
|
||||
}
|
||||
#endif //MOZ_WEBM_ENCODER
|
||||
#ifdef MOZ_OMX_ENCODER
|
||||
else if (MediaEncoder::IsOMXEncoderEnabled() &&
|
||||
(aMIMEType.EqualsLiteral(VIDEO_MP4) ||
|
||||
(aTrackTypes & ContainerWriter::CREATE_VIDEO_TRACK))) {
|
||||
if (aTrackTypes & ContainerWriter::CREATE_AUDIO_TRACK) {
|
||||
audioEncoder = new OmxAACAudioTrackEncoder();
|
||||
NS_ENSURE_TRUE(audioEncoder, nullptr);
|
||||
}
|
||||
videoEncoder = new OmxVideoTrackEncoder(aTrackRate);
|
||||
writer = new ISOMediaWriter(aTrackTypes);
|
||||
NS_ENSURE_TRUE(writer, nullptr);
|
||||
NS_ENSURE_TRUE(videoEncoder, nullptr);
|
||||
mimeType = NS_LITERAL_STRING(VIDEO_MP4);
|
||||
} else if (MediaEncoder::IsOMXEncoderEnabled() &&
|
||||
(aMIMEType.EqualsLiteral(AUDIO_3GPP))) {
|
||||
audioEncoder = new OmxAMRAudioTrackEncoder();
|
||||
NS_ENSURE_TRUE(audioEncoder, nullptr);
|
||||
|
||||
writer = new ISOMediaWriter(aTrackTypes, ISOMediaWriter::TYPE_FRAG_3GP);
|
||||
NS_ENSURE_TRUE(writer, nullptr);
|
||||
mimeType = NS_LITERAL_STRING(AUDIO_3GPP);
|
||||
} else if (MediaEncoder::IsOMXEncoderEnabled() &&
|
||||
(aMIMEType.EqualsLiteral(AUDIO_3GPP2))) {
|
||||
audioEncoder = new OmxEVRCAudioTrackEncoder();
|
||||
NS_ENSURE_TRUE(audioEncoder, nullptr);
|
||||
|
||||
writer = new ISOMediaWriter(aTrackTypes, ISOMediaWriter::TYPE_FRAG_3G2);
|
||||
NS_ENSURE_TRUE(writer, nullptr);
|
||||
mimeType = NS_LITERAL_STRING(AUDIO_3GPP2) ;
|
||||
}
|
||||
#endif // MOZ_OMX_ENCODER
|
||||
else if (MediaDecoder::IsOggEnabled() && MediaDecoder::IsOpusEnabled() &&
|
||||
(aMIMEType.EqualsLiteral(AUDIO_OGG) ||
|
||||
(aTrackTypes & ContainerWriter::CREATE_AUDIO_TRACK))) {
|
||||
@ -419,14 +384,6 @@ MediaEncoder::IsWebMEncoderEnabled()
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef MOZ_OMX_ENCODER
|
||||
bool
|
||||
MediaEncoder::IsOMXEncoderEnabled()
|
||||
{
|
||||
return Preferences::GetBool("media.encoder.omx.enabled");
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* SizeOfExcludingThis measures memory being used by the Media Encoder.
|
||||
* Currently it measures the size of the Encoder buffer and memory occupied
|
||||
|
@ -217,10 +217,6 @@ public :
|
||||
static bool IsWebMEncoderEnabled();
|
||||
#endif
|
||||
|
||||
#ifdef MOZ_OMX_ENCODER
|
||||
static bool IsOMXEncoderEnabled();
|
||||
#endif
|
||||
|
||||
MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf)
|
||||
/*
|
||||
* Measure the size of the buffer, and memory occupied by mAudioEncoder
|
||||
|
@ -1,384 +0,0 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/
|
||||
/* 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 "OmxTrackEncoder.h"
|
||||
#include "OMXCodecWrapper.h"
|
||||
#include "VideoUtils.h"
|
||||
#include "ISOTrackMetadata.h"
|
||||
#include "GeckoProfiler.h"
|
||||
|
||||
#ifdef MOZ_WIDGET_GONK
|
||||
#include <android/log.h>
|
||||
#define OMX_LOG(args...) \
|
||||
do { \
|
||||
__android_log_print(ANDROID_LOG_INFO, "OmxTrackEncoder", ##args); \
|
||||
} while (0)
|
||||
#else
|
||||
#define OMX_LOG(args, ...)
|
||||
#endif
|
||||
|
||||
using namespace android;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
#define ENCODER_CONFIG_FRAME_RATE 30 // fps
|
||||
#define GET_ENCODED_VIDEO_FRAME_TIMEOUT 100000 // microseconds
|
||||
|
||||
OmxVideoTrackEncoder::OmxVideoTrackEncoder(TrackRate aTrackRate)
|
||||
: VideoTrackEncoder(aTrackRate)
|
||||
{}
|
||||
|
||||
OmxVideoTrackEncoder::~OmxVideoTrackEncoder()
|
||||
{}
|
||||
|
||||
nsresult
|
||||
OmxVideoTrackEncoder::Init(int aWidth, int aHeight, int aDisplayWidth,
|
||||
int aDisplayHeight)
|
||||
{
|
||||
mFrameWidth = aWidth;
|
||||
mFrameHeight = aHeight;
|
||||
mDisplayWidth = aDisplayWidth;
|
||||
mDisplayHeight = aDisplayHeight;
|
||||
|
||||
mEncoder = OMXCodecWrapper::CreateAVCEncoder();
|
||||
NS_ENSURE_TRUE(mEncoder, NS_ERROR_FAILURE);
|
||||
|
||||
nsresult rv = mEncoder->Configure(mFrameWidth, mFrameHeight,
|
||||
ENCODER_CONFIG_FRAME_RATE);
|
||||
|
||||
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
|
||||
mInitialized = (rv == NS_OK);
|
||||
|
||||
mReentrantMonitor.NotifyAll();
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
already_AddRefed<TrackMetadataBase>
|
||||
OmxVideoTrackEncoder::GetMetadata()
|
||||
{
|
||||
PROFILER_LABEL("OmxVideoTrackEncoder", "GetMetadata",
|
||||
js::ProfileEntry::Category::OTHER);
|
||||
{
|
||||
// Wait if mEncoder is not initialized nor is being canceled.
|
||||
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
|
||||
while (!mCanceled && !mInitialized) {
|
||||
mReentrantMonitor.Wait();
|
||||
}
|
||||
}
|
||||
|
||||
if (mCanceled || mEncodingComplete) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RefPtr<AVCTrackMetadata> meta = new AVCTrackMetadata();
|
||||
meta->mWidth = mFrameWidth;
|
||||
meta->mHeight = mFrameHeight;
|
||||
meta->mDisplayWidth = mDisplayWidth;
|
||||
meta->mDisplayHeight = mDisplayHeight;
|
||||
meta->mFrameRate = ENCODER_CONFIG_FRAME_RATE;
|
||||
return meta.forget();
|
||||
}
|
||||
|
||||
nsresult
|
||||
OmxVideoTrackEncoder::GetEncodedTrack(EncodedFrameContainer& aData)
|
||||
{
|
||||
PROFILER_LABEL("OmxVideoTrackEncoder", "GetEncodedTrack",
|
||||
js::ProfileEntry::Category::OTHER);
|
||||
VideoSegment segment;
|
||||
{
|
||||
// Move all the samples from mRawSegment to segment. We only hold the
|
||||
// monitor in this block.
|
||||
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
|
||||
|
||||
// Wait if mEncoder is not initialized nor is being canceled.
|
||||
while (!mCanceled && (!mInitialized ||
|
||||
(mRawSegment.GetDuration() == 0 && !mEndOfStream))) {
|
||||
mReentrantMonitor.Wait();
|
||||
}
|
||||
|
||||
if (mCanceled || mEncodingComplete) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
segment.AppendFrom(&mRawSegment);
|
||||
}
|
||||
|
||||
nsresult rv;
|
||||
// Start queuing raw frames to the input buffers of OMXCodecWrapper.
|
||||
VideoSegment::ChunkIterator iter(segment);
|
||||
while (!iter.IsEnded()) {
|
||||
VideoChunk chunk = *iter;
|
||||
|
||||
uint64_t totalDurationUs = mTotalFrameDuration * USECS_PER_S / mTrackRate;
|
||||
layers::Image* img = (chunk.IsNull() || chunk.mFrame.GetForceBlack()) ?
|
||||
nullptr : chunk.mFrame.GetImage();
|
||||
rv = mEncoder->Encode(img, mFrameWidth, mFrameHeight, totalDurationUs);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
mTotalFrameDuration += chunk.GetDuration();
|
||||
|
||||
iter.Next();
|
||||
}
|
||||
|
||||
// Send the EOS signal to OMXCodecWrapper.
|
||||
if (mEndOfStream && iter.IsEnded() && !mEosSetInEncoder) {
|
||||
uint64_t totalDurationUs = mTotalFrameDuration * USECS_PER_S / mTrackRate;
|
||||
layers::Image* img = (!mLastFrame.GetImage() || mLastFrame.GetForceBlack())
|
||||
? nullptr : mLastFrame.GetImage();
|
||||
rv = mEncoder->Encode(img, mFrameWidth, mFrameHeight, totalDurationUs,
|
||||
OMXCodecWrapper::BUFFER_EOS, &mEosSetInEncoder);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
|
||||
// Dequeue an encoded frame from the output buffers of OMXCodecWrapper.
|
||||
nsTArray<uint8_t> buffer;
|
||||
int outFlags = 0;
|
||||
int64_t outTimeStampUs = 0;
|
||||
rv = mEncoder->GetNextEncodedFrame(&buffer, &outTimeStampUs, &outFlags,
|
||||
GET_ENCODED_VIDEO_FRAME_TIMEOUT);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
if (!buffer.IsEmpty()) {
|
||||
RefPtr<EncodedFrame> videoData = new EncodedFrame();
|
||||
if (outFlags & OMXCodecWrapper::BUFFER_CODEC_CONFIG) {
|
||||
videoData->SetFrameType(EncodedFrame::AVC_CSD);
|
||||
} else {
|
||||
videoData->SetFrameType((outFlags & OMXCodecWrapper::BUFFER_SYNC_FRAME) ?
|
||||
EncodedFrame::AVC_I_FRAME : EncodedFrame::AVC_P_FRAME);
|
||||
}
|
||||
videoData->SwapInFrameData(buffer);
|
||||
videoData->SetTimeStamp(outTimeStampUs);
|
||||
aData.AppendEncodedFrame(videoData);
|
||||
}
|
||||
|
||||
if (outFlags & OMXCodecWrapper::BUFFER_EOS) {
|
||||
mEncodingComplete = true;
|
||||
OMX_LOG("Done encoding video.");
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
OmxAudioTrackEncoder::OmxAudioTrackEncoder()
|
||||
: AudioTrackEncoder()
|
||||
{}
|
||||
|
||||
OmxAudioTrackEncoder::~OmxAudioTrackEncoder()
|
||||
{}
|
||||
|
||||
nsresult
|
||||
OmxAudioTrackEncoder::AppendEncodedFrames(EncodedFrameContainer& aContainer)
|
||||
{
|
||||
nsTArray<uint8_t> frameData;
|
||||
int outFlags = 0;
|
||||
int64_t outTimeUs = -1;
|
||||
|
||||
nsresult rv = mEncoder->GetNextEncodedFrame(&frameData, &outTimeUs, &outFlags,
|
||||
3000); // wait up to 3ms
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
if (!frameData.IsEmpty() || outFlags & OMXCodecWrapper::BUFFER_EOS) { // Some hw codec may send out EOS with an empty frame
|
||||
bool isCSD = false;
|
||||
if (outFlags & OMXCodecWrapper::BUFFER_CODEC_CONFIG) { // codec specific data
|
||||
isCSD = true;
|
||||
} else if (outFlags & OMXCodecWrapper::BUFFER_EOS) { // last frame
|
||||
mEncodingComplete = true;
|
||||
}
|
||||
|
||||
RefPtr<EncodedFrame> audiodata = new EncodedFrame();
|
||||
if (mEncoder->GetCodecType() == OMXCodecWrapper::AAC_ENC) {
|
||||
audiodata->SetFrameType(isCSD ?
|
||||
EncodedFrame::AAC_CSD : EncodedFrame::AAC_AUDIO_FRAME);
|
||||
} else if (mEncoder->GetCodecType() == OMXCodecWrapper::AMR_NB_ENC){
|
||||
audiodata->SetFrameType(isCSD ?
|
||||
EncodedFrame::AMR_AUDIO_CSD : EncodedFrame::AMR_AUDIO_FRAME);
|
||||
} else if (mEncoder->GetCodecType() == OMXCodecWrapper::EVRC_ENC){
|
||||
audiodata->SetFrameType(isCSD ?
|
||||
EncodedFrame::EVRC_AUDIO_CSD : EncodedFrame::EVRC_AUDIO_FRAME);
|
||||
} else {
|
||||
MOZ_ASSERT(false, "audio codec not supported");
|
||||
}
|
||||
audiodata->SetTimeStamp(outTimeUs);
|
||||
audiodata->SwapInFrameData(frameData);
|
||||
aContainer.AppendEncodedFrame(audiodata);
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
OmxAudioTrackEncoder::GetEncodedTrack(EncodedFrameContainer& aData)
|
||||
{
|
||||
PROFILER_LABEL("OmxAACAudioTrackEncoder", "GetEncodedTrack",
|
||||
js::ProfileEntry::Category::OTHER);
|
||||
AudioSegment segment;
|
||||
bool EOS;
|
||||
// Move all the samples from mRawSegment to segment. We only hold
|
||||
// the monitor in this block.
|
||||
{
|
||||
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
|
||||
|
||||
// Wait if mEncoder is not initialized nor canceled.
|
||||
while (!mInitialized && !mCanceled) {
|
||||
mReentrantMonitor.Wait();
|
||||
}
|
||||
|
||||
if (mCanceled || mEncodingComplete) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
segment.AppendFrom(&mRawSegment);
|
||||
EOS = mEndOfStream;
|
||||
}
|
||||
|
||||
nsresult rv;
|
||||
if (segment.GetDuration() == 0) {
|
||||
// Notify EOS at least once, even if segment is empty.
|
||||
if (EOS && !mEosSetInEncoder) {
|
||||
rv = mEncoder->Encode(segment, OMXCodecWrapper::BUFFER_EOS,
|
||||
&mEosSetInEncoder);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
// Nothing to encode but encoder could still have encoded data for earlier
|
||||
// input.
|
||||
return AppendEncodedFrames(aData);
|
||||
}
|
||||
|
||||
// OMX encoder has limited input buffers only so we have to feed input and get
|
||||
// output more than once if there are too many samples pending in segment.
|
||||
while (segment.GetDuration() > 0) {
|
||||
rv = mEncoder->Encode(segment, 0);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
rv = AppendEncodedFrames(aData);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
OmxAACAudioTrackEncoder::Init(int aChannels, int aSamplingRate)
|
||||
{
|
||||
mChannels = aChannels;
|
||||
mSamplingRate = aSamplingRate;
|
||||
|
||||
mEncoder = OMXCodecWrapper::CreateAACEncoder();
|
||||
NS_ENSURE_TRUE(mEncoder, NS_ERROR_FAILURE);
|
||||
|
||||
nsresult rv = mEncoder->Configure(mChannels, mSamplingRate, mSamplingRate);
|
||||
|
||||
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
|
||||
mInitialized = (rv == NS_OK);
|
||||
|
||||
mReentrantMonitor.NotifyAll();
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
already_AddRefed<TrackMetadataBase>
|
||||
OmxAACAudioTrackEncoder::GetMetadata()
|
||||
{
|
||||
PROFILER_LABEL("OmxAACAudioTrackEncoder", "GetMetadata",
|
||||
js::ProfileEntry::Category::OTHER);
|
||||
{
|
||||
// Wait if mEncoder is not initialized nor is being canceled.
|
||||
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
|
||||
while (!mCanceled && !mInitialized) {
|
||||
mReentrantMonitor.Wait();
|
||||
}
|
||||
}
|
||||
|
||||
if (mCanceled || mEncodingComplete) {
|
||||
return nullptr;
|
||||
}
|
||||
RefPtr<AACTrackMetadata> meta = new AACTrackMetadata();
|
||||
meta->mChannels = mChannels;
|
||||
meta->mSampleRate = mSamplingRate;
|
||||
meta->mFrameSize = OMXCodecWrapper::kAACFrameSize;
|
||||
meta->mFrameDuration = OMXCodecWrapper::kAACFrameDuration;
|
||||
return meta.forget();
|
||||
}
|
||||
|
||||
nsresult
|
||||
OmxAMRAudioTrackEncoder::Init(int aChannels, int aSamplingRate)
|
||||
{
|
||||
mChannels = aChannels;
|
||||
mSamplingRate = aSamplingRate;
|
||||
|
||||
mEncoder = OMXCodecWrapper::CreateAMRNBEncoder();
|
||||
NS_ENSURE_TRUE(mEncoder, NS_ERROR_FAILURE);
|
||||
|
||||
nsresult rv = mEncoder->Configure(mChannels, mSamplingRate, AMR_NB_SAMPLERATE);
|
||||
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
|
||||
mInitialized = (rv == NS_OK);
|
||||
|
||||
mReentrantMonitor.NotifyAll();
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
already_AddRefed<TrackMetadataBase>
|
||||
OmxAMRAudioTrackEncoder::GetMetadata()
|
||||
{
|
||||
PROFILER_LABEL("OmxAMRAudioTrackEncoder", "GetMetadata",
|
||||
js::ProfileEntry::Category::OTHER);
|
||||
{
|
||||
// Wait if mEncoder is not initialized nor is being canceled.
|
||||
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
|
||||
while (!mCanceled && !mInitialized) {
|
||||
mReentrantMonitor.Wait();
|
||||
}
|
||||
}
|
||||
|
||||
if (mCanceled || mEncodingComplete) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RefPtr<AMRTrackMetadata> meta = new AMRTrackMetadata();
|
||||
return meta.forget();
|
||||
}
|
||||
|
||||
nsresult
|
||||
OmxEVRCAudioTrackEncoder::Init(int aChannels, int aSamplingRate)
|
||||
{
|
||||
mChannels = aChannels;
|
||||
mSamplingRate = aSamplingRate;
|
||||
|
||||
mEncoder = OMXCodecWrapper::CreateEVRCEncoder();
|
||||
NS_ENSURE_TRUE(mEncoder, NS_ERROR_FAILURE);
|
||||
|
||||
nsresult rv = mEncoder->Configure(mChannels, mSamplingRate, EVRC_SAMPLERATE);
|
||||
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
|
||||
mInitialized = (rv == NS_OK);
|
||||
|
||||
mReentrantMonitor.NotifyAll();
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
already_AddRefed<TrackMetadataBase>
|
||||
OmxEVRCAudioTrackEncoder::GetMetadata()
|
||||
{
|
||||
PROFILER_LABEL("OmxEVRCAudioTrackEncoder", "GetMetadata",
|
||||
js::ProfileEntry::Category::OTHER);
|
||||
{
|
||||
// Wait if mEncoder is not initialized nor is being canceled.
|
||||
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
|
||||
while (!mCanceled && !mInitialized) {
|
||||
mReentrantMonitor.Wait();
|
||||
}
|
||||
}
|
||||
|
||||
if (mCanceled || mEncodingComplete) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RefPtr<EVRCTrackMetadata> meta = new EVRCTrackMetadata();
|
||||
meta->mChannels = mChannels;
|
||||
return meta.forget();
|
||||
}
|
||||
|
||||
}
|
@ -1,110 +0,0 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/
|
||||
/* 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/. */
|
||||
|
||||
#ifndef OmxTrackEncoder_h_
|
||||
#define OmxTrackEncoder_h_
|
||||
|
||||
#include "nsAutoPtr.h"
|
||||
#include "TrackEncoder.h"
|
||||
|
||||
namespace android {
|
||||
class OMXVideoEncoder;
|
||||
class OMXAudioEncoder;
|
||||
}
|
||||
|
||||
/**
|
||||
* There are two major classes defined in file OmxTrackEncoder;
|
||||
* OmxVideoTrackEncoder and OmxAudioTrackEncoder, the video and audio track
|
||||
* encoder for media type AVC/H.264 and AAC. OMXCodecWrapper wraps and controls
|
||||
* an instance of MediaCodec, defined in libstagefright, runs on Android Jelly
|
||||
* Bean platform.
|
||||
*/
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class OmxVideoTrackEncoder: public VideoTrackEncoder
|
||||
{
|
||||
public:
|
||||
explicit OmxVideoTrackEncoder(TrackRate aTrackRate);
|
||||
~OmxVideoTrackEncoder();
|
||||
|
||||
already_AddRefed<TrackMetadataBase> GetMetadata() override;
|
||||
|
||||
nsresult GetEncodedTrack(EncodedFrameContainer& aData) override;
|
||||
|
||||
protected:
|
||||
nsresult Init(int aWidth, int aHeight,
|
||||
int aDisplayWidth, int aDisplayHeight) override;
|
||||
|
||||
private:
|
||||
nsAutoPtr<android::OMXVideoEncoder> mEncoder;
|
||||
};
|
||||
|
||||
class OmxAudioTrackEncoder : public AudioTrackEncoder
|
||||
{
|
||||
public:
|
||||
OmxAudioTrackEncoder();
|
||||
~OmxAudioTrackEncoder();
|
||||
|
||||
already_AddRefed<TrackMetadataBase> GetMetadata() = 0;
|
||||
|
||||
nsresult GetEncodedTrack(EncodedFrameContainer& aData) override;
|
||||
|
||||
protected:
|
||||
nsresult Init(int aChannels, int aSamplingRate) = 0;
|
||||
|
||||
// Append encoded frames to aContainer.
|
||||
nsresult AppendEncodedFrames(EncodedFrameContainer& aContainer);
|
||||
|
||||
nsAutoPtr<android::OMXAudioEncoder> mEncoder;
|
||||
};
|
||||
|
||||
class OmxAACAudioTrackEncoder final : public OmxAudioTrackEncoder
|
||||
{
|
||||
public:
|
||||
OmxAACAudioTrackEncoder()
|
||||
: OmxAudioTrackEncoder()
|
||||
{}
|
||||
|
||||
already_AddRefed<TrackMetadataBase> GetMetadata() override;
|
||||
|
||||
protected:
|
||||
nsresult Init(int aChannels, int aSamplingRate) override;
|
||||
};
|
||||
|
||||
class OmxAMRAudioTrackEncoder final : public OmxAudioTrackEncoder
|
||||
{
|
||||
public:
|
||||
OmxAMRAudioTrackEncoder()
|
||||
: OmxAudioTrackEncoder()
|
||||
{}
|
||||
|
||||
enum {
|
||||
AMR_NB_SAMPLERATE = 8000,
|
||||
};
|
||||
already_AddRefed<TrackMetadataBase> GetMetadata() override;
|
||||
|
||||
protected:
|
||||
nsresult Init(int aChannels, int aSamplingRate) override;
|
||||
};
|
||||
|
||||
class OmxEVRCAudioTrackEncoder final : public OmxAudioTrackEncoder
|
||||
{
|
||||
public:
|
||||
OmxEVRCAudioTrackEncoder()
|
||||
: OmxAudioTrackEncoder()
|
||||
{}
|
||||
|
||||
enum {
|
||||
EVRC_SAMPLERATE = 8000,
|
||||
};
|
||||
already_AddRefed<TrackMetadataBase> GetMetadata() override;
|
||||
|
||||
protected:
|
||||
nsresult Init(int aChannels, int aSamplingRate) override;
|
||||
};
|
||||
|
||||
}
|
||||
#endif
|
@ -25,10 +25,6 @@ UNIFIED_SOURCES += [
|
||||
'TrackEncoder.cpp',
|
||||
]
|
||||
|
||||
if CONFIG['MOZ_OMX_ENCODER']:
|
||||
EXPORTS += ['OmxTrackEncoder.h']
|
||||
UNIFIED_SOURCES += ['OmxTrackEncoder.cpp']
|
||||
|
||||
if CONFIG['MOZ_WEBM_ENCODER']:
|
||||
EXPORTS += ['VP8TrackEncoder.h',
|
||||
]
|
||||
|
@ -54,9 +54,6 @@ if CONFIG['MOZ_FMP4']:
|
||||
if CONFIG['MOZ_WEBRTC']:
|
||||
DIRS += ['bridge']
|
||||
|
||||
if CONFIG['MOZ_OMX_DECODER']:
|
||||
DIRS += ['omx']
|
||||
|
||||
TEST_DIRS += [
|
||||
'compiledtest',
|
||||
'gtest',
|
||||
@ -323,9 +320,6 @@ if CONFIG['OS_TARGET'] == 'WINNT':
|
||||
else:
|
||||
DEFINES['WEBRTC_POSIX'] = True
|
||||
|
||||
if CONFIG['MOZ_OMX_DECODER']:
|
||||
DEFINES['MOZ_OMX_DECODER'] = True
|
||||
|
||||
if CONFIG['ANDROID_VERSION'] > '15':
|
||||
DEFINES['MOZ_OMX_WEBM_DECODER'] = True
|
||||
|
||||
|
@ -1,758 +0,0 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
||||
/*
|
||||
* Copyright (c) 2014 The Linux Foundation. All rights reserved.
|
||||
* Copyright (C) 2009 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.
|
||||
*/
|
||||
|
||||
#include "AudioOffloadPlayer.h"
|
||||
#include "nsComponentManagerUtils.h"
|
||||
#include "nsITimer.h"
|
||||
#include "MediaOmxCommonDecoder.h"
|
||||
#include "mozilla/dom/HTMLMediaElement.h"
|
||||
#include "VideoUtils.h"
|
||||
#include "mozilla/dom/power/PowerManagerService.h"
|
||||
#include "mozilla/dom/WakeLock.h"
|
||||
|
||||
#include <binder/IPCThreadState.h>
|
||||
#include <stagefright/foundation/ADebug.h>
|
||||
#include <stagefright/foundation/ALooper.h>
|
||||
#include <stagefright/MediaDefs.h>
|
||||
#include <stagefright/MediaErrors.h>
|
||||
#include <stagefright/MediaSource.h>
|
||||
#include <stagefright/MetaData.h>
|
||||
#include <stagefright/Utils.h>
|
||||
#include <AudioTrack.h>
|
||||
#include <AudioSystem.h>
|
||||
#include <AudioParameter.h>
|
||||
#include <hardware/audio.h>
|
||||
|
||||
using namespace android;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
LazyLogModule gAudioOffloadPlayerLog("AudioOffloadPlayer");
|
||||
#define AUDIO_OFFLOAD_LOG(type, msg) \
|
||||
MOZ_LOG(gAudioOffloadPlayerLog, type, msg)
|
||||
|
||||
// maximum time in paused state when offloading audio decompression.
|
||||
// When elapsed, the GonkAudioSink is destroyed to allow the audio DSP to power down.
|
||||
static const uint64_t OFFLOAD_PAUSE_MAX_MSECS = 60000ll;
|
||||
|
||||
AudioOffloadPlayer::AudioOffloadPlayer(MediaOmxCommonDecoder* aObserver) :
|
||||
mStarted(false),
|
||||
mPlaying(false),
|
||||
mReachedEOS(false),
|
||||
mIsElementVisible(true),
|
||||
mSampleRate(0),
|
||||
mStartPosUs(0),
|
||||
mPositionTimeMediaUs(-1),
|
||||
mInputBuffer(nullptr)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
CHECK(aObserver);
|
||||
#if ANDROID_VERSION >= 21
|
||||
mSessionId = AudioSystem::newAudioUniqueId();
|
||||
AudioSystem::acquireAudioSessionId(mSessionId, -1);
|
||||
#else
|
||||
mSessionId = AudioSystem::newAudioSessionId();
|
||||
AudioSystem::acquireAudioSessionId(mSessionId);
|
||||
#endif
|
||||
mAudioSink = new AudioOutput(mSessionId,
|
||||
IPCThreadState::self()->getCallingUid());
|
||||
|
||||
nsCOMPtr<nsIThread> thread;
|
||||
MOZ_ALWAYS_SUCCEEDS(NS_GetMainThread(getter_AddRefs(thread)));
|
||||
mPositionChanged = mOnPositionChanged.Connect(
|
||||
thread, aObserver, &MediaOmxCommonDecoder::NotifyOffloadPlayerPositionChanged);
|
||||
mPlaybackEnded = mOnPlaybackEnded.Connect(
|
||||
thread, aObserver, &MediaDecoder::PlaybackEnded);
|
||||
mPlayerTearDown = mOnPlayerTearDown.Connect(
|
||||
thread, aObserver, &MediaOmxCommonDecoder::AudioOffloadTearDown);
|
||||
mSeekingStarted = mOnSeekingStarted.Connect(
|
||||
thread, aObserver, &MediaDecoder::SeekingStarted);
|
||||
}
|
||||
|
||||
AudioOffloadPlayer::~AudioOffloadPlayer()
|
||||
{
|
||||
Reset();
|
||||
#if ANDROID_VERSION >= 21
|
||||
AudioSystem::releaseAudioSessionId(mSessionId, -1);
|
||||
#else
|
||||
AudioSystem::releaseAudioSessionId(mSessionId);
|
||||
#endif
|
||||
|
||||
// Disconnect the listeners to prevent notifications from reaching
|
||||
// the MediaOmxCommonDecoder object after shutdown.
|
||||
mPositionChanged.Disconnect();
|
||||
mPlaybackEnded.Disconnect();
|
||||
mPlayerTearDown.Disconnect();
|
||||
mSeekingStarted.Disconnect();
|
||||
}
|
||||
|
||||
void AudioOffloadPlayer::SetSource(const sp<MediaSource> &aSource)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
CHECK(!mSource.get());
|
||||
|
||||
mSource = aSource;
|
||||
}
|
||||
|
||||
status_t AudioOffloadPlayer::Start(bool aSourceAlreadyStarted)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
CHECK(!mStarted);
|
||||
CHECK(mSource.get());
|
||||
|
||||
status_t err;
|
||||
CHECK(mAudioSink.get());
|
||||
|
||||
if (!aSourceAlreadyStarted) {
|
||||
err = mSource->start();
|
||||
|
||||
if (err != OK) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
sp<MetaData> format = mSource->getFormat();
|
||||
const char* mime;
|
||||
int avgBitRate = -1;
|
||||
int32_t channelMask;
|
||||
int32_t numChannels;
|
||||
int64_t durationUs = -1;
|
||||
audio_format_t audioFormat = AUDIO_FORMAT_PCM_16_BIT;
|
||||
uint32_t flags = AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD;
|
||||
audio_offload_info_t offloadInfo = AUDIO_INFO_INITIALIZER;
|
||||
|
||||
CHECK(format->findCString(kKeyMIMEType, &mime));
|
||||
CHECK(format->findInt32(kKeySampleRate, &mSampleRate));
|
||||
CHECK(format->findInt32(kKeyChannelCount, &numChannels));
|
||||
format->findInt32(kKeyBitRate, &avgBitRate);
|
||||
format->findInt64(kKeyDuration, &durationUs);
|
||||
|
||||
if(!format->findInt32(kKeyChannelMask, &channelMask)) {
|
||||
channelMask = CHANNEL_MASK_USE_CHANNEL_ORDER;
|
||||
}
|
||||
|
||||
if (mapMimeToAudioFormat(audioFormat, mime) != OK) {
|
||||
AUDIO_OFFLOAD_LOG(LogLevel::Error, ("Couldn't map mime type \"%s\" to a valid "
|
||||
"AudioSystem::audio_format", mime));
|
||||
audioFormat = AUDIO_FORMAT_INVALID;
|
||||
}
|
||||
|
||||
offloadInfo.duration_us = durationUs;
|
||||
offloadInfo.sample_rate = mSampleRate;
|
||||
offloadInfo.channel_mask = channelMask;
|
||||
offloadInfo.format = audioFormat;
|
||||
offloadInfo.stream_type = AUDIO_STREAM_MUSIC;
|
||||
offloadInfo.bit_rate = avgBitRate;
|
||||
offloadInfo.has_video = false;
|
||||
offloadInfo.is_streaming = false;
|
||||
|
||||
AUDIO_OFFLOAD_LOG(LogLevel::Debug, ("isOffloadSupported: SR=%u, CM=0x%x, "
|
||||
"Format=0x%x, StreamType=%d, BitRate=%u, duration=%lld us, has_video=%d",
|
||||
offloadInfo.sample_rate, offloadInfo.channel_mask, offloadInfo.format,
|
||||
offloadInfo.stream_type, offloadInfo.bit_rate, offloadInfo.duration_us,
|
||||
offloadInfo.has_video));
|
||||
|
||||
err = mAudioSink->Open(mSampleRate,
|
||||
numChannels,
|
||||
channelMask,
|
||||
audioFormat,
|
||||
&AudioOffloadPlayer::AudioSinkCallback,
|
||||
this,
|
||||
(audio_output_flags_t) flags,
|
||||
&offloadInfo);
|
||||
if (err == OK) {
|
||||
// If the playback is offloaded to h/w we pass the
|
||||
// HAL some metadata information
|
||||
// We don't want to do this for PCM because it will be going
|
||||
// through the AudioFlinger mixer before reaching the hardware
|
||||
SendMetaDataToHal(mAudioSink, format);
|
||||
}
|
||||
mStarted = true;
|
||||
mPlaying = false;
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
status_t AudioOffloadPlayer::ChangeState(MediaDecoder::PlayState aState)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
mPlayState = aState;
|
||||
|
||||
switch (mPlayState) {
|
||||
case MediaDecoder::PLAY_STATE_PLAYING: {
|
||||
status_t err = Play();
|
||||
if (err != OK) {
|
||||
return err;
|
||||
}
|
||||
StartTimeUpdate();
|
||||
} break;
|
||||
|
||||
case MediaDecoder::PLAY_STATE_PAUSED:
|
||||
case MediaDecoder::PLAY_STATE_SHUTDOWN:
|
||||
// Just pause here during play state shutdown as well to stop playing
|
||||
// offload track immediately. Resources will be freed by
|
||||
// MediaOmxCommonDecoder
|
||||
Pause();
|
||||
break;
|
||||
|
||||
case MediaDecoder::PLAY_STATE_ENDED:
|
||||
Pause(true);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
|
||||
static void ResetCallback(nsITimer* aTimer, void* aClosure)
|
||||
{
|
||||
AUDIO_OFFLOAD_LOG(LogLevel::Debug, ("%s", __FUNCTION__));
|
||||
AudioOffloadPlayer* player = static_cast<AudioOffloadPlayer*>(aClosure);
|
||||
if (player) {
|
||||
player->Reset();
|
||||
}
|
||||
}
|
||||
|
||||
void AudioOffloadPlayer::Pause(bool aPlayPendingSamples)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
if (mStarted) {
|
||||
CHECK(mAudioSink.get());
|
||||
WakeLockCreate();
|
||||
|
||||
if (aPlayPendingSamples) {
|
||||
mAudioSink->Stop();
|
||||
} else {
|
||||
mAudioSink->Pause();
|
||||
}
|
||||
mPlaying = false;
|
||||
}
|
||||
|
||||
if (mResetTimer) {
|
||||
return;
|
||||
}
|
||||
mResetTimer = do_CreateInstance("@mozilla.org/timer;1");
|
||||
mResetTimer->InitWithFuncCallback(ResetCallback,
|
||||
this,
|
||||
OFFLOAD_PAUSE_MAX_MSECS,
|
||||
nsITimer::TYPE_ONE_SHOT);
|
||||
}
|
||||
|
||||
status_t AudioOffloadPlayer::Play()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
if (mResetTimer) {
|
||||
mResetTimer->Cancel();
|
||||
mResetTimer = nullptr;
|
||||
WakeLockRelease();
|
||||
}
|
||||
|
||||
status_t err = OK;
|
||||
|
||||
if (!mStarted) {
|
||||
// Last pause timed out and offloaded audio sink was reset. Start it again
|
||||
err = Start(false);
|
||||
if (err != OK) {
|
||||
return err;
|
||||
}
|
||||
// Seek to last play position only when there was no seek during last pause
|
||||
android::Mutex::Autolock autoLock(mLock);
|
||||
if (!mSeekTarget.IsValid()) {
|
||||
mSeekTarget = SeekTarget(mPositionTimeMediaUs,
|
||||
SeekTarget::Accurate,
|
||||
MediaDecoderEventVisibility::Suppressed);
|
||||
DoSeek();
|
||||
}
|
||||
}
|
||||
|
||||
if (!mPlaying) {
|
||||
CHECK(mAudioSink.get());
|
||||
err = mAudioSink->Start();
|
||||
if (err == OK) {
|
||||
mPlaying = true;
|
||||
}
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
void AudioOffloadPlayer::Reset()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
if (!mStarted) {
|
||||
return;
|
||||
}
|
||||
|
||||
CHECK(mAudioSink.get());
|
||||
|
||||
AUDIO_OFFLOAD_LOG(LogLevel::Debug, ("reset: mPlaying=%d mReachedEOS=%d",
|
||||
mPlaying, mReachedEOS));
|
||||
|
||||
mAudioSink->Stop();
|
||||
// If we're closing and have reached EOS, we don't want to flush
|
||||
// the track because if it is offloaded there could be a small
|
||||
// amount of residual data in the hardware buffer which we must
|
||||
// play to give gapless playback.
|
||||
// But if we're resetting when paused or before we've reached EOS
|
||||
// we can't be doing a gapless playback and there could be a large
|
||||
// amount of data queued in the hardware if the track is offloaded,
|
||||
// so we must flush to prevent a track switch being delayed playing
|
||||
// the buffered data that we don't want now
|
||||
if (!mPlaying || !mReachedEOS) {
|
||||
mAudioSink->Flush();
|
||||
}
|
||||
|
||||
mAudioSink->Close();
|
||||
// Make sure to release any buffer we hold onto so that the
|
||||
// source is able to stop().
|
||||
|
||||
if (mInputBuffer) {
|
||||
AUDIO_OFFLOAD_LOG(LogLevel::Debug, ("Releasing input buffer"));
|
||||
|
||||
mInputBuffer->release();
|
||||
mInputBuffer = nullptr;
|
||||
}
|
||||
mSource->stop();
|
||||
|
||||
IPCThreadState::self()->flushCommands();
|
||||
StopTimeUpdate();
|
||||
|
||||
mReachedEOS = false;
|
||||
mStarted = false;
|
||||
mPlaying = false;
|
||||
mStartPosUs = 0;
|
||||
|
||||
WakeLockRelease();
|
||||
}
|
||||
|
||||
RefPtr<MediaDecoder::SeekPromise> AudioOffloadPlayer::Seek(SeekTarget aTarget)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
android::Mutex::Autolock autoLock(mLock);
|
||||
|
||||
mSeekPromise.RejectIfExists(true, __func__);
|
||||
mSeekTarget = aTarget;
|
||||
RefPtr<MediaDecoder::SeekPromise> p = mSeekPromise.Ensure(__func__);
|
||||
DoSeek();
|
||||
return p;
|
||||
}
|
||||
|
||||
status_t AudioOffloadPlayer::DoSeek()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(mSeekTarget.IsValid());
|
||||
CHECK(mAudioSink.get());
|
||||
|
||||
AUDIO_OFFLOAD_LOG(LogLevel::Debug,
|
||||
("DoSeek ( %lld )", mSeekTarget.GetTime().ToMicroseconds()));
|
||||
|
||||
mReachedEOS = false;
|
||||
mPositionTimeMediaUs = -1;
|
||||
mStartPosUs = mSeekTarget.GetTime().ToMicroseconds();
|
||||
|
||||
if (!mSeekPromise.IsEmpty() &&
|
||||
mSeekTarget.mEventVisibility == MediaDecoderEventVisibility::Observable) {
|
||||
mOnSeekingStarted.Notify();
|
||||
}
|
||||
|
||||
if (mPlaying) {
|
||||
mAudioSink->Pause();
|
||||
mAudioSink->Flush();
|
||||
mAudioSink->Start();
|
||||
|
||||
} else {
|
||||
if (mStarted) {
|
||||
mAudioSink->Flush();
|
||||
}
|
||||
|
||||
if (!mSeekPromise.IsEmpty()) {
|
||||
AUDIO_OFFLOAD_LOG(LogLevel::Debug, ("Fake seek complete during pause"));
|
||||
// We do not reset mSeekTarget here.
|
||||
MediaDecoder::SeekResolveValue val(mReachedEOS, mSeekTarget.mEventVisibility);
|
||||
mSeekPromise.Resolve(val, __func__);
|
||||
}
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
int64_t AudioOffloadPlayer::GetMediaTimeUs()
|
||||
{
|
||||
android::Mutex::Autolock autoLock(mLock);
|
||||
|
||||
int64_t playPosition = 0;
|
||||
if (mSeekTarget.IsValid()) {
|
||||
return mSeekTarget.GetTime().ToMicroseconds();
|
||||
}
|
||||
if (!mStarted) {
|
||||
return mPositionTimeMediaUs;
|
||||
}
|
||||
|
||||
playPosition = GetOutputPlayPositionUs_l();
|
||||
if (!mReachedEOS) {
|
||||
mPositionTimeMediaUs = playPosition;
|
||||
}
|
||||
|
||||
return mPositionTimeMediaUs;
|
||||
}
|
||||
|
||||
int64_t AudioOffloadPlayer::GetOutputPlayPositionUs_l() const
|
||||
{
|
||||
CHECK(mAudioSink.get());
|
||||
uint32_t playedSamples = 0;
|
||||
|
||||
mAudioSink->GetPosition(&playedSamples);
|
||||
|
||||
const int64_t playedUs = (static_cast<int64_t>(playedSamples) * 1000000 ) /
|
||||
mSampleRate;
|
||||
|
||||
// HAL position is relative to the first buffer we sent at mStartPosUs
|
||||
const int64_t renderedDuration = mStartPosUs + playedUs;
|
||||
return renderedDuration;
|
||||
}
|
||||
|
||||
void AudioOffloadPlayer::NotifyAudioEOS()
|
||||
{
|
||||
android::Mutex::Autolock autoLock(mLock);
|
||||
// We do not reset mSeekTarget here.
|
||||
if (!mSeekPromise.IsEmpty()) {
|
||||
MediaDecoder::SeekResolveValue val(mReachedEOS, mSeekTarget.mEventVisibility);
|
||||
mSeekPromise.Resolve(val, __func__);
|
||||
}
|
||||
mOnPlaybackEnded.Notify();
|
||||
}
|
||||
|
||||
void AudioOffloadPlayer::NotifyPositionChanged()
|
||||
{
|
||||
mOnPositionChanged.Notify();
|
||||
}
|
||||
|
||||
void AudioOffloadPlayer::NotifyAudioTearDown()
|
||||
{
|
||||
// Fallback to state machine.
|
||||
// state machine's seeks will be done with
|
||||
// MediaDecoderEventVisibility::Suppressed.
|
||||
android::Mutex::Autolock autoLock(mLock);
|
||||
// We do not reset mSeekTarget here.
|
||||
if (!mSeekPromise.IsEmpty()) {
|
||||
MediaDecoder::SeekResolveValue val(mReachedEOS, mSeekTarget.mEventVisibility);
|
||||
mSeekPromise.Resolve(val, __func__);
|
||||
}
|
||||
mOnPlayerTearDown.Notify();
|
||||
}
|
||||
|
||||
// static
|
||||
size_t AudioOffloadPlayer::AudioSinkCallback(GonkAudioSink* aAudioSink,
|
||||
void* aBuffer,
|
||||
size_t aSize,
|
||||
void* aCookie,
|
||||
GonkAudioSink::cb_event_t aEvent)
|
||||
{
|
||||
AudioOffloadPlayer* me = (AudioOffloadPlayer*) aCookie;
|
||||
|
||||
switch (aEvent) {
|
||||
|
||||
case GonkAudioSink::CB_EVENT_FILL_BUFFER:
|
||||
AUDIO_OFFLOAD_LOG(LogLevel::Debug, ("Notify Audio position changed"));
|
||||
me->NotifyPositionChanged();
|
||||
return me->FillBuffer(aBuffer, aSize);
|
||||
|
||||
case GonkAudioSink::CB_EVENT_STREAM_END:
|
||||
AUDIO_OFFLOAD_LOG(LogLevel::Debug, ("Notify Audio EOS"));
|
||||
me->mReachedEOS = true;
|
||||
me->NotifyAudioEOS();
|
||||
break;
|
||||
|
||||
case GonkAudioSink::CB_EVENT_TEAR_DOWN:
|
||||
AUDIO_OFFLOAD_LOG(LogLevel::Debug, ("Notify Tear down event"));
|
||||
me->NotifyAudioTearDown();
|
||||
break;
|
||||
|
||||
default:
|
||||
AUDIO_OFFLOAD_LOG(LogLevel::Error, ("Unknown event %d from audio sink",
|
||||
aEvent));
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t AudioOffloadPlayer::FillBuffer(void* aData, size_t aSize)
|
||||
{
|
||||
CHECK(mAudioSink.get());
|
||||
|
||||
if (mReachedEOS) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t sizeDone = 0;
|
||||
size_t sizeRemaining = aSize;
|
||||
int64_t seekTimeUs = -1;
|
||||
while (sizeRemaining > 0) {
|
||||
MediaSource::ReadOptions options;
|
||||
bool refreshSeekTime = false;
|
||||
{
|
||||
android::Mutex::Autolock autoLock(mLock);
|
||||
|
||||
if (mSeekTarget.IsValid()) {
|
||||
seekTimeUs = mSeekTarget.GetTime().ToMicroseconds();
|
||||
options.setSeekTo(seekTimeUs);
|
||||
refreshSeekTime = true;
|
||||
|
||||
if (mInputBuffer) {
|
||||
mInputBuffer->release();
|
||||
mInputBuffer = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!mInputBuffer) {
|
||||
status_t err;
|
||||
err = mSource->read(&mInputBuffer, &options);
|
||||
|
||||
CHECK((!err && mInputBuffer) || (err && !mInputBuffer));
|
||||
|
||||
android::Mutex::Autolock autoLock(mLock);
|
||||
|
||||
if (err != OK) {
|
||||
if (mSeekTarget.IsValid()) {
|
||||
mSeekTarget.Reset();
|
||||
}
|
||||
AUDIO_OFFLOAD_LOG(LogLevel::Error, ("Error while reading media source %d "
|
||||
"Ok to receive EOS error at end", err));
|
||||
if (!mReachedEOS) {
|
||||
// After seek there is a possible race condition if
|
||||
// OffloadThread is observing state_stopping_1 before
|
||||
// framesReady() > 0. Ensure sink stop is called
|
||||
// after last buffer is released. This ensures the
|
||||
// partial buffer is written to the driver before
|
||||
// stopping one is observed.The drawback is that
|
||||
// there will be an unnecessary call to the parser
|
||||
// after parser signalled EOS.
|
||||
if (sizeDone > 0) {
|
||||
AUDIO_OFFLOAD_LOG(LogLevel::Debug, ("send Partial buffer down"));
|
||||
AUDIO_OFFLOAD_LOG(LogLevel::Debug, ("skip calling stop till next"
|
||||
" fillBuffer"));
|
||||
break;
|
||||
}
|
||||
// no more buffers to push - stop() and wait for STREAM_END
|
||||
// don't set mReachedEOS until stream end received
|
||||
mAudioSink->Stop();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if(mInputBuffer->range_length() != 0) {
|
||||
CHECK(mInputBuffer->meta_data()->findInt64(
|
||||
kKeyTime, &mPositionTimeMediaUs));
|
||||
}
|
||||
|
||||
if (mSeekTarget.IsValid() &&
|
||||
seekTimeUs == mSeekTarget.GetTime().ToMicroseconds()) {
|
||||
MOZ_ASSERT(mSeekTarget.IsValid());
|
||||
mSeekTarget.Reset();
|
||||
if (!mSeekPromise.IsEmpty()) {
|
||||
AUDIO_OFFLOAD_LOG(LogLevel::Debug, ("FillBuffer posting SEEK_COMPLETE"));
|
||||
MediaDecoder::SeekResolveValue val(mReachedEOS, mSeekTarget.mEventVisibility);
|
||||
mSeekPromise.Resolve(val, __func__);
|
||||
}
|
||||
} else if (mSeekTarget.IsValid()) {
|
||||
AUDIO_OFFLOAD_LOG(LogLevel::Debug, ("seek is updated during unlocking mLock"));
|
||||
}
|
||||
|
||||
if (refreshSeekTime) {
|
||||
NotifyPositionChanged();
|
||||
|
||||
// need to adjust the mStartPosUs for offload decoding since parser
|
||||
// might not be able to get the exact seek time requested.
|
||||
mStartPosUs = mPositionTimeMediaUs;
|
||||
AUDIO_OFFLOAD_LOG(LogLevel::Debug, ("Adjust seek time to: %.2f",
|
||||
mStartPosUs / 1E6));
|
||||
}
|
||||
}
|
||||
|
||||
if (mInputBuffer->range_length() == 0) {
|
||||
mInputBuffer->release();
|
||||
mInputBuffer = nullptr;
|
||||
continue;
|
||||
}
|
||||
|
||||
size_t copy = sizeRemaining;
|
||||
if (copy > mInputBuffer->range_length()) {
|
||||
copy = mInputBuffer->range_length();
|
||||
}
|
||||
|
||||
memcpy((char *)aData + sizeDone,
|
||||
(const char *)mInputBuffer->data() + mInputBuffer->range_offset(),
|
||||
copy);
|
||||
|
||||
mInputBuffer->set_range(mInputBuffer->range_offset() + copy,
|
||||
mInputBuffer->range_length() - copy);
|
||||
|
||||
sizeDone += copy;
|
||||
sizeRemaining -= copy;
|
||||
}
|
||||
return sizeDone;
|
||||
}
|
||||
|
||||
void AudioOffloadPlayer::SetElementVisibility(bool aIsVisible)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
mIsElementVisible = aIsVisible;
|
||||
if (mIsElementVisible) {
|
||||
AUDIO_OFFLOAD_LOG(LogLevel::Debug, ("Element is visible. Start time update"));
|
||||
StartTimeUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
static void TimeUpdateCallback(nsITimer* aTimer, void* aClosure)
|
||||
{
|
||||
AudioOffloadPlayer* player = static_cast<AudioOffloadPlayer*>(aClosure);
|
||||
player->TimeUpdate();
|
||||
}
|
||||
|
||||
void AudioOffloadPlayer::TimeUpdate()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
TimeStamp now = TimeStamp::Now();
|
||||
|
||||
// If TIMEUPDATE_MS has passed since the last fire update event fired, fire
|
||||
// another timeupdate event.
|
||||
if ((mLastFireUpdateTime.IsNull() ||
|
||||
now - mLastFireUpdateTime >=
|
||||
TimeDuration::FromMilliseconds(TIMEUPDATE_MS))) {
|
||||
mLastFireUpdateTime = now;
|
||||
NotifyPositionChanged();
|
||||
}
|
||||
|
||||
if (mPlayState != MediaDecoder::PLAY_STATE_PLAYING || !mIsElementVisible) {
|
||||
StopTimeUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
nsresult AudioOffloadPlayer::StartTimeUpdate()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
if (mTimeUpdateTimer) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
mTimeUpdateTimer = do_CreateInstance("@mozilla.org/timer;1");
|
||||
return mTimeUpdateTimer->InitWithFuncCallback(TimeUpdateCallback,
|
||||
this,
|
||||
TIMEUPDATE_MS,
|
||||
nsITimer::TYPE_REPEATING_SLACK);
|
||||
}
|
||||
|
||||
nsresult AudioOffloadPlayer::StopTimeUpdate()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
if (!mTimeUpdateTimer) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult rv = mTimeUpdateTimer->Cancel();
|
||||
mTimeUpdateTimer = nullptr;
|
||||
return rv;
|
||||
}
|
||||
|
||||
MediaDecoderOwner::NextFrameStatus AudioOffloadPlayer::GetNextFrameStatus()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
if (mSeekTarget.IsValid()) {
|
||||
return MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE_SEEKING;
|
||||
} else if (mPlayState == MediaDecoder::PLAY_STATE_ENDED) {
|
||||
return MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE;
|
||||
} else {
|
||||
return MediaDecoderOwner::NEXT_FRAME_AVAILABLE;
|
||||
}
|
||||
}
|
||||
|
||||
void AudioOffloadPlayer::SendMetaDataToHal(sp<GonkAudioSink>& aSink,
|
||||
const sp<MetaData>& aMeta)
|
||||
{
|
||||
int32_t sampleRate = 0;
|
||||
int32_t bitRate = 0;
|
||||
int32_t channelMask = 0;
|
||||
int32_t delaySamples = 0;
|
||||
int32_t paddingSamples = 0;
|
||||
CHECK(aSink.get());
|
||||
|
||||
AudioParameter param = AudioParameter();
|
||||
|
||||
if (aMeta->findInt32(kKeySampleRate, &sampleRate)) {
|
||||
param.addInt(String8(AUDIO_OFFLOAD_CODEC_SAMPLE_RATE), sampleRate);
|
||||
}
|
||||
if (aMeta->findInt32(kKeyChannelMask, &channelMask)) {
|
||||
param.addInt(String8(AUDIO_OFFLOAD_CODEC_NUM_CHANNEL), channelMask);
|
||||
}
|
||||
if (aMeta->findInt32(kKeyBitRate, &bitRate)) {
|
||||
param.addInt(String8(AUDIO_OFFLOAD_CODEC_AVG_BIT_RATE), bitRate);
|
||||
}
|
||||
if (aMeta->findInt32(kKeyEncoderDelay, &delaySamples)) {
|
||||
param.addInt(String8(AUDIO_OFFLOAD_CODEC_DELAY_SAMPLES), delaySamples);
|
||||
}
|
||||
if (aMeta->findInt32(kKeyEncoderPadding, &paddingSamples)) {
|
||||
param.addInt(String8(AUDIO_OFFLOAD_CODEC_PADDING_SAMPLES), paddingSamples);
|
||||
}
|
||||
|
||||
AUDIO_OFFLOAD_LOG(LogLevel::Debug, ("SendMetaDataToHal: bitRate %d,"
|
||||
" sampleRate %d, chanMask %d, delaySample %d, paddingSample %d", bitRate,
|
||||
sampleRate, channelMask, delaySamples, paddingSamples));
|
||||
|
||||
aSink->SetParameters(param.toString());
|
||||
return;
|
||||
}
|
||||
|
||||
void AudioOffloadPlayer::SetVolume(double aVolume)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
CHECK(mAudioSink.get());
|
||||
mAudioSink->SetVolume((float) aVolume);
|
||||
}
|
||||
|
||||
void AudioOffloadPlayer::WakeLockCreate()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
AUDIO_OFFLOAD_LOG(LogLevel::Debug, ("%s", __FUNCTION__));
|
||||
if (!mWakeLock) {
|
||||
RefPtr<dom::power::PowerManagerService> pmService =
|
||||
dom::power::PowerManagerService::GetInstance();
|
||||
NS_ENSURE_TRUE_VOID(pmService);
|
||||
|
||||
ErrorResult rv;
|
||||
mWakeLock = pmService->NewWakeLock(NS_LITERAL_STRING("cpu"), nullptr, rv);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioOffloadPlayer::WakeLockRelease()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
AUDIO_OFFLOAD_LOG(LogLevel::Debug, ("%s", __FUNCTION__));
|
||||
if (mWakeLock) {
|
||||
ErrorResult rv;
|
||||
mWakeLock->Unlock(rv);
|
||||
mWakeLock = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
@ -1,273 +0,0 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
||||
/*
|
||||
* Copyright (c) 2014 The Linux Foundation. All rights reserved.
|
||||
* Copyright (C) 2009 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.
|
||||
*/
|
||||
|
||||
#ifndef AUDIO_OFFLOAD_PLAYER_H_
|
||||
#define AUDIO_OFFLOAD_PLAYER_H_
|
||||
|
||||
#include <stagefright/MediaBuffer.h>
|
||||
#include <stagefright/MediaSource.h>
|
||||
#include <stagefright/TimeSource.h>
|
||||
#include <utils/threads.h>
|
||||
#include <utils/RefBase.h>
|
||||
|
||||
#include "AudioOutput.h"
|
||||
#include "AudioOffloadPlayerBase.h"
|
||||
#include "MediaDecoderOwner.h"
|
||||
#include "MediaEventSource.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
namespace dom {
|
||||
class WakeLock;
|
||||
}
|
||||
|
||||
/**
|
||||
* AudioOffloadPlayer adds support for audio tunneling to a digital signal
|
||||
* processor (DSP) in the device chipset. With tunneling, audio decoding is
|
||||
* off-loaded to the DSP, waking the application processor less often and using
|
||||
* less battery
|
||||
*
|
||||
* This depends on offloading capability provided by Android KK AudioTrack class
|
||||
*
|
||||
* Audio playback is based on pull mechanism, whenever audio sink needs
|
||||
* data, FillBuffer() will read data from compressed audio source and provide
|
||||
* it to the sink
|
||||
*
|
||||
* Also this class passes state changes (play/pause/seek) from
|
||||
* MediaOmxCommonDecoder to GonkAudioSink as well as provide GonkAudioSink status
|
||||
* (position changed, playback ended, seek complete, audio tear down) back to
|
||||
* MediaOmxCommonDecoder
|
||||
*
|
||||
* It acts as a bridge between MediaOmxCommonDecoder and GonkAudioSink during
|
||||
* offload playback
|
||||
*/
|
||||
|
||||
class MediaOmxCommonDecoder;
|
||||
|
||||
class AudioOffloadPlayer : public AudioOffloadPlayerBase
|
||||
{
|
||||
typedef android::Mutex Mutex;
|
||||
typedef android::MetaData MetaData;
|
||||
typedef android::status_t status_t;
|
||||
typedef android::AudioTrack AudioTrack;
|
||||
typedef android::MediaBuffer MediaBuffer;
|
||||
typedef android::MediaSource MediaSource;
|
||||
|
||||
public:
|
||||
enum {
|
||||
REACHED_EOS,
|
||||
SEEK_COMPLETE
|
||||
};
|
||||
|
||||
AudioOffloadPlayer(MediaOmxCommonDecoder* aDecoder);
|
||||
|
||||
~AudioOffloadPlayer();
|
||||
|
||||
// Caller retains ownership of "aSource".
|
||||
void SetSource(const android::sp<MediaSource> &aSource) override;
|
||||
|
||||
// Start the source if it's not already started and open the GonkAudioSink to
|
||||
// create an offloaded audio track
|
||||
status_t Start(bool aSourceAlreadyStarted = false) override;
|
||||
|
||||
status_t ChangeState(MediaDecoder::PlayState aState) override;
|
||||
|
||||
void SetVolume(double aVolume) override;
|
||||
|
||||
int64_t GetMediaTimeUs() override;
|
||||
|
||||
// To update progress bar when the element is visible
|
||||
void SetElementVisibility(bool aIsVisible) override;
|
||||
|
||||
// Update ready state based on current play state. Not checking data
|
||||
// availability since offloading is currently done only when whole compressed
|
||||
// data is available
|
||||
MediaDecoderOwner::NextFrameStatus GetNextFrameStatus() override;
|
||||
|
||||
RefPtr<MediaDecoder::SeekPromise> Seek(SeekTarget aTarget) override;
|
||||
|
||||
void TimeUpdate();
|
||||
|
||||
// Close the audio sink, stop time updates, frees the input buffers
|
||||
void Reset();
|
||||
|
||||
private:
|
||||
// Set when audio source is started and GonkAudioSink is initialized
|
||||
// Used only in main thread
|
||||
bool mStarted;
|
||||
|
||||
// Set when audio sink is started. i.e. playback started
|
||||
// Used only in main thread
|
||||
bool mPlaying;
|
||||
|
||||
// Once playback reached end of stream (last ~100ms), position provided by DSP
|
||||
// may be reset/corrupted. This bool is used to avoid that.
|
||||
// Used in main thread and offload callback thread, protected by Mutex
|
||||
// mLock
|
||||
bool mReachedEOS;
|
||||
|
||||
// Set when the HTML Audio Element is visible to the user.
|
||||
// Used only in main thread
|
||||
bool mIsElementVisible;
|
||||
|
||||
// Session id given by Android::AudioSystem and used while creating audio sink
|
||||
// Used only in main thread
|
||||
int mSessionId;
|
||||
|
||||
// Sample rate of current audio track. Used only in main thread
|
||||
int mSampleRate;
|
||||
|
||||
// After seeking, positions returned by offlaoded tracks (DSP) will be
|
||||
// relative to the seeked position. And seeked position may be slightly
|
||||
// different than given mSeekTimeUs, if audio source cannot find a frame at
|
||||
// that position. Store seeked position in mStartPosUs and provide
|
||||
// mStartPosUs + GetPosition() (i.e. absolute position) to
|
||||
// MediaOmxCommonDecoder
|
||||
// Used in main thread and offload callback thread, protected by Mutex
|
||||
// mLock
|
||||
int64_t mStartPosUs;
|
||||
|
||||
// The target of current seek when there is a request to seek
|
||||
// Used in main thread and offload callback thread, protected by Mutex
|
||||
// mLock
|
||||
SeekTarget mSeekTarget;
|
||||
|
||||
// MozPromise of current seek.
|
||||
// Used in main thread and offload callback thread, protected by Mutex
|
||||
// mLock
|
||||
MozPromiseHolder<MediaDecoder::SeekPromise> mSeekPromise;
|
||||
|
||||
// Positions obtained from offlaoded tracks (DSP)
|
||||
// Used in main thread and offload callback thread, protected by Mutex
|
||||
// mLock
|
||||
int64_t mPositionTimeMediaUs;
|
||||
|
||||
// State obtained from MediaOmxCommonDecoder. Used only in main thread
|
||||
MediaDecoder::PlayState mPlayState;
|
||||
|
||||
// Protect accessing audio position related variables between main thread and
|
||||
// offload callback thread
|
||||
Mutex mLock;
|
||||
|
||||
// Compressed audio source.
|
||||
// Used in main thread first and later in offload callback thread
|
||||
android::sp<MediaSource> mSource;
|
||||
|
||||
// Audio sink wrapper to access offloaded audio tracks
|
||||
// Used in main thread and offload callback thread
|
||||
// Race conditions are protected in underlying Android::AudioTrack class
|
||||
android::sp<GonkAudioSink> mAudioSink;
|
||||
|
||||
// Buffer used to get date from audio source. Used in offload callback thread
|
||||
MediaBuffer* mInputBuffer;
|
||||
|
||||
TimeStamp mLastFireUpdateTime;
|
||||
|
||||
// Timer to trigger position changed events
|
||||
nsCOMPtr<nsITimer> mTimeUpdateTimer;
|
||||
|
||||
// Timer to reset GonkAudioSink when audio is paused for OFFLOAD_PAUSE_MAX_USECS.
|
||||
// It is triggered in Pause() and canceled when there is a Play() within
|
||||
// OFFLOAD_PAUSE_MAX_USECS. Used only from main thread so no lock is needed.
|
||||
nsCOMPtr<nsITimer> mResetTimer;
|
||||
|
||||
// To avoid device suspend when mResetTimer is going to be triggered.
|
||||
// Used only from main thread so no lock is needed.
|
||||
RefPtr<mozilla::dom::WakeLock> mWakeLock;
|
||||
|
||||
MediaEventProducer<void> mOnPositionChanged;
|
||||
MediaEventProducer<void> mOnPlaybackEnded;
|
||||
MediaEventProducer<void> mOnPlayerTearDown;
|
||||
MediaEventProducer<void> mOnSeekingStarted;
|
||||
MediaEventListener mPositionChanged;
|
||||
MediaEventListener mPlaybackEnded;
|
||||
MediaEventListener mPlayerTearDown;
|
||||
MediaEventListener mSeekingStarted;
|
||||
|
||||
// Provide the playback position in microseconds from total number of
|
||||
// frames played by audio track
|
||||
int64_t GetOutputPlayPositionUs_l() const;
|
||||
|
||||
// Fill the buffer given by audio sink with data from compressed audio
|
||||
// source. Also handles the seek by seeking audio source and stop the sink in
|
||||
// case of error
|
||||
size_t FillBuffer(void *aData, size_t aSize);
|
||||
|
||||
// Called by GonkAudioSink when it needs data, to notify EOS or tear down event
|
||||
static size_t AudioSinkCallback(GonkAudioSink *aAudioSink,
|
||||
void *aData,
|
||||
size_t aSize,
|
||||
void *aMe,
|
||||
GonkAudioSink::cb_event_t aEvent);
|
||||
|
||||
bool IsSeeking();
|
||||
|
||||
// Set mSeekTarget to the given position and restart the sink. Actual seek
|
||||
// happens in FillBuffer(). If mSeekPromise is not empty, send
|
||||
// SeekingStarted event always and SeekingStopped event when the play state is
|
||||
// paused to MediaDecoder.
|
||||
// When decoding and playing happens separately, if there is a seek during
|
||||
// pause, we can decode and keep data ready.
|
||||
// In case of offload player, no way to seek during pause. So just fake that
|
||||
// seek is done.
|
||||
status_t DoSeek();
|
||||
|
||||
// Start/Resume the audio sink so that callback will start being called to get
|
||||
// compressed data
|
||||
status_t Play();
|
||||
|
||||
// Stop the audio sink if we need to play till we drain the current buffer.
|
||||
// or Pause the sink in case we should stop playing immediately
|
||||
void Pause(bool aPlayPendingSamples = false);
|
||||
|
||||
// When audio is offloaded, application processor wakes up less frequently
|
||||
// (>1sec) But when Player UI is visible we need to update progress bar
|
||||
// atleast once in 250ms. Start a timer when player UI becomes visible or
|
||||
// audio starts playing to send UpdateLogicalPosition events once in 250ms.
|
||||
// Stop the timer when UI goes invisible or play state is not playing.
|
||||
// Also make sure timer functions are always called from main thread
|
||||
nsresult StartTimeUpdate();
|
||||
nsresult StopTimeUpdate();
|
||||
|
||||
void WakeLockCreate();
|
||||
void WakeLockRelease();
|
||||
|
||||
// Notify end of stream by sending PlaybackEnded event to observer
|
||||
// (i.e.MediaDecoder)
|
||||
void NotifyAudioEOS();
|
||||
|
||||
// Notify position changed event by sending UpdateLogicalPosition event to
|
||||
// observer
|
||||
void NotifyPositionChanged();
|
||||
|
||||
// Offloaded audio track is invalidated due to usecase change. Notify
|
||||
// MediaDecoder to re-evaluate offloading options
|
||||
void NotifyAudioTearDown();
|
||||
|
||||
// Send information from MetaData to the HAL via GonkAudioSink
|
||||
void SendMetaDataToHal(android::sp<GonkAudioSink>& aSink,
|
||||
const android::sp<MetaData>& aMeta);
|
||||
|
||||
AudioOffloadPlayer(const AudioOffloadPlayer &);
|
||||
AudioOffloadPlayer &operator=(const AudioOffloadPlayer &);
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // AUDIO_OFFLOAD_PLAYER_H_
|
@ -1,75 +0,0 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
||||
/*
|
||||
* Copyright (c) 2014 The Linux Foundation. All rights reserved.
|
||||
* Copyright (C) 2009 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.
|
||||
*/
|
||||
|
||||
#ifndef AUDIO_OFFLOAD_PLAYER_BASE_H_
|
||||
#define AUDIO_OFFLOAD_PLAYER_BASE_H_
|
||||
|
||||
#include "MediaDecoder.h"
|
||||
#include "MediaDecoderOwner.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
/**
|
||||
* AudioOffloadPlayer interface class which has funtions used by MediaOmxDecoder
|
||||
* This is to reduce the dependency of AudioOffloadPlayer in MediaOmxDecoder
|
||||
*/
|
||||
class AudioOffloadPlayerBase
|
||||
{
|
||||
typedef android::status_t status_t;
|
||||
typedef android::MediaSource MediaSource;
|
||||
|
||||
public:
|
||||
virtual ~AudioOffloadPlayerBase() {};
|
||||
|
||||
// Caller retains ownership of "aSource".
|
||||
virtual void SetSource(const android::sp<MediaSource> &aSource) {}
|
||||
|
||||
// Start the source if it's not already started and open the AudioSink to
|
||||
// create an offloaded audio track
|
||||
virtual status_t Start(bool aSourceAlreadyStarted = false)
|
||||
{
|
||||
return android::NO_INIT;
|
||||
}
|
||||
|
||||
virtual status_t ChangeState(MediaDecoder::PlayState aState)
|
||||
{
|
||||
return android::NO_INIT;
|
||||
}
|
||||
|
||||
virtual void SetVolume(double aVolume) {}
|
||||
|
||||
virtual int64_t GetMediaTimeUs() { return 0; }
|
||||
|
||||
// To update progress bar when the element is visible
|
||||
virtual void SetElementVisibility(bool aIsVisible) {}
|
||||
|
||||
// Update ready state based on current play state. Not checking data
|
||||
// availability since offloading is currently done only when whole compressed
|
||||
// data is available
|
||||
virtual MediaDecoderOwner::NextFrameStatus GetNextFrameStatus()
|
||||
{
|
||||
return MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE;
|
||||
}
|
||||
|
||||
virtual RefPtr<MediaDecoder::SeekPromise> Seek(SeekTarget aTarget) = 0;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // AUDIO_OFFLOAD_PLAYER_BASE_H_
|
@ -1,235 +0,0 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
||||
/*
|
||||
* Copyright (c) 2014 The Linux Foundation. All rights reserved.
|
||||
* Copyright (C) 2008 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.
|
||||
*/
|
||||
|
||||
#include <stagefright/foundation/ADebug.h>
|
||||
#include "AudioOutput.h"
|
||||
|
||||
#include "mozilla/Logging.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
extern LazyLogModule gAudioOffloadPlayerLog;
|
||||
#define AUDIO_OFFLOAD_LOG(type, msg) \
|
||||
MOZ_LOG(gAudioOffloadPlayerLog, type, msg)
|
||||
|
||||
using namespace android;
|
||||
|
||||
AudioOutput::AudioOutput(int aSessionId, int aUid) :
|
||||
mCallbackCookie(nullptr),
|
||||
mCallback(nullptr),
|
||||
mCallbackData(nullptr),
|
||||
mUid(aUid),
|
||||
mSessionId(aSessionId)
|
||||
{
|
||||
}
|
||||
|
||||
AudioOutput::~AudioOutput()
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
ssize_t AudioOutput::FrameSize() const
|
||||
{
|
||||
if (!mTrack.get()) {
|
||||
return NO_INIT;
|
||||
}
|
||||
return mTrack->frameSize();
|
||||
}
|
||||
|
||||
status_t AudioOutput::GetPosition(uint32_t *aPosition) const
|
||||
{
|
||||
if (!mTrack.get()) {
|
||||
return NO_INIT;
|
||||
}
|
||||
return mTrack->getPosition(aPosition);
|
||||
}
|
||||
|
||||
status_t AudioOutput::SetVolume(float aVolume) const
|
||||
{
|
||||
if (!mTrack.get()) {
|
||||
return NO_INIT;
|
||||
}
|
||||
return mTrack->setVolume(aVolume);
|
||||
}
|
||||
|
||||
status_t AudioOutput::SetParameters(const String8& aKeyValuePairs)
|
||||
{
|
||||
if (!mTrack.get()) {
|
||||
return NO_INIT;
|
||||
}
|
||||
return mTrack->setParameters(aKeyValuePairs);
|
||||
}
|
||||
|
||||
status_t AudioOutput::Open(uint32_t aSampleRate,
|
||||
int aChannelCount,
|
||||
audio_channel_mask_t aChannelMask,
|
||||
audio_format_t aFormat,
|
||||
AudioCallback aCb,
|
||||
void* aCookie,
|
||||
audio_output_flags_t aFlags,
|
||||
const audio_offload_info_t *aOffloadInfo)
|
||||
{
|
||||
mCallback = aCb;
|
||||
mCallbackCookie = aCookie;
|
||||
|
||||
if (((aFlags & AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD) == 0) || !aCb ||
|
||||
!aOffloadInfo) {
|
||||
return BAD_VALUE;
|
||||
}
|
||||
|
||||
AUDIO_OFFLOAD_LOG(LogLevel::Debug, ("open(%u, %d, 0x%x, 0x%x, %d 0x%x)",
|
||||
aSampleRate, aChannelCount, aChannelMask, aFormat, mSessionId, aFlags));
|
||||
|
||||
if (aChannelMask == CHANNEL_MASK_USE_CHANNEL_ORDER) {
|
||||
aChannelMask = audio_channel_out_mask_from_count(aChannelCount);
|
||||
if (0 == aChannelMask) {
|
||||
AUDIO_OFFLOAD_LOG(LogLevel::Error, ("open() error, can\'t derive mask for"
|
||||
" %d audio channels", aChannelCount));
|
||||
return NO_INIT;
|
||||
}
|
||||
}
|
||||
|
||||
sp<AudioTrack> t;
|
||||
CallbackData* newcbd = new CallbackData(this);
|
||||
|
||||
t = new AudioTrack(
|
||||
AUDIO_STREAM_MUSIC,
|
||||
aSampleRate,
|
||||
aFormat,
|
||||
aChannelMask,
|
||||
0, // Offloaded tracks will get frame count from AudioFlinger
|
||||
aFlags,
|
||||
CallbackWrapper,
|
||||
newcbd,
|
||||
0, // notification frames
|
||||
mSessionId,
|
||||
AudioTrack::TRANSFER_CALLBACK,
|
||||
aOffloadInfo,
|
||||
mUid);
|
||||
|
||||
if ((!t.get()) || (t->initCheck() != NO_ERROR)) {
|
||||
AUDIO_OFFLOAD_LOG(LogLevel::Error, ("Unable to create audio track"));
|
||||
delete newcbd;
|
||||
return NO_INIT;
|
||||
}
|
||||
|
||||
mCallbackData = newcbd;
|
||||
t->setVolume(1.0);
|
||||
|
||||
mTrack = t;
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
status_t AudioOutput::Start()
|
||||
{
|
||||
AUDIO_OFFLOAD_LOG(LogLevel::Debug, ("%s", __PRETTY_FUNCTION__));
|
||||
if (!mTrack.get()) {
|
||||
return NO_INIT;
|
||||
}
|
||||
mTrack->setVolume(1.0);
|
||||
return mTrack->start();
|
||||
}
|
||||
|
||||
void AudioOutput::Stop()
|
||||
{
|
||||
AUDIO_OFFLOAD_LOG(LogLevel::Debug, ("%s", __PRETTY_FUNCTION__));
|
||||
if (mTrack.get()) {
|
||||
mTrack->stop();
|
||||
}
|
||||
}
|
||||
|
||||
void AudioOutput::Flush()
|
||||
{
|
||||
if (mTrack.get()) {
|
||||
mTrack->flush();
|
||||
}
|
||||
}
|
||||
|
||||
void AudioOutput::Pause()
|
||||
{
|
||||
if (mTrack.get()) {
|
||||
mTrack->pause();
|
||||
}
|
||||
}
|
||||
|
||||
void AudioOutput::Close()
|
||||
{
|
||||
AUDIO_OFFLOAD_LOG(LogLevel::Debug, ("%s", __PRETTY_FUNCTION__));
|
||||
mTrack.clear();
|
||||
|
||||
delete mCallbackData;
|
||||
mCallbackData = nullptr;
|
||||
}
|
||||
|
||||
// static
|
||||
void AudioOutput::CallbackWrapper(int aEvent, void* aCookie, void* aInfo)
|
||||
{
|
||||
CallbackData* data = (CallbackData*) aCookie;
|
||||
data->Lock();
|
||||
AudioOutput* me = data->GetOutput();
|
||||
AudioTrack::Buffer* buffer = (AudioTrack::Buffer*) aInfo;
|
||||
if (!me) {
|
||||
// no output set, likely because the track was scheduled to be reused
|
||||
// by another player, but the format turned out to be incompatible.
|
||||
data->Unlock();
|
||||
if (buffer) {
|
||||
buffer->size = 0;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
switch(aEvent) {
|
||||
|
||||
case AudioTrack::EVENT_MORE_DATA: {
|
||||
|
||||
size_t actualSize = (*me->mCallback)(me, buffer->raw, buffer->size,
|
||||
me->mCallbackCookie, CB_EVENT_FILL_BUFFER);
|
||||
|
||||
if (actualSize == 0 && buffer->size > 0) {
|
||||
// We've reached EOS but the audio track is not stopped yet,
|
||||
// keep playing silence.
|
||||
memset(buffer->raw, 0, buffer->size);
|
||||
actualSize = buffer->size;
|
||||
}
|
||||
|
||||
buffer->size = actualSize;
|
||||
} break;
|
||||
|
||||
case AudioTrack::EVENT_STREAM_END:
|
||||
AUDIO_OFFLOAD_LOG(LogLevel::Debug, ("Callback wrapper: EVENT_STREAM_END"));
|
||||
(*me->mCallback)(me, nullptr /* buffer */, 0 /* size */,
|
||||
me->mCallbackCookie, CB_EVENT_STREAM_END);
|
||||
break;
|
||||
|
||||
case AudioTrack::EVENT_NEW_IAUDIOTRACK :
|
||||
AUDIO_OFFLOAD_LOG(LogLevel::Debug, ("Callback wrapper: EVENT_TEAR_DOWN"));
|
||||
(*me->mCallback)(me, nullptr /* buffer */, 0 /* size */,
|
||||
me->mCallbackCookie, CB_EVENT_TEAR_DOWN);
|
||||
break;
|
||||
|
||||
default:
|
||||
AUDIO_OFFLOAD_LOG(LogLevel::Debug, ("received unknown event type: %d in"
|
||||
" Callback wrapper!", aEvent));
|
||||
break;
|
||||
}
|
||||
|
||||
data->Unlock();
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
@ -1,112 +0,0 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
||||
/*
|
||||
* Copyright (c) 2014 The Linux Foundation. All rights reserved.
|
||||
* Copyright (C) 2008 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.
|
||||
*/
|
||||
|
||||
#ifndef AUDIOOUTPUT_H_
|
||||
#define AUDIOOUTPUT_H_
|
||||
|
||||
#include <stagefright/foundation/ABase.h>
|
||||
#include <utils/Mutex.h>
|
||||
#include <AudioTrack.h>
|
||||
|
||||
#include "GonkAudioSink.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
/**
|
||||
* Stripped version of Android KK MediaPlayerService::AudioOutput class
|
||||
* Android MediaPlayer uses AudioOutput as a wrapper to handle
|
||||
* Android::AudioTrack
|
||||
* Similarly to ease handling offloaded tracks, part of AudioOutput is used here
|
||||
*/
|
||||
class AudioOutput : public GonkAudioSink
|
||||
{
|
||||
typedef android::Mutex Mutex;
|
||||
typedef android::String8 String8;
|
||||
typedef android::status_t status_t;
|
||||
typedef android::AudioTrack AudioTrack;
|
||||
|
||||
class CallbackData;
|
||||
|
||||
public:
|
||||
AudioOutput(int aSessionId, int aUid);
|
||||
virtual ~AudioOutput();
|
||||
|
||||
ssize_t FrameSize() const override;
|
||||
status_t GetPosition(uint32_t* aPosition) const override;
|
||||
status_t SetVolume(float aVolume) const override;
|
||||
status_t SetParameters(const String8& aKeyValuePairs) override;
|
||||
|
||||
// Creates an offloaded audio track with the given parameters
|
||||
// TODO: Try to recycle audio tracks instead of creating new audio tracks
|
||||
// every time
|
||||
status_t Open(uint32_t aSampleRate,
|
||||
int aChannelCount,
|
||||
audio_channel_mask_t aChannelMask,
|
||||
audio_format_t aFormat,
|
||||
AudioCallback aCb,
|
||||
void* aCookie,
|
||||
audio_output_flags_t aFlags = AUDIO_OUTPUT_FLAG_NONE,
|
||||
const audio_offload_info_t* aOffloadInfo = nullptr) override;
|
||||
|
||||
status_t Start() override;
|
||||
void Stop() override;
|
||||
void Flush() override;
|
||||
void Pause() override;
|
||||
void Close() override;
|
||||
|
||||
private:
|
||||
static void CallbackWrapper(int aEvent, void* aMe, void* aInfo);
|
||||
|
||||
android::sp<AudioTrack> mTrack;
|
||||
void* mCallbackCookie;
|
||||
AudioCallback mCallback;
|
||||
CallbackData* mCallbackData;
|
||||
|
||||
// Uid of the current process, need to create audio track
|
||||
int mUid;
|
||||
|
||||
// Session id given by Android::AudioSystem and used to create audio track
|
||||
int mSessionId;
|
||||
|
||||
// CallbackData is what is passed to the AudioTrack as the "user" data.
|
||||
// We need to be able to target this to a different Output on the fly,
|
||||
// so we can't use the Output itself for this.
|
||||
class CallbackData
|
||||
{
|
||||
public:
|
||||
CallbackData(AudioOutput* aCookie)
|
||||
{
|
||||
mData = aCookie;
|
||||
}
|
||||
AudioOutput* GetOutput() { return mData;}
|
||||
void SetOutput(AudioOutput* aNewcookie) { mData = aNewcookie; }
|
||||
// Lock/Unlock are used by the callback before accessing the payload of
|
||||
// this object
|
||||
void Lock() { mLock.lock(); }
|
||||
void Unlock() { mLock.unlock(); }
|
||||
private:
|
||||
AudioOutput* mData;
|
||||
mutable Mutex mLock;
|
||||
DISALLOW_EVIL_CONSTRUCTORS(CallbackData);
|
||||
};
|
||||
}; // AudioOutput
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif /* AUDIOOUTPUT_H_ */
|
@ -1,89 +0,0 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
||||
/*
|
||||
* Copyright (c) 2014 The Linux Foundation. All rights reserved.
|
||||
* Copyright (C) 2007 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.
|
||||
*/
|
||||
|
||||
#ifndef GONK_AUDIO_SINK_H_
|
||||
#define GONK_AUDIO_SINK_H_
|
||||
|
||||
#include <utils/Errors.h>
|
||||
#include <utils/String8.h>
|
||||
#include <system/audio.h>
|
||||
|
||||
#define DEFAULT_AUDIOSINK_BUFFERCOUNT 4
|
||||
#define DEFAULT_AUDIOSINK_BUFFERSIZE 1200
|
||||
#define DEFAULT_AUDIOSINK_SAMPLERATE 44100
|
||||
|
||||
// when the channel mask isn't known, use the channel count to derive a mask in
|
||||
// AudioSink::open()
|
||||
#define CHANNEL_MASK_USE_CHANNEL_ORDER 0
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
/**
|
||||
* AudioSink: abstraction layer for audio output
|
||||
* Stripped version of Android KK MediaPlayerBase::AudioSink class
|
||||
*/
|
||||
|
||||
class GonkAudioSink : public android::RefBase
|
||||
{
|
||||
typedef android::String8 String8;
|
||||
typedef android::status_t status_t;
|
||||
|
||||
public:
|
||||
enum cb_event_t {
|
||||
CB_EVENT_FILL_BUFFER, // Request to write more data to buffer.
|
||||
CB_EVENT_STREAM_END, // Sent after all the buffers queued in AF and HW
|
||||
// are played back (after stop is called)
|
||||
CB_EVENT_TEAR_DOWN // The AudioTrack was invalidated due to usecase
|
||||
// change. Need to re-evaluate offloading options
|
||||
};
|
||||
|
||||
// Callback returns the number of bytes actually written to the buffer.
|
||||
typedef size_t (*AudioCallback)(GonkAudioSink* aAudioSink,
|
||||
void* aBuffer,
|
||||
size_t aSize,
|
||||
void* aCookie,
|
||||
cb_event_t aEvent);
|
||||
virtual ~GonkAudioSink() {}
|
||||
virtual ssize_t FrameSize() const = 0;
|
||||
virtual status_t GetPosition(uint32_t* aPosition) const = 0;
|
||||
virtual status_t SetVolume(float aVolume) const = 0;
|
||||
virtual status_t SetParameters(const String8& aKeyValuePairs)
|
||||
{
|
||||
return android::NO_ERROR;
|
||||
}
|
||||
|
||||
virtual status_t Open(uint32_t aSampleRate,
|
||||
int aChannelCount,
|
||||
audio_channel_mask_t aChannelMask,
|
||||
audio_format_t aFormat=AUDIO_FORMAT_PCM_16_BIT,
|
||||
AudioCallback aCb = nullptr,
|
||||
void* aCookie = nullptr,
|
||||
audio_output_flags_t aFlags = AUDIO_OUTPUT_FLAG_NONE,
|
||||
const audio_offload_info_t* aOffloadInfo = nullptr) = 0;
|
||||
|
||||
virtual status_t Start() = 0;
|
||||
virtual void Stop() = 0;
|
||||
virtual void Flush() = 0;
|
||||
virtual void Pause() = 0;
|
||||
virtual void Close() = 0;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // GONK_AUDIO_SINK_H_
|
@ -1,220 +0,0 @@
|
||||
/* -*- 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 "I420ColorConverterHelper.h"
|
||||
|
||||
#include <dlfcn.h>
|
||||
|
||||
#include "mozilla/Logging.h"
|
||||
|
||||
mozilla::LazyLogModule gI420ColorConverterHelperLog("I420ColorConverterHelper");
|
||||
#define LOG(msg...) MOZ_LOG(gI420ColorConverterHelperLog, mozilla::LogLevel::Warning, (msg))
|
||||
|
||||
namespace android {
|
||||
|
||||
I420ColorConverterHelper::I420ColorConverterHelper()
|
||||
: mHandle(nullptr)
|
||||
, mConverter({nullptr, nullptr, nullptr, nullptr, nullptr})
|
||||
{
|
||||
}
|
||||
|
||||
I420ColorConverterHelper::~I420ColorConverterHelper()
|
||||
{
|
||||
RWLock::AutoWLock awl(mLock);
|
||||
unloadLocked();
|
||||
}
|
||||
|
||||
// Prerequisite: a writer-lock should be held
|
||||
bool
|
||||
I420ColorConverterHelper::loadLocked()
|
||||
{
|
||||
if (loadedLocked()) {
|
||||
return true;
|
||||
}
|
||||
unloadLocked();
|
||||
|
||||
// Open the shared library
|
||||
mHandle = dlopen("libI420colorconvert.so", RTLD_NOW);
|
||||
if (mHandle == nullptr) {
|
||||
LOG("libI420colorconvert.so not found");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Find the entry point
|
||||
// Pointer to function with signature
|
||||
// void getI420ColorConverter(II420ColorConverter *converter)
|
||||
typedef int (* getConverterFn)(II420ColorConverter *converter);
|
||||
getConverterFn getI420ColorConverter =
|
||||
(getConverterFn) dlsym(mHandle, "getI420ColorConverter");
|
||||
if (getI420ColorConverter == nullptr) {
|
||||
LOG("Cannot load getI420ColorConverter from libI420colorconvert.so");
|
||||
unloadLocked();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Fill the function pointers.
|
||||
getI420ColorConverter(&mConverter);
|
||||
if (mConverter.getDecoderOutputFormat == nullptr ||
|
||||
mConverter.convertDecoderOutputToI420 == nullptr ||
|
||||
mConverter.getEncoderInputFormat == nullptr ||
|
||||
mConverter.convertI420ToEncoderInput == nullptr ||
|
||||
mConverter.getEncoderInputBufferInfo == nullptr) {
|
||||
LOG("Failed to initialize I420 color converter");
|
||||
unloadLocked();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Prerequisite: a reader-lock or a writer-lock should be held
|
||||
bool
|
||||
I420ColorConverterHelper::loadedLocked() const
|
||||
{
|
||||
if (mHandle == nullptr ||
|
||||
mConverter.getDecoderOutputFormat == nullptr ||
|
||||
mConverter.convertDecoderOutputToI420 == nullptr ||
|
||||
mConverter.getEncoderInputFormat == nullptr ||
|
||||
mConverter.convertI420ToEncoderInput == nullptr ||
|
||||
mConverter.getEncoderInputBufferInfo == nullptr) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Prerequisite: a writer-lock should be held
|
||||
void
|
||||
I420ColorConverterHelper::unloadLocked()
|
||||
{
|
||||
if (mHandle != nullptr) {
|
||||
dlclose(mHandle);
|
||||
}
|
||||
mHandle = nullptr;
|
||||
mConverter.getDecoderOutputFormat = nullptr;
|
||||
mConverter.convertDecoderOutputToI420 = nullptr;
|
||||
mConverter.getEncoderInputFormat = nullptr;
|
||||
mConverter.convertI420ToEncoderInput = nullptr;
|
||||
mConverter.getEncoderInputBufferInfo = nullptr;
|
||||
}
|
||||
|
||||
bool
|
||||
I420ColorConverterHelper::ensureLoaded()
|
||||
{
|
||||
{
|
||||
RWLock::AutoRLock arl(mLock);
|
||||
// Check whether the library has been loaded or not.
|
||||
if (loadedLocked()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
RWLock::AutoWLock awl(mLock);
|
||||
// Check whether the library has been loaded or not on other threads.
|
||||
if (loadedLocked()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Reload the library
|
||||
unloadLocked();
|
||||
if (loadLocked()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Reset the library
|
||||
unloadLocked();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int
|
||||
I420ColorConverterHelper::getDecoderOutputFormat()
|
||||
{
|
||||
if (!ensureLoaded()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
RWLock::AutoRLock arl(mLock);
|
||||
if (mConverter.getDecoderOutputFormat != nullptr) {
|
||||
return mConverter.getDecoderOutputFormat();
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int
|
||||
I420ColorConverterHelper::convertDecoderOutputToI420(
|
||||
void* decoderBits, int decoderWidth, int decoderHeight,
|
||||
ARect decoderRect, void* dstBits)
|
||||
{
|
||||
if (!ensureLoaded()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
RWLock::AutoRLock arl(mLock);
|
||||
if (mConverter.convertDecoderOutputToI420 != nullptr) {
|
||||
return mConverter.convertDecoderOutputToI420(decoderBits,
|
||||
decoderWidth, decoderHeight, decoderRect, dstBits);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int
|
||||
I420ColorConverterHelper::getEncoderInputFormat()
|
||||
{
|
||||
if (!ensureLoaded()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
RWLock::AutoRLock arl(mLock);
|
||||
if (mConverter.getEncoderInputFormat != nullptr) {
|
||||
return mConverter.getEncoderInputFormat();
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int
|
||||
I420ColorConverterHelper::convertI420ToEncoderInput(void* aSrcBits,
|
||||
int aSrcWidth,
|
||||
int aSrcHeight,
|
||||
int aEncoderWidth,
|
||||
int aEncoderHeight,
|
||||
ARect aEncoderRect,
|
||||
void* aEncoderBits)
|
||||
{
|
||||
if (!ensureLoaded()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
RWLock::AutoRLock arl(mLock);
|
||||
if (mConverter.convertI420ToEncoderInput != nullptr) {
|
||||
return mConverter.convertI420ToEncoderInput(aSrcBits, aSrcWidth, aSrcHeight,
|
||||
aEncoderWidth, aEncoderHeight, aEncoderRect, aEncoderBits);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int
|
||||
I420ColorConverterHelper::getEncoderInputBufferInfo(int aSrcWidth,
|
||||
int aSrcHeight,
|
||||
int* aEncoderWidth,
|
||||
int* aEncoderHeight,
|
||||
ARect* aEncoderRect,
|
||||
int* aEncoderBufferSize)
|
||||
{
|
||||
if (!ensureLoaded()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
RWLock::AutoRLock arl(mLock);
|
||||
if (mConverter.getEncoderInputBufferInfo != nullptr) {
|
||||
return mConverter.getEncoderInputBufferInfo(aSrcWidth, aSrcHeight,
|
||||
aEncoderWidth, aEncoderHeight, aEncoderRect, aEncoderBufferSize);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
} // namespace android
|
@ -1,64 +0,0 @@
|
||||
/* -*- 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/. */
|
||||
|
||||
#ifndef I420_COLOR_CONVERTER_HELPER_H
|
||||
#define I420_COLOR_CONVERTER_HELPER_H
|
||||
|
||||
#include <utils/RWLock.h>
|
||||
#include <media/editor/II420ColorConverter.h>
|
||||
|
||||
#include <mozilla/Attributes.h>
|
||||
|
||||
namespace android {
|
||||
|
||||
class I420ColorConverterHelper {
|
||||
public:
|
||||
I420ColorConverterHelper();
|
||||
~I420ColorConverterHelper();
|
||||
|
||||
int getDecoderOutputFormat();
|
||||
|
||||
int convertDecoderOutputToI420(void* aDecoderBits,
|
||||
int aDecoderWidth,
|
||||
int aDecoderHeight,
|
||||
ARect aDecoderRect,
|
||||
void* aDstBits);
|
||||
|
||||
int getEncoderInputFormat();
|
||||
|
||||
int convertI420ToEncoderInput(void* aSrcBits,
|
||||
int aSrcWidth,
|
||||
int aSrcHeight,
|
||||
int aEncoderWidth,
|
||||
int aEncoderHeight,
|
||||
ARect aEncoderRect,
|
||||
void* aEncoderBits);
|
||||
|
||||
int getEncoderInputBufferInfo(int aSrcWidth,
|
||||
int aSrcHeight,
|
||||
int* aEncoderWidth,
|
||||
int* aEncoderHeight,
|
||||
ARect* aEncoderRect,
|
||||
int* aEncoderBufferSize);
|
||||
|
||||
private:
|
||||
mutable RWLock mLock;
|
||||
void *mHandle;
|
||||
II420ColorConverter mConverter;
|
||||
|
||||
bool loadLocked();
|
||||
bool loadedLocked() const;
|
||||
void unloadLocked();
|
||||
|
||||
bool ensureLoaded();
|
||||
|
||||
I420ColorConverterHelper(const I420ColorConverterHelper &) = delete;
|
||||
const I420ColorConverterHelper &operator=(const I420ColorConverterHelper &) = delete;
|
||||
};
|
||||
|
||||
} // namespace android
|
||||
|
||||
#endif // I420_COLOR_CONVERTER_HELPER_H
|
@ -1,153 +0,0 @@
|
||||
/* -*- 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/. */
|
||||
#if !defined(MPAPI_h_)
|
||||
#define MPAPI_h_
|
||||
|
||||
#include <stdint.h>
|
||||
#include "mozilla/layers/TextureClient.h"
|
||||
|
||||
namespace MPAPI {
|
||||
|
||||
struct VideoPlane {
|
||||
VideoPlane() :
|
||||
mData(nullptr),
|
||||
mStride(0),
|
||||
mWidth(0),
|
||||
mHeight(0),
|
||||
mOffset(0),
|
||||
mSkip(0)
|
||||
{}
|
||||
|
||||
void *mData;
|
||||
int32_t mStride;
|
||||
int32_t mWidth;
|
||||
int32_t mHeight;
|
||||
int32_t mOffset;
|
||||
int32_t mSkip;
|
||||
};
|
||||
|
||||
struct VideoFrame {
|
||||
int64_t mTimeUs;
|
||||
bool mKeyFrame;
|
||||
bool mShouldSkip;
|
||||
void *mData;
|
||||
size_t mSize;
|
||||
int32_t mStride;
|
||||
int32_t mSliceHeight;
|
||||
int32_t mRotation;
|
||||
VideoPlane Y;
|
||||
VideoPlane Cb;
|
||||
VideoPlane Cr;
|
||||
RefPtr<mozilla::layers::TextureClient> mGraphicBuffer;
|
||||
|
||||
VideoFrame() :
|
||||
mTimeUs(0),
|
||||
mKeyFrame(false),
|
||||
mShouldSkip(false),
|
||||
mData(nullptr),
|
||||
mSize(0),
|
||||
mStride(0),
|
||||
mSliceHeight(0),
|
||||
mRotation(0)
|
||||
{}
|
||||
|
||||
void Set(int64_t aTimeUs, bool aKeyFrame,
|
||||
void *aData, size_t aSize, int32_t aStride, int32_t aSliceHeight, int32_t aRotation,
|
||||
void *aYData, int32_t aYStride, int32_t aYWidth, int32_t aYHeight, int32_t aYOffset, int32_t aYSkip,
|
||||
void *aCbData, int32_t aCbStride, int32_t aCbWidth, int32_t aCbHeight, int32_t aCbOffset, int32_t aCbSkip,
|
||||
void *aCrData, int32_t aCrStride, int32_t aCrWidth, int32_t aCrHeight, int32_t aCrOffset, int32_t aCrSkip)
|
||||
{
|
||||
mTimeUs = aTimeUs;
|
||||
mKeyFrame = aKeyFrame;
|
||||
mData = aData;
|
||||
mSize = aSize;
|
||||
mStride = aStride;
|
||||
mSliceHeight = aSliceHeight;
|
||||
mRotation = aRotation;
|
||||
mGraphicBuffer = nullptr;
|
||||
Y.mData = aYData;
|
||||
Y.mStride = aYStride;
|
||||
Y.mWidth = aYWidth;
|
||||
Y.mHeight = aYHeight;
|
||||
Y.mOffset = aYOffset;
|
||||
Y.mSkip = aYSkip;
|
||||
Cb.mData = aCbData;
|
||||
Cb.mStride = aCbStride;
|
||||
Cb.mWidth = aCbWidth;
|
||||
Cb.mHeight = aCbHeight;
|
||||
Cb.mOffset = aCbOffset;
|
||||
Cb.mSkip = aCbSkip;
|
||||
Cr.mData = aCrData;
|
||||
Cr.mStride = aCrStride;
|
||||
Cr.mWidth = aCrWidth;
|
||||
Cr.mHeight = aCrHeight;
|
||||
Cr.mOffset = aCrOffset;
|
||||
Cr.mSkip = aCrSkip;
|
||||
}
|
||||
};
|
||||
|
||||
struct AudioFrame {
|
||||
int64_t mTimeUs;
|
||||
void *mData; // 16PCM interleaved
|
||||
size_t mSize; // Size of mData in bytes
|
||||
int32_t mAudioChannels;
|
||||
int32_t mAudioSampleRate;
|
||||
|
||||
AudioFrame() :
|
||||
mTimeUs(0),
|
||||
mData(0),
|
||||
mSize(0),
|
||||
mAudioChannels(0),
|
||||
mAudioSampleRate(0)
|
||||
{
|
||||
}
|
||||
|
||||
void Set(int64_t aTimeUs,
|
||||
void *aData, size_t aSize,
|
||||
int32_t aAudioChannels, int32_t aAudioSampleRate)
|
||||
{
|
||||
mTimeUs = aTimeUs;
|
||||
mData = aData;
|
||||
mSize = aSize;
|
||||
mAudioChannels = aAudioChannels;
|
||||
mAudioSampleRate = aAudioSampleRate;
|
||||
}
|
||||
};
|
||||
|
||||
struct Decoder;
|
||||
|
||||
struct PluginHost {
|
||||
bool (*Read)(Decoder *aDecoder, char *aBuffer, int64_t aOffset, uint32_t aCount, uint32_t* aBytes);
|
||||
uint64_t (*GetLength)(Decoder *aDecoder);
|
||||
void (*SetMetaDataReadMode)(Decoder *aDecoder);
|
||||
void (*SetPlaybackReadMode)(Decoder *aDecoder);
|
||||
};
|
||||
|
||||
struct Decoder {
|
||||
void *mResource;
|
||||
void *mPrivate;
|
||||
|
||||
Decoder();
|
||||
|
||||
void (*GetDuration)(Decoder *aDecoder, int64_t *durationUs);
|
||||
void (*GetVideoParameters)(Decoder *aDecoder, int32_t *aWidth, int32_t *aHeight);
|
||||
void (*GetAudioParameters)(Decoder *aDecoder, int32_t *aNumChannels, int32_t *aSampleRate);
|
||||
bool (*HasVideo)(Decoder *aDecoder);
|
||||
bool (*HasAudio)(Decoder *aDecoder);
|
||||
bool (*ReadVideo)(Decoder *aDecoder, VideoFrame *aFrame, int64_t aSeekTimeUs);
|
||||
bool (*ReadAudio)(Decoder *aDecoder, AudioFrame *aFrame, int64_t aSeekTimeUs);
|
||||
void (*DestroyDecoder)(Decoder *);
|
||||
};
|
||||
|
||||
struct Manifest {
|
||||
bool (*CanDecode)(const char *aMimeChars, size_t aMimeLen, const char* const**aCodecs);
|
||||
bool (*CreateDecoder)(PluginHost *aPluginHost, Decoder *aDecoder,
|
||||
const char *aMimeChars, size_t aMimeLen);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
@ -1,626 +0,0 @@
|
||||
/* -*- 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 "MediaCodecProxy.h"
|
||||
#include <string.h>
|
||||
#include <binder/IPCThreadState.h>
|
||||
#include <stagefright/foundation/ABuffer.h>
|
||||
#include <stagefright/foundation/ADebug.h>
|
||||
#include <stagefright/MetaData.h>
|
||||
#include "stagefright/MediaErrors.h"
|
||||
|
||||
#include <android/log.h>
|
||||
#define MCP_LOG(...) __android_log_print(ANDROID_LOG_DEBUG, "MediaCodecProxy", __VA_ARGS__)
|
||||
|
||||
namespace android {
|
||||
|
||||
// General Template: MediaCodec::getOutputGraphicBufferFromIndex(...)
|
||||
template <typename T, bool InterfaceSupported>
|
||||
struct OutputGraphicBufferStub
|
||||
{
|
||||
static status_t GetOutputGraphicBuffer(T *aMediaCodec,
|
||||
size_t aIndex,
|
||||
sp<GraphicBuffer> *aGraphicBuffer)
|
||||
{
|
||||
return ERROR_UNSUPPORTED;
|
||||
}
|
||||
};
|
||||
|
||||
// Class Template Specialization: MediaCodec::getOutputGraphicBufferFromIndex(...)
|
||||
template <typename T>
|
||||
struct OutputGraphicBufferStub<T, true>
|
||||
{
|
||||
static status_t GetOutputGraphicBuffer(T *aMediaCodec,
|
||||
size_t aIndex,
|
||||
sp<GraphicBuffer> *aGraphicBuffer)
|
||||
{
|
||||
if (aMediaCodec == nullptr || aGraphicBuffer == nullptr) {
|
||||
return BAD_VALUE;
|
||||
}
|
||||
*aGraphicBuffer = aMediaCodec->getOutputGraphicBufferFromIndex(aIndex);
|
||||
return OK;
|
||||
}
|
||||
};
|
||||
|
||||
// Wrapper class to handle interface-difference of MediaCodec.
|
||||
struct MediaCodecInterfaceWrapper
|
||||
{
|
||||
typedef int8_t Supported;
|
||||
typedef int16_t Unsupported;
|
||||
|
||||
template <typename T>
|
||||
static auto TestOutputGraphicBuffer(T *aMediaCodec) -> decltype(aMediaCodec->getOutputGraphicBufferFromIndex(0), Supported());
|
||||
|
||||
template <typename T>
|
||||
static auto TestOutputGraphicBuffer(...) -> Unsupported;
|
||||
|
||||
// SFINAE: Substitution Failure Is Not An Error
|
||||
static const bool OutputGraphicBufferSupported = sizeof(TestOutputGraphicBuffer<MediaCodec>(nullptr)) == sizeof(Supported);
|
||||
|
||||
// Class Template Specialization
|
||||
static OutputGraphicBufferStub<MediaCodec, OutputGraphicBufferSupported> sOutputGraphicBufferStub;
|
||||
|
||||
// Wrapper Function
|
||||
static status_t GetOutputGraphicBuffer(MediaCodec *aMediaCodec,
|
||||
size_t aIndex,
|
||||
sp<GraphicBuffer> *aGraphicBuffer)
|
||||
{
|
||||
return sOutputGraphicBufferStub.GetOutputGraphicBuffer(aMediaCodec, aIndex, aGraphicBuffer);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
sp<MediaCodecProxy>
|
||||
MediaCodecProxy::CreateByType(sp<ALooper> aLooper,
|
||||
const char *aMime,
|
||||
bool aEncoder)
|
||||
{
|
||||
sp<MediaCodecProxy> codec = new MediaCodecProxy(aLooper,
|
||||
aMime,
|
||||
aEncoder);
|
||||
return codec;
|
||||
}
|
||||
|
||||
MediaCodecProxy::MediaCodecProxy(sp<ALooper> aLooper,
|
||||
const char *aMime,
|
||||
bool aEncoder)
|
||||
: mCodecLooper(aLooper)
|
||||
, mCodecMime(aMime)
|
||||
, mCodecEncoder(aEncoder)
|
||||
, mPromiseMonitor("MediaCodecProxy::mPromiseMonitor")
|
||||
{
|
||||
MOZ_ASSERT(mCodecLooper != nullptr, "ALooper should not be nullptr.");
|
||||
mCodecPromise.SetMonitor(&mPromiseMonitor);
|
||||
}
|
||||
|
||||
MediaCodecProxy::~MediaCodecProxy()
|
||||
{
|
||||
ReleaseMediaCodec();
|
||||
}
|
||||
|
||||
bool
|
||||
MediaCodecProxy::AllocateAudioMediaCodec()
|
||||
{
|
||||
if (mResourceClient || mCodec.get()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (strncasecmp(mCodecMime.get(), "audio/", 6) == 0) {
|
||||
if (allocateCodec()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
RefPtr<MediaCodecProxy::CodecPromise>
|
||||
MediaCodecProxy::AsyncAllocateVideoMediaCodec()
|
||||
{
|
||||
if (mResourceClient || mCodec.get()) {
|
||||
return CodecPromise::CreateAndReject(true, __func__);
|
||||
}
|
||||
|
||||
if (strncasecmp(mCodecMime.get(), "video/", 6) != 0) {
|
||||
return CodecPromise::CreateAndReject(true, __func__);
|
||||
}
|
||||
// request video codec
|
||||
mozilla::MediaSystemResourceType type =
|
||||
mCodecEncoder ? mozilla::MediaSystemResourceType::VIDEO_ENCODER :
|
||||
mozilla::MediaSystemResourceType::VIDEO_DECODER;
|
||||
mResourceClient = new mozilla::MediaSystemResourceClient(type);
|
||||
mResourceClient->SetListener(this);
|
||||
mResourceClient->Acquire();
|
||||
|
||||
mozilla::MonitorAutoLock lock(mPromiseMonitor);
|
||||
RefPtr<CodecPromise> p = mCodecPromise.Ensure(__func__);
|
||||
return p.forget();
|
||||
}
|
||||
|
||||
void
|
||||
MediaCodecProxy::ReleaseMediaCodec()
|
||||
{
|
||||
// At first, release mResourceClient's resource to prevent a conflict with
|
||||
// mResourceClient's callback.
|
||||
if (mResourceClient) {
|
||||
mResourceClient->ReleaseResource();
|
||||
mResourceClient = nullptr;
|
||||
}
|
||||
|
||||
mozilla::MonitorAutoLock lock(mPromiseMonitor);
|
||||
mCodecPromise.RejectIfExists(true, __func__);
|
||||
releaseCodec();
|
||||
}
|
||||
|
||||
bool
|
||||
MediaCodecProxy::allocateCodec()
|
||||
{
|
||||
if (mCodecLooper == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Write Lock for mCodec
|
||||
RWLock::AutoWLock awl(mCodecLock);
|
||||
|
||||
// Create MediaCodec
|
||||
mCodec = MediaCodec::CreateByType(mCodecLooper, mCodecMime.get(), mCodecEncoder);
|
||||
if (mCodec == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
MediaCodecProxy::releaseCodec()
|
||||
{
|
||||
wp<MediaCodec> codec;
|
||||
|
||||
{
|
||||
// Write Lock for mCodec
|
||||
RWLock::AutoWLock awl(mCodecLock);
|
||||
|
||||
codec = mCodec;
|
||||
|
||||
// Release MediaCodec
|
||||
if (mCodec != nullptr) {
|
||||
mCodec->stop();
|
||||
mCodec->release();
|
||||
mCodec = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
while (codec.promote() != nullptr) {
|
||||
// this value come from stagefright's AwesomePlayer.
|
||||
usleep(1000);
|
||||
}
|
||||
|
||||
// Complete all pending Binder ipc transactions
|
||||
IPCThreadState::self()->flushCommands();
|
||||
|
||||
}
|
||||
|
||||
bool
|
||||
MediaCodecProxy::allocated() const
|
||||
{
|
||||
// Read Lock for mCodec
|
||||
RWLock::AutoRLock arl(mCodecLock);
|
||||
|
||||
return mCodec != nullptr;
|
||||
}
|
||||
|
||||
status_t
|
||||
MediaCodecProxy::configure(const sp<AMessage> &aFormat,
|
||||
const sp<Surface> &aNativeWindow,
|
||||
const sp<ICrypto> &aCrypto,
|
||||
uint32_t aFlags)
|
||||
{
|
||||
// Read Lock for mCodec
|
||||
RWLock::AutoRLock arl(mCodecLock);
|
||||
|
||||
if (mCodec == nullptr) {
|
||||
return NO_INIT;
|
||||
}
|
||||
return mCodec->configure(aFormat, aNativeWindow, aCrypto, aFlags);
|
||||
}
|
||||
|
||||
status_t
|
||||
MediaCodecProxy::start()
|
||||
{
|
||||
// Read Lock for mCodec
|
||||
RWLock::AutoRLock arl(mCodecLock);
|
||||
|
||||
if (mCodec == nullptr) {
|
||||
return NO_INIT;
|
||||
}
|
||||
|
||||
return mCodec->start();
|
||||
}
|
||||
|
||||
status_t
|
||||
MediaCodecProxy::stop()
|
||||
{
|
||||
// Read Lock for mCodec
|
||||
RWLock::AutoRLock arl(mCodecLock);
|
||||
|
||||
if (mCodec == nullptr) {
|
||||
return NO_INIT;
|
||||
}
|
||||
return mCodec->stop();
|
||||
}
|
||||
|
||||
status_t
|
||||
MediaCodecProxy::release()
|
||||
{
|
||||
// Read Lock for mCodec
|
||||
RWLock::AutoRLock arl(mCodecLock);
|
||||
|
||||
if (mCodec == nullptr) {
|
||||
return NO_INIT;
|
||||
}
|
||||
return mCodec->release();
|
||||
}
|
||||
|
||||
status_t
|
||||
MediaCodecProxy::flush()
|
||||
{
|
||||
// Read Lock for mCodec
|
||||
RWLock::AutoRLock arl(mCodecLock);
|
||||
|
||||
if (mCodec == nullptr) {
|
||||
return NO_INIT;
|
||||
}
|
||||
return mCodec->flush();
|
||||
}
|
||||
|
||||
status_t
|
||||
MediaCodecProxy::queueInputBuffer(size_t aIndex,
|
||||
size_t aOffset,
|
||||
size_t aSize,
|
||||
int64_t aPresentationTimeUs,
|
||||
uint32_t aFlags,
|
||||
AString *aErrorDetailMessage)
|
||||
{
|
||||
// Read Lock for mCodec
|
||||
RWLock::AutoRLock arl(mCodecLock);
|
||||
|
||||
if (mCodec == nullptr) {
|
||||
return NO_INIT;
|
||||
}
|
||||
return mCodec->queueInputBuffer(aIndex, aOffset, aSize,
|
||||
aPresentationTimeUs, aFlags, aErrorDetailMessage);
|
||||
}
|
||||
|
||||
status_t
|
||||
MediaCodecProxy::queueSecureInputBuffer(size_t aIndex,
|
||||
size_t aOffset,
|
||||
const CryptoPlugin::SubSample *aSubSamples,
|
||||
size_t aNumSubSamples,
|
||||
const uint8_t aKey[16],
|
||||
const uint8_t aIV[16],
|
||||
CryptoPlugin::Mode aMode,
|
||||
int64_t aPresentationTimeUs,
|
||||
uint32_t aFlags,
|
||||
AString *aErrorDetailMessage)
|
||||
{
|
||||
// Read Lock for mCodec
|
||||
RWLock::AutoRLock arl(mCodecLock);
|
||||
|
||||
if (mCodec == nullptr) {
|
||||
return NO_INIT;
|
||||
}
|
||||
return mCodec->queueSecureInputBuffer(aIndex, aOffset,
|
||||
aSubSamples, aNumSubSamples, aKey, aIV, aMode,
|
||||
aPresentationTimeUs, aFlags, aErrorDetailMessage);
|
||||
}
|
||||
|
||||
status_t
|
||||
MediaCodecProxy::dequeueInputBuffer(size_t *aIndex,
|
||||
int64_t aTimeoutUs)
|
||||
{
|
||||
// Read Lock for mCodec
|
||||
RWLock::AutoRLock arl(mCodecLock);
|
||||
|
||||
if (mCodec == nullptr) {
|
||||
return NO_INIT;
|
||||
}
|
||||
return mCodec->dequeueInputBuffer(aIndex, aTimeoutUs);
|
||||
}
|
||||
|
||||
status_t
|
||||
MediaCodecProxy::dequeueOutputBuffer(size_t *aIndex,
|
||||
size_t *aOffset,
|
||||
size_t *aSize,
|
||||
int64_t *aPresentationTimeUs,
|
||||
uint32_t *aFlags,
|
||||
int64_t aTimeoutUs)
|
||||
{
|
||||
// Read Lock for mCodec
|
||||
RWLock::AutoRLock arl(mCodecLock);
|
||||
|
||||
if (mCodec == nullptr) {
|
||||
return NO_INIT;
|
||||
}
|
||||
return mCodec->dequeueOutputBuffer(aIndex, aOffset, aSize,
|
||||
aPresentationTimeUs, aFlags, aTimeoutUs);
|
||||
}
|
||||
|
||||
status_t
|
||||
MediaCodecProxy::renderOutputBufferAndRelease(size_t aIndex)
|
||||
{
|
||||
// Read Lock for mCodec
|
||||
RWLock::AutoRLock arl(mCodecLock);
|
||||
|
||||
if (mCodec == nullptr) {
|
||||
return NO_INIT;
|
||||
}
|
||||
return mCodec->renderOutputBufferAndRelease(aIndex);
|
||||
}
|
||||
|
||||
status_t
|
||||
MediaCodecProxy::releaseOutputBuffer(size_t aIndex)
|
||||
{
|
||||
// Read Lock for mCodec
|
||||
RWLock::AutoRLock arl(mCodecLock);
|
||||
|
||||
if (mCodec == nullptr) {
|
||||
return NO_INIT;
|
||||
}
|
||||
return mCodec->releaseOutputBuffer(aIndex);
|
||||
}
|
||||
|
||||
status_t
|
||||
MediaCodecProxy::signalEndOfInputStream()
|
||||
{
|
||||
// Read Lock for mCodec
|
||||
RWLock::AutoRLock arl(mCodecLock);
|
||||
|
||||
if (mCodec == nullptr) {
|
||||
return NO_INIT;
|
||||
}
|
||||
return mCodec->signalEndOfInputStream();
|
||||
}
|
||||
|
||||
status_t
|
||||
MediaCodecProxy::getOutputFormat(sp<AMessage> *aFormat) const
|
||||
{
|
||||
// Read Lock for mCodec
|
||||
RWLock::AutoRLock arl(mCodecLock);
|
||||
|
||||
if (mCodec == nullptr) {
|
||||
return NO_INIT;
|
||||
}
|
||||
return mCodec->getOutputFormat(aFormat);
|
||||
}
|
||||
|
||||
status_t
|
||||
MediaCodecProxy::getInputBuffers(Vector<sp<ABuffer>> *aBuffers) const
|
||||
{
|
||||
// Read Lock for mCodec
|
||||
RWLock::AutoRLock arl(mCodecLock);
|
||||
|
||||
if (mCodec == nullptr) {
|
||||
return NO_INIT;
|
||||
}
|
||||
return mCodec->getInputBuffers(aBuffers);
|
||||
}
|
||||
|
||||
status_t
|
||||
MediaCodecProxy::getOutputBuffers(Vector<sp<ABuffer>> *aBuffers) const
|
||||
{
|
||||
// Read Lock for mCodec
|
||||
RWLock::AutoRLock arl(mCodecLock);
|
||||
|
||||
if (mCodec == nullptr) {
|
||||
return NO_INIT;
|
||||
}
|
||||
return mCodec->getOutputBuffers(aBuffers);
|
||||
}
|
||||
|
||||
void
|
||||
MediaCodecProxy::requestActivityNotification(const sp<AMessage> &aNotify)
|
||||
{
|
||||
// Read Lock for mCodec
|
||||
RWLock::AutoRLock arl(mCodecLock);
|
||||
|
||||
if (mCodec == nullptr) {
|
||||
return;
|
||||
}
|
||||
mCodec->requestActivityNotification(aNotify);
|
||||
}
|
||||
|
||||
status_t
|
||||
MediaCodecProxy::getOutputGraphicBufferFromIndex(size_t aIndex,
|
||||
sp<GraphicBuffer> *aGraphicBuffer)
|
||||
{
|
||||
// Read Lock for mCodec
|
||||
RWLock::AutoRLock arl(mCodecLock);
|
||||
|
||||
if (mCodec == nullptr) {
|
||||
return NO_INIT;
|
||||
}
|
||||
return MediaCodecInterfaceWrapper::GetOutputGraphicBuffer(mCodec.get(), aIndex, aGraphicBuffer);
|
||||
}
|
||||
|
||||
status_t
|
||||
MediaCodecProxy::getCapability(uint32_t *aCapability)
|
||||
{
|
||||
if (aCapability == nullptr) {
|
||||
return BAD_VALUE;
|
||||
}
|
||||
|
||||
uint32_t capability = kEmptyCapability;
|
||||
|
||||
if (MediaCodecInterfaceWrapper::OutputGraphicBufferSupported) {
|
||||
capability |= kCanExposeGraphicBuffer;
|
||||
}
|
||||
|
||||
*aCapability = capability;
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
// Called on ImageBridge thread
|
||||
void
|
||||
MediaCodecProxy::ResourceReserved()
|
||||
{
|
||||
MCP_LOG("resourceReserved");
|
||||
mozilla::MonitorAutoLock lock(mPromiseMonitor);
|
||||
// Create MediaCodec
|
||||
if (!allocateCodec()) {
|
||||
mCodecPromise.RejectIfExists(true, __func__);
|
||||
return;
|
||||
}
|
||||
mCodecPromise.ResolveIfExists(true, __func__);
|
||||
}
|
||||
|
||||
// Called on ImageBridge thread
|
||||
void
|
||||
MediaCodecProxy::ResourceReserveFailed()
|
||||
{
|
||||
mozilla::MonitorAutoLock lock(mPromiseMonitor);
|
||||
mCodecPromise.RejectIfExists(true, __func__);
|
||||
}
|
||||
|
||||
bool MediaCodecProxy::Prepare()
|
||||
{
|
||||
|
||||
if (start() != OK) {
|
||||
MCP_LOG("Couldn't start MediaCodec");
|
||||
return false;
|
||||
}
|
||||
if (getInputBuffers(&mInputBuffers) != OK) {
|
||||
MCP_LOG("Couldn't get input buffers from MediaCodec");
|
||||
return false;
|
||||
}
|
||||
if (getOutputBuffers(&mOutputBuffers) != OK) {
|
||||
MCP_LOG("Couldn't get output buffers from MediaCodec");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MediaCodecProxy::UpdateOutputBuffers()
|
||||
{
|
||||
// Read Lock for mCodec
|
||||
{
|
||||
RWLock::AutoRLock autolock(mCodecLock);
|
||||
if (mCodec == nullptr) {
|
||||
MCP_LOG("MediaCodec has not been inited from UpdateOutputBuffers");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
status_t err = getOutputBuffers(&mOutputBuffers);
|
||||
if (err != OK){
|
||||
MCP_LOG("Couldn't update output buffers from MediaCodec");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
status_t MediaCodecProxy::Input(const uint8_t* aData, uint32_t aDataSize,
|
||||
int64_t aTimestampUsecs, uint64_t aflags,
|
||||
int64_t aTimeoutUs)
|
||||
{
|
||||
// Read Lock for mCodec
|
||||
{
|
||||
RWLock::AutoRLock autolock(mCodecLock);
|
||||
if (mCodec == nullptr) {
|
||||
MCP_LOG("MediaCodec has not been inited from input!");
|
||||
return NO_INIT;
|
||||
}
|
||||
}
|
||||
|
||||
size_t index;
|
||||
status_t err = dequeueInputBuffer(&index, aTimeoutUs);
|
||||
if (err != OK) {
|
||||
if (err != -EAGAIN) {
|
||||
MCP_LOG("dequeueInputBuffer returned %d", err);
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
if (aData) {
|
||||
const sp<ABuffer> &dstBuffer = mInputBuffers.itemAt(index);
|
||||
|
||||
CHECK_LE(aDataSize, dstBuffer->capacity());
|
||||
dstBuffer->setRange(0, aDataSize);
|
||||
|
||||
memcpy(dstBuffer->data(), aData, aDataSize);
|
||||
err = queueInputBuffer(index, 0, dstBuffer->size(), aTimestampUsecs, aflags);
|
||||
} else {
|
||||
err = queueInputBuffer(index, 0, 0, 0ll, MediaCodec::BUFFER_FLAG_EOS);
|
||||
}
|
||||
|
||||
if (err != OK) {
|
||||
MCP_LOG("queueInputBuffer returned %d", err);
|
||||
return err;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
status_t MediaCodecProxy::Output(MediaBuffer** aBuffer, int64_t aTimeoutUs)
|
||||
{
|
||||
// Read Lock for mCodec
|
||||
{
|
||||
RWLock::AutoRLock autolock(mCodecLock);
|
||||
if (mCodec == nullptr) {
|
||||
MCP_LOG("MediaCodec has not been inited from output!");
|
||||
return NO_INIT;
|
||||
}
|
||||
}
|
||||
|
||||
size_t index = 0;
|
||||
size_t offset = 0;
|
||||
size_t size = 0;
|
||||
int64_t timeUs = 0;
|
||||
uint32_t flags = 0;
|
||||
|
||||
*aBuffer = nullptr;
|
||||
|
||||
status_t err = dequeueOutputBuffer(&index, &offset, &size,
|
||||
&timeUs, &flags, aTimeoutUs);
|
||||
if (err != OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
MediaBuffer *buffer;
|
||||
sp<GraphicBuffer> graphicBuffer;
|
||||
|
||||
if (getOutputGraphicBufferFromIndex(index, &graphicBuffer) == OK &&
|
||||
graphicBuffer != nullptr) {
|
||||
buffer = new MediaBuffer(graphicBuffer);
|
||||
} else {
|
||||
buffer = new MediaBuffer(mOutputBuffers.itemAt(index));
|
||||
}
|
||||
sp<MetaData> metaData = buffer->meta_data();
|
||||
metaData->setInt32(kKeyBufferIndex, index);
|
||||
metaData->setInt64(kKeyTime, timeUs);
|
||||
buffer->set_range(buffer->range_offset(), size);
|
||||
*aBuffer = buffer;
|
||||
if (flags & MediaCodec::BUFFER_FLAG_EOS) {
|
||||
return ERROR_END_OF_STREAM;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
void MediaCodecProxy::ReleaseMediaResources()
|
||||
{
|
||||
ReleaseMediaCodec();
|
||||
}
|
||||
|
||||
void MediaCodecProxy::ReleaseMediaBuffer(MediaBuffer* aBuffer) {
|
||||
if (aBuffer) {
|
||||
sp<MetaData> metaData = aBuffer->meta_data();
|
||||
int32_t index;
|
||||
metaData->findInt32(kKeyBufferIndex, &index);
|
||||
aBuffer->release();
|
||||
releaseOutputBuffer(index);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace android
|
@ -1,183 +0,0 @@
|
||||
/* -*- 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/. */
|
||||
|
||||
#ifndef MEDIA_CODEC_PROXY_H
|
||||
#define MEDIA_CODEC_PROXY_H
|
||||
|
||||
#include <nsString.h>
|
||||
#include <stagefright/MediaCodec.h>
|
||||
#include <stagefright/MediaBuffer.h>
|
||||
#include <utils/threads.h>
|
||||
|
||||
#include "mozilla/media/MediaSystemResourceClient.h"
|
||||
#include "mozilla/Monitor.h"
|
||||
#include "mozilla/MozPromise.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
|
||||
namespace android {
|
||||
// This class is intended to be a proxy for MediaCodec with codec resource
|
||||
// management. Basically user can use it like MediaCodec, but need to handle
|
||||
// the listener when Codec is reserved for Async case. A good example is
|
||||
// MediaCodecReader.cpp. Another useage is to use configure(), Prepare(),
|
||||
// Input(), and Output(). It is used in GonkVideoDecoderManager.cpp which
|
||||
// doesn't need to handle the buffers for codec.
|
||||
class MediaCodecProxy : public RefBase
|
||||
, public mozilla::MediaSystemResourceReservationListener
|
||||
{
|
||||
public:
|
||||
typedef mozilla::MozPromise<bool /* aIgnored */, bool /* aIgnored */, /* IsExclusive = */ true> CodecPromise;
|
||||
|
||||
enum Capability {
|
||||
kEmptyCapability = 0x00000000,
|
||||
kCanExposeGraphicBuffer = 0x00000001,
|
||||
};
|
||||
|
||||
enum {
|
||||
kKeyBufferIndex = 'bfin',
|
||||
};
|
||||
|
||||
// Check whether MediaCodec has been allocated.
|
||||
bool allocated() const;
|
||||
|
||||
// Static MediaCodec methods
|
||||
// Only support MediaCodec::CreateByType()
|
||||
static sp<MediaCodecProxy> CreateByType(sp<ALooper> aLooper,
|
||||
const char *aMime,
|
||||
bool aEncoder);
|
||||
|
||||
// MediaCodec methods
|
||||
status_t configure(const sp<AMessage> &aFormat,
|
||||
const sp<Surface> &aNativeWindow,
|
||||
const sp<ICrypto> &aCrypto,
|
||||
uint32_t aFlags);
|
||||
|
||||
status_t start();
|
||||
|
||||
status_t stop();
|
||||
|
||||
status_t release();
|
||||
|
||||
status_t flush();
|
||||
|
||||
status_t queueInputBuffer(size_t aIndex,
|
||||
size_t aOffset,
|
||||
size_t aSize,
|
||||
int64_t aPresentationTimeUs,
|
||||
uint32_t aFlags,
|
||||
AString *aErrorDetailMessage=nullptr);
|
||||
|
||||
status_t queueSecureInputBuffer(size_t aIndex,
|
||||
size_t aOffset,
|
||||
const CryptoPlugin::SubSample *aSubSamples,
|
||||
size_t aNumSubSamples,
|
||||
const uint8_t aKey[16],
|
||||
const uint8_t aIV[16],
|
||||
CryptoPlugin::Mode aMode,
|
||||
int64_t aPresentationTimeUs,
|
||||
uint32_t aFlags,
|
||||
AString *aErrorDetailMessage=nullptr);
|
||||
|
||||
status_t dequeueInputBuffer(size_t *aIndex,
|
||||
int64_t aTimeoutUs=INT64_C(0));
|
||||
|
||||
status_t dequeueOutputBuffer(size_t *aIndex,
|
||||
size_t *aOffset,
|
||||
size_t *aSize,
|
||||
int64_t *aPresentationTimeUs,
|
||||
uint32_t *aFlags,
|
||||
int64_t aTimeoutUs=INT64_C(0));
|
||||
|
||||
status_t renderOutputBufferAndRelease(size_t aIndex);
|
||||
|
||||
status_t releaseOutputBuffer(size_t aIndex);
|
||||
|
||||
status_t signalEndOfInputStream();
|
||||
|
||||
status_t getOutputFormat(sp<AMessage> *aFormat) const;
|
||||
|
||||
status_t getInputBuffers(Vector<sp<ABuffer>> *aBuffers) const;
|
||||
|
||||
status_t getOutputBuffers(Vector<sp<ABuffer>> *aBuffers) const;
|
||||
|
||||
// Notification will be posted once there "is something to do", i.e.
|
||||
// an input/output buffer has become available, a format change is
|
||||
// pending, an error is pending.
|
||||
void requestActivityNotification(const sp<AMessage> &aNotify);
|
||||
|
||||
status_t getOutputGraphicBufferFromIndex(size_t aIndex,
|
||||
sp<GraphicBuffer> *aGraphicBuffer);
|
||||
|
||||
status_t getCapability(uint32_t *aCapability);
|
||||
|
||||
// Utility functions
|
||||
|
||||
// If aData is null, will notify decoder input EOS
|
||||
status_t Input(const uint8_t* aData, uint32_t aDataSize,
|
||||
int64_t aTimestampUsecs, uint64_t flags, int64_t aTimeoutUs = 0);
|
||||
status_t Output(MediaBuffer** aBuffer, int64_t aTimeoutUs);
|
||||
bool Prepare();
|
||||
void ReleaseMediaResources();
|
||||
// This updates mOutputBuffer when receiving INFO_OUTPUT_BUFFERS_CHANGED event.
|
||||
bool UpdateOutputBuffers();
|
||||
|
||||
void ReleaseMediaBuffer(MediaBuffer* abuffer);
|
||||
|
||||
// It allocates audio MediaCodec synchronously.
|
||||
bool AllocateAudioMediaCodec();
|
||||
|
||||
// It allocates video MediaCodec asynchronously.
|
||||
RefPtr<CodecPromise> AsyncAllocateVideoMediaCodec();
|
||||
|
||||
// Free the OMX codec so others can allocate it.
|
||||
void ReleaseMediaCodec();
|
||||
|
||||
protected:
|
||||
virtual ~MediaCodecProxy();
|
||||
|
||||
// MediaResourceReservationListener
|
||||
void ResourceReserved() override;
|
||||
void ResourceReserveFailed() override;
|
||||
|
||||
private:
|
||||
// Forbidden
|
||||
MediaCodecProxy() = delete;
|
||||
MediaCodecProxy(const MediaCodecProxy &) = delete;
|
||||
const MediaCodecProxy &operator=(const MediaCodecProxy &) = delete;
|
||||
|
||||
// Constructor for MediaCodecProxy::CreateByType
|
||||
MediaCodecProxy(sp<ALooper> aLooper,
|
||||
const char *aMime,
|
||||
bool aEncoder);
|
||||
|
||||
// Allocate Codec Resource
|
||||
bool allocateCodec();
|
||||
// Release Codec Resource
|
||||
void releaseCodec();
|
||||
|
||||
// MediaCodec Parameter
|
||||
sp<ALooper> mCodecLooper;
|
||||
nsCString mCodecMime;
|
||||
bool mCodecEncoder;
|
||||
|
||||
mozilla::MozPromiseHolder<CodecPromise> mCodecPromise;
|
||||
// When mPromiseMonitor is held, mResourceClient's functions should not be called.
|
||||
mozilla::Monitor mPromiseMonitor;
|
||||
|
||||
// Media Resource Management
|
||||
RefPtr<mozilla::MediaSystemResourceClient> mResourceClient;
|
||||
|
||||
// MediaCodec instance
|
||||
mutable RWLock mCodecLock;
|
||||
sp<MediaCodec> mCodec;
|
||||
|
||||
//MediaCodec buffers to hold input/output data.
|
||||
Vector<sp<ABuffer> > mInputBuffers;
|
||||
Vector<sp<ABuffer> > mOutputBuffers;
|
||||
};
|
||||
|
||||
} // namespace android
|
||||
|
||||
#endif // MEDIA_CODEC_PROXY_H
|
@ -1,297 +0,0 @@
|
||||
/* -*- 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 "MediaOmxCommonDecoder.h"
|
||||
|
||||
#include <stagefright/MediaSource.h>
|
||||
|
||||
#include "AudioOffloadPlayerBase.h"
|
||||
#include "MediaDecoderStateMachine.h"
|
||||
#include "MediaOmxCommonReader.h"
|
||||
|
||||
#ifdef MOZ_AUDIO_OFFLOAD
|
||||
#include "AudioOffloadPlayer.h"
|
||||
#endif
|
||||
|
||||
using namespace android;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
extern LazyLogModule gMediaDecoderLog;
|
||||
#define DECODER_LOG(type, msg) MOZ_LOG(gMediaDecoderLog, type, msg)
|
||||
|
||||
MediaOmxCommonDecoder::MediaOmxCommonDecoder(MediaDecoderOwner* aOwner)
|
||||
: MediaDecoder(aOwner)
|
||||
, mReader(nullptr)
|
||||
, mCanOffloadAudio(false)
|
||||
, mFallbackToStateMachine(false)
|
||||
, mIsCaptured(false)
|
||||
{
|
||||
mDormantSupported = true;
|
||||
}
|
||||
|
||||
MediaOmxCommonDecoder::~MediaOmxCommonDecoder() {}
|
||||
|
||||
void
|
||||
MediaOmxCommonDecoder::SetPlatformCanOffloadAudio(bool aCanOffloadAudio)
|
||||
{
|
||||
if (!aCanOffloadAudio) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Stop MDSM from playing to avoid startup glitch (bug 1053186).
|
||||
GetStateMachine()->DispatchAudioOffloading(true);
|
||||
|
||||
// Modify mCanOffloadAudio in the main thread.
|
||||
RefPtr<MediaOmxCommonDecoder> self = this;
|
||||
nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([=] () {
|
||||
self->mCanOffloadAudio = true;
|
||||
});
|
||||
AbstractThread::MainThread()->Dispatch(r.forget());
|
||||
}
|
||||
|
||||
void
|
||||
MediaOmxCommonDecoder::DisableStateMachineAudioOffloading()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
if (mCanOffloadAudio) {
|
||||
// mCanOffloadAudio is true implies we've called
|
||||
// |GetStateMachine()->DispatchAudioOffloading(true)| in
|
||||
// SetPlatformCanOffloadAudio(). We need to turn off audio offloading
|
||||
// for MDSM so it can start playback.
|
||||
GetStateMachine()->DispatchAudioOffloading(false);
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
MediaOmxCommonDecoder::CheckDecoderCanOffloadAudio()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
return (mCanOffloadAudio && !mFallbackToStateMachine &&
|
||||
!mIsCaptured && mPlaybackRate == 1.0);
|
||||
}
|
||||
|
||||
void
|
||||
MediaOmxCommonDecoder::FirstFrameLoaded(nsAutoPtr<MediaInfo> aInfo,
|
||||
MediaDecoderEventVisibility aEventVisibility)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
MediaDecoder::FirstFrameLoaded(aInfo, aEventVisibility);
|
||||
|
||||
if (!CheckDecoderCanOffloadAudio()) {
|
||||
DECODER_LOG(LogLevel::Debug, ("In %s Offload Audio check failed",
|
||||
__PRETTY_FUNCTION__));
|
||||
DisableStateMachineAudioOffloading();
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef MOZ_AUDIO_OFFLOAD
|
||||
mAudioOffloadPlayer = new AudioOffloadPlayer(this);
|
||||
#endif
|
||||
if (!mAudioOffloadPlayer) {
|
||||
DisableStateMachineAudioOffloading();
|
||||
return;
|
||||
}
|
||||
|
||||
mAudioOffloadPlayer->SetSource(mReader->GetAudioOffloadTrack());
|
||||
status_t err = mAudioOffloadPlayer->Start(false);
|
||||
if (err != OK) {
|
||||
mAudioOffloadPlayer = nullptr;
|
||||
mFallbackToStateMachine = true;
|
||||
DECODER_LOG(LogLevel::Debug, ("In %s Unable to start offload audio %d."
|
||||
"Switching to normal mode", __PRETTY_FUNCTION__, err));
|
||||
DisableStateMachineAudioOffloading();
|
||||
return;
|
||||
}
|
||||
PauseStateMachine();
|
||||
if (mLogicallySeeking) {
|
||||
SeekTarget target = SeekTarget(mLogicalPosition,
|
||||
SeekTarget::Accurate,
|
||||
MediaDecoderEventVisibility::Observable);
|
||||
mSeekRequest.DisconnectIfExists();
|
||||
mSeekRequest.Begin(mAudioOffloadPlayer->Seek(target)
|
||||
->Then(AbstractThread::MainThread(), __func__, static_cast<MediaDecoder*>(this),
|
||||
&MediaDecoder::OnSeekResolved, &MediaDecoder::OnSeekRejected));
|
||||
}
|
||||
// Call ChangeState() to run AudioOffloadPlayer since offload state enabled
|
||||
ChangeState(mPlayState);
|
||||
}
|
||||
|
||||
void
|
||||
MediaOmxCommonDecoder::PauseStateMachine()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
DECODER_LOG(LogLevel::Debug, ("%s", __PRETTY_FUNCTION__));
|
||||
|
||||
MOZ_ASSERT(GetStateMachine());
|
||||
// enter dormant state
|
||||
GetStateMachine()->DispatchSetDormant(true);
|
||||
}
|
||||
|
||||
void
|
||||
MediaOmxCommonDecoder::ResumeStateMachine()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
DECODER_LOG(LogLevel::Debug, ("%s current time %f", __PRETTY_FUNCTION__, mLogicalPosition));
|
||||
|
||||
if (IsShutdown()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!GetStateMachine()) {
|
||||
return;
|
||||
}
|
||||
|
||||
GetStateMachine()->DispatchAudioOffloading(false);
|
||||
|
||||
mFallbackToStateMachine = true;
|
||||
mAudioOffloadPlayer = nullptr;
|
||||
SeekTarget target = SeekTarget(mLogicalPosition,
|
||||
SeekTarget::Accurate,
|
||||
MediaDecoderEventVisibility::Suppressed);
|
||||
// Call Seek of MediaDecoderStateMachine to suppress seek events.
|
||||
GetStateMachine()->InvokeSeek(target);
|
||||
|
||||
// exit dormant state
|
||||
GetStateMachine()->DispatchSetDormant(false);
|
||||
UpdateLogicalPosition();
|
||||
}
|
||||
|
||||
void
|
||||
MediaOmxCommonDecoder::AudioOffloadTearDown()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(!IsShutdown());
|
||||
DECODER_LOG(LogLevel::Debug, ("%s", __PRETTY_FUNCTION__));
|
||||
|
||||
// mAudioOffloadPlayer can be null here if ResumeStateMachine was called
|
||||
// just before because of some other error.
|
||||
if (mAudioOffloadPlayer) {
|
||||
ResumeStateMachine();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
MediaOmxCommonDecoder::AddOutputStream(ProcessedMediaStream* aStream,
|
||||
bool aFinishWhenEnded)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
mIsCaptured = true;
|
||||
|
||||
if (mAudioOffloadPlayer) {
|
||||
ResumeStateMachine();
|
||||
}
|
||||
|
||||
MediaDecoder::AddOutputStream(aStream, aFinishWhenEnded);
|
||||
}
|
||||
|
||||
void
|
||||
MediaOmxCommonDecoder::SetPlaybackRate(double aPlaybackRate)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
if (mAudioOffloadPlayer &&
|
||||
((aPlaybackRate != 0.0) || (aPlaybackRate != 1.0))) {
|
||||
ResumeStateMachine();
|
||||
}
|
||||
|
||||
MediaDecoder::SetPlaybackRate(aPlaybackRate);
|
||||
}
|
||||
|
||||
void
|
||||
MediaOmxCommonDecoder::ChangeState(PlayState aState)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
// Keep MediaDecoder state in sync with MediaElement irrespective of offload
|
||||
// playback so it will continue to work in normal mode when offloading fails
|
||||
// in between
|
||||
MediaDecoder::ChangeState(aState);
|
||||
|
||||
if (!mAudioOffloadPlayer) {
|
||||
return;
|
||||
}
|
||||
|
||||
status_t err = mAudioOffloadPlayer->ChangeState(aState);
|
||||
if (err != OK) {
|
||||
ResumeStateMachine();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
MediaOmxCommonDecoder::CallSeek(const SeekTarget& aTarget, dom::Promise* aPromise)
|
||||
{
|
||||
if (!mAudioOffloadPlayer) {
|
||||
MediaDecoder::CallSeek(aTarget, aPromise);
|
||||
return;
|
||||
}
|
||||
|
||||
DiscardOngoingSeekIfExists();
|
||||
mSeekDOMPromise = aPromise;
|
||||
mSeekRequest.Begin(mAudioOffloadPlayer->Seek(aTarget)
|
||||
->Then(AbstractThread::MainThread(), __func__, static_cast<MediaDecoder*>(this),
|
||||
&MediaDecoder::OnSeekResolved, &MediaDecoder::OnSeekRejected));
|
||||
}
|
||||
|
||||
int64_t
|
||||
MediaOmxCommonDecoder::CurrentPosition()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
if (!mAudioOffloadPlayer) {
|
||||
return MediaDecoder::CurrentPosition();
|
||||
}
|
||||
return mAudioOffloadPlayer->GetMediaTimeUs();
|
||||
}
|
||||
|
||||
void
|
||||
MediaOmxCommonDecoder::SetElementVisibility(bool aIsVisible)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MediaDecoder::SetElementVisibility(aIsVisible);
|
||||
if (mAudioOffloadPlayer) {
|
||||
mAudioOffloadPlayer->SetElementVisibility(aIsVisible);
|
||||
}
|
||||
}
|
||||
|
||||
MediaDecoderOwner::NextFrameStatus
|
||||
MediaOmxCommonDecoder::NextFrameStatus()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
return mAudioOffloadPlayer ? mAudioOffloadPlayer->GetNextFrameStatus()
|
||||
: MediaDecoder::NextFrameStatus();
|
||||
}
|
||||
|
||||
void
|
||||
MediaOmxCommonDecoder::SetVolume(double aVolume)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
if (!mAudioOffloadPlayer) {
|
||||
MediaDecoder::SetVolume(aVolume);
|
||||
return;
|
||||
}
|
||||
mAudioOffloadPlayer->SetVolume(aVolume);
|
||||
}
|
||||
|
||||
MediaDecoderStateMachine*
|
||||
MediaOmxCommonDecoder::CreateStateMachine()
|
||||
{
|
||||
mReader = CreateReader();
|
||||
if (mReader != nullptr) {
|
||||
mReader->SetAudioChannel(GetAudioChannel());
|
||||
}
|
||||
return CreateStateMachineFromReader(mReader);
|
||||
}
|
||||
|
||||
void
|
||||
MediaOmxCommonDecoder::Shutdown()
|
||||
{
|
||||
mAudioOffloadPlayer = nullptr;
|
||||
MediaDecoder::Shutdown();
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
@ -1,78 +0,0 @@
|
||||
/* -*- 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/. */
|
||||
|
||||
#ifndef MEDIA_OMX_COMMON_DECODER_H
|
||||
#define MEDIA_OMX_COMMON_DECODER_H
|
||||
|
||||
#include "MediaDecoder.h"
|
||||
#include "nsAutoPtr.h"
|
||||
|
||||
namespace android {
|
||||
struct MOZ_EXPORT MediaSource;
|
||||
} // namespace android
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class AudioOffloadPlayerBase;
|
||||
class MediaOmxCommonReader;
|
||||
|
||||
class MediaOmxCommonDecoder : public MediaDecoder
|
||||
{
|
||||
public:
|
||||
explicit MediaOmxCommonDecoder(MediaDecoderOwner* aOwner);
|
||||
|
||||
void FirstFrameLoaded(nsAutoPtr<MediaInfo> aInfo,
|
||||
MediaDecoderEventVisibility aEventVisibility) override;
|
||||
void ChangeState(PlayState aState) override;
|
||||
void CallSeek(const SeekTarget& aTarget, dom::Promise* aPromise) override;
|
||||
void SetVolume(double aVolume) override;
|
||||
int64_t CurrentPosition() override;
|
||||
MediaDecoderOwner::NextFrameStatus NextFrameStatus() override;
|
||||
void SetElementVisibility(bool aIsVisible) override;
|
||||
void SetPlatformCanOffloadAudio(bool aCanOffloadAudio) override;
|
||||
void AddOutputStream(ProcessedMediaStream* aStream,
|
||||
bool aFinishWhenEnded) override;
|
||||
void SetPlaybackRate(double aPlaybackRate) override;
|
||||
|
||||
void AudioOffloadTearDown();
|
||||
|
||||
MediaDecoderStateMachine* CreateStateMachine() override;
|
||||
|
||||
virtual MediaOmxCommonReader* CreateReader() = 0;
|
||||
virtual MediaDecoderStateMachine* CreateStateMachineFromReader(MediaOmxCommonReader* aReader) = 0;
|
||||
|
||||
void NotifyOffloadPlayerPositionChanged() { UpdateLogicalPosition(); }
|
||||
|
||||
void Shutdown() override;
|
||||
|
||||
protected:
|
||||
virtual ~MediaOmxCommonDecoder();
|
||||
void PauseStateMachine();
|
||||
void ResumeStateMachine();
|
||||
bool CheckDecoderCanOffloadAudio();
|
||||
void DisableStateMachineAudioOffloading();
|
||||
|
||||
MediaOmxCommonReader* mReader;
|
||||
|
||||
// Offloaded audio track
|
||||
android::sp<android::MediaSource> mAudioTrack;
|
||||
|
||||
nsAutoPtr<AudioOffloadPlayerBase> mAudioOffloadPlayer;
|
||||
|
||||
// Set by Media*Reader to denote current track can be offloaded
|
||||
bool mCanOffloadAudio;
|
||||
|
||||
// Set when offload playback of current track fails in the middle and need to
|
||||
// fallback to state machine
|
||||
bool mFallbackToStateMachine;
|
||||
|
||||
// True if the media element is captured.
|
||||
bool mIsCaptured;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // MEDIA_OMX_COMMON_DECODER_H
|
@ -1,79 +0,0 @@
|
||||
/* -*- 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 "MediaOmxCommonReader.h"
|
||||
|
||||
#include <stagefright/MediaSource.h>
|
||||
|
||||
#include "AbstractMediaDecoder.h"
|
||||
#include "AudioChannelService.h"
|
||||
#include "MediaStreamSource.h"
|
||||
#include "MediaPrefs.h"
|
||||
|
||||
#ifdef MOZ_AUDIO_OFFLOAD
|
||||
#include <stagefright/Utils.h>
|
||||
#include <cutils/properties.h>
|
||||
#include <stagefright/MetaData.h>
|
||||
#endif
|
||||
|
||||
using namespace android;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
extern LazyLogModule gMediaDecoderLog;
|
||||
#define DECODER_LOG(type, msg) MOZ_LOG(gMediaDecoderLog, type, msg)
|
||||
|
||||
MediaOmxCommonReader::MediaOmxCommonReader(AbstractMediaDecoder *aDecoder)
|
||||
: MediaDecoderReader(aDecoder)
|
||||
, mStreamSource(nullptr)
|
||||
{
|
||||
mAudioChannel = dom::AudioChannelService::GetDefaultAudioChannel();
|
||||
}
|
||||
|
||||
bool MediaOmxCommonReader::IsMonoAudioEnabled()
|
||||
{
|
||||
return MediaPrefs::MonoAudio();
|
||||
}
|
||||
|
||||
#ifdef MOZ_AUDIO_OFFLOAD
|
||||
void MediaOmxCommonReader::CheckAudioOffload()
|
||||
{
|
||||
MOZ_ASSERT(OnTaskQueue());
|
||||
|
||||
char offloadProp[128];
|
||||
property_get("audio.offload.disable", offloadProp, "0");
|
||||
bool offloadDisable = atoi(offloadProp) != 0;
|
||||
if (offloadDisable) {
|
||||
return;
|
||||
}
|
||||
|
||||
sp<MediaSource> audioOffloadTrack = GetAudioOffloadTrack();
|
||||
sp<MetaData> meta = audioOffloadTrack.get()
|
||||
? audioOffloadTrack->getFormat() : nullptr;
|
||||
|
||||
// Supporting audio offload only when there is no video, no streaming
|
||||
bool hasNoVideo = !HasVideo();
|
||||
bool isNotStreaming
|
||||
= mDecoder->GetResource()->IsDataCachedToEndOfResource(0);
|
||||
|
||||
// Not much benefit in trying to offload other channel types. Most of them
|
||||
// aren't supported and also duration would be less than a minute
|
||||
bool isTypeMusic = mAudioChannel == dom::AudioChannel::Content;
|
||||
|
||||
DECODER_LOG(LogLevel::Debug, ("%s meta %p, no video %d, no streaming %d,"
|
||||
" channel type %d", __FUNCTION__, meta.get(), hasNoVideo,
|
||||
isNotStreaming, mAudioChannel));
|
||||
|
||||
if ((meta.get()) && hasNoVideo && isNotStreaming && isTypeMusic &&
|
||||
canOffloadStream(meta, false, false, AUDIO_STREAM_MUSIC) &&
|
||||
!IsMonoAudioEnabled()) {
|
||||
DECODER_LOG(LogLevel::Debug, ("Can offload this audio stream"));
|
||||
mDecoder->SetPlatformCanOffloadAudio(true);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace mozilla
|
@ -1,60 +0,0 @@
|
||||
/* -*- 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/. */
|
||||
|
||||
#ifndef MEDIA_OMX_COMMON_READER_H
|
||||
#define MEDIA_OMX_COMMON_READER_H
|
||||
|
||||
#include "MediaDecoderReader.h"
|
||||
|
||||
#include <utils/RefBase.h>
|
||||
|
||||
#include "mozilla/dom/AudioChannelBinding.h"
|
||||
|
||||
namespace android {
|
||||
struct MOZ_EXPORT MediaSource;
|
||||
class MediaStreamSource;
|
||||
} // namespace android
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class AbstractMediaDecoder;
|
||||
|
||||
class MediaOmxCommonReader : public MediaDecoderReader
|
||||
{
|
||||
public:
|
||||
typedef MozPromise<bool /* aIgnored */, bool /* aIgnored */, /* IsExclusive = */ true> MediaResourcePromise;
|
||||
|
||||
MediaOmxCommonReader(AbstractMediaDecoder* aDecoder);
|
||||
|
||||
void SetAudioChannel(dom::AudioChannel aAudioChannel) {
|
||||
mAudioChannel = aAudioChannel;
|
||||
}
|
||||
|
||||
virtual android::sp<android::MediaSource> GetAudioOffloadTrack() = 0;
|
||||
|
||||
#ifdef MOZ_AUDIO_OFFLOAD
|
||||
// Check whether it is possible to offload current audio track. This access
|
||||
// canOffloadStream() from libStageFright Utils.cpp, which is not there in
|
||||
// ANDROID_VERSION < 19
|
||||
void CheckAudioOffload();
|
||||
#endif
|
||||
|
||||
protected:
|
||||
dom::AudioChannel mAudioChannel;
|
||||
// Weak reference to the MediaStreamSource that will be created by either
|
||||
// MediaOmxReader or MediaCodecReader.
|
||||
android::MediaStreamSource* mStreamSource;
|
||||
// Get value from the preferece, if true, we stop the audio offload.
|
||||
bool IsMonoAudioEnabled();
|
||||
|
||||
private:
|
||||
virtual bool HasAudio() = 0;
|
||||
virtual bool HasVideo() = 0;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // MEDIA_OMX_COMMON_READER_H
|
@ -1,33 +0,0 @@
|
||||
/* -*- 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 "MediaOmxDecoder.h"
|
||||
#include "MediaOmxReader.h"
|
||||
#include "MediaDecoderStateMachine.h"
|
||||
|
||||
using namespace android;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
MediaDecoder*
|
||||
MediaOmxDecoder::Clone(MediaDecoderOwner* aOwner)
|
||||
{
|
||||
return new MediaOmxDecoder(aOwner);
|
||||
}
|
||||
|
||||
MediaOmxCommonReader*
|
||||
MediaOmxDecoder::CreateReader()
|
||||
{
|
||||
return new MediaOmxReader(this);
|
||||
}
|
||||
|
||||
MediaDecoderStateMachine*
|
||||
MediaOmxDecoder::CreateStateMachineFromReader(MediaOmxCommonReader* aReader)
|
||||
{
|
||||
return new MediaDecoderStateMachine(this, aReader);
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
@ -1,25 +0,0 @@
|
||||
/* -*- 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/. */
|
||||
#if !defined(MediaOmxDecoder_h_)
|
||||
#define MediaOmxDecoder_h_
|
||||
|
||||
#include "MediaOmxCommonDecoder.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class MediaOmxDecoder : public MediaOmxCommonDecoder
|
||||
{
|
||||
public:
|
||||
explicit MediaOmxDecoder(MediaDecoderOwner* aOwner)
|
||||
: MediaOmxCommonDecoder(aOwner) {}
|
||||
virtual MediaDecoder* Clone(MediaDecoderOwner* aOwner);
|
||||
virtual MediaOmxCommonReader* CreateReader();
|
||||
virtual MediaDecoderStateMachine* CreateStateMachineFromReader(MediaOmxCommonReader* aReader);
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif
|
@ -1,622 +0,0 @@
|
||||
/* -*- 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 "MediaOmxReader.h"
|
||||
|
||||
#include "MediaDecoderStateMachine.h"
|
||||
#include "mozilla/TimeStamp.h"
|
||||
#include "MediaResource.h"
|
||||
#include "VideoUtils.h"
|
||||
#include "MediaOmxDecoder.h"
|
||||
#include "AbstractMediaDecoder.h"
|
||||
#include "AudioChannelService.h"
|
||||
#include "OmxDecoder.h"
|
||||
#include "MPAPI.h"
|
||||
#include "gfx2DGlue.h"
|
||||
#include "MediaStreamSource.h"
|
||||
#include "VideoFrameContainer.h"
|
||||
|
||||
#define MAX_DROPPED_FRAMES 25
|
||||
// Try not to spend more than this much time in a single call to DecodeVideoFrame.
|
||||
#define MAX_VIDEO_DECODE_SECONDS 0.1
|
||||
|
||||
using namespace mozilla::gfx;
|
||||
using namespace mozilla::media;
|
||||
using namespace android;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
extern LazyLogModule gMediaDecoderLog;
|
||||
#define DECODER_LOG(type, msg) MOZ_LOG(gMediaDecoderLog, type, msg)
|
||||
|
||||
class MediaOmxReader::ProcessCachedDataTask : public Runnable
|
||||
{
|
||||
public:
|
||||
ProcessCachedDataTask(MediaOmxReader* aOmxReader, int64_t aOffset)
|
||||
: mOmxReader(aOmxReader),
|
||||
mOffset(aOffset)
|
||||
{ }
|
||||
|
||||
NS_IMETHOD Run() override
|
||||
{
|
||||
MOZ_ASSERT(!NS_IsMainThread());
|
||||
MOZ_ASSERT(mOmxReader.get());
|
||||
mOmxReader->ProcessCachedData(mOffset);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
RefPtr<MediaOmxReader> mOmxReader;
|
||||
int64_t mOffset;
|
||||
};
|
||||
|
||||
// When loading an MP3 stream from a file, we need to parse the file's
|
||||
// content to find its duration. Reading files of 100 MiB or more can
|
||||
// delay the player app noticably, so the file is read and decoded in
|
||||
// smaller chunks.
|
||||
//
|
||||
// We first read on the decode thread, but parsing must be done on the
|
||||
// main thread. After we read the file's initial MiBs in the decode
|
||||
// thread, an instance of this class is scheduled to the main thread for
|
||||
// parsing the MP3 stream. The decode thread waits until it has finished.
|
||||
//
|
||||
// If there is more data available from the file, the runnable dispatches
|
||||
// a task to the IO thread for retrieving the next chunk of data, and
|
||||
// the IO task dispatches a runnable to the main thread for parsing the
|
||||
// data. This goes on until all of the MP3 file has been parsed.
|
||||
|
||||
class MediaOmxReader::NotifyDataArrivedRunnable : public Runnable
|
||||
{
|
||||
public:
|
||||
NotifyDataArrivedRunnable(MediaOmxReader* aOmxReader,
|
||||
uint64_t aLength,
|
||||
int64_t aOffset, uint64_t aFullLength)
|
||||
: mOmxReader(aOmxReader),
|
||||
mLength(aLength),
|
||||
mOffset(aOffset),
|
||||
mFullLength(aFullLength)
|
||||
{
|
||||
MOZ_ASSERT(mOmxReader.get());
|
||||
}
|
||||
|
||||
NS_IMETHOD Run() override
|
||||
{
|
||||
MOZ_ASSERT(mOmxReader->OnTaskQueue());
|
||||
NotifyDataArrived();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
void NotifyDataArrived()
|
||||
{
|
||||
if (mOmxReader->IsShutdown()) {
|
||||
return;
|
||||
}
|
||||
|
||||
while (mLength) {
|
||||
uint32_t length = std::min<uint64_t>(mLength, UINT32_MAX);
|
||||
mOmxReader->NotifyDataArrived();
|
||||
mLength -= length;
|
||||
mOffset += length;
|
||||
}
|
||||
|
||||
if (static_cast<uint64_t>(mOffset) < mFullLength) {
|
||||
// We cannot read data in the main thread because it
|
||||
// might block for too long. Instead we post an IO task
|
||||
// to the IO thread if there is more data available.
|
||||
RefPtr<Runnable> task = new ProcessCachedDataTask(mOmxReader.get(), mOffset);
|
||||
XRE_GetIOMessageLoop()->PostTask(task.forget());
|
||||
}
|
||||
}
|
||||
|
||||
RefPtr<MediaOmxReader> mOmxReader;
|
||||
uint64_t mLength;
|
||||
int64_t mOffset;
|
||||
uint64_t mFullLength;
|
||||
};
|
||||
|
||||
MediaOmxReader::MediaOmxReader(AbstractMediaDecoder *aDecoder)
|
||||
: MediaOmxCommonReader(aDecoder)
|
||||
, mShutdownMutex("MediaOmxReader.Shutdown")
|
||||
, mHasVideo(false)
|
||||
, mHasAudio(false)
|
||||
, mVideoSeekTimeUs(-1)
|
||||
, mAudioSeekTimeUs(-1)
|
||||
, mLastParserDuration(-1)
|
||||
, mSkipCount(0)
|
||||
, mIsShutdown(false)
|
||||
, mMP3FrameParser(-1)
|
||||
{
|
||||
mAudioChannel = dom::AudioChannelService::GetDefaultAudioChannel();
|
||||
}
|
||||
|
||||
MediaOmxReader::~MediaOmxReader()
|
||||
{
|
||||
}
|
||||
|
||||
already_AddRefed<AbstractMediaDecoder>
|
||||
MediaOmxReader::SafeGetDecoder() {
|
||||
RefPtr<AbstractMediaDecoder> decoder;
|
||||
MutexAutoLock lock(mShutdownMutex);
|
||||
if (!mIsShutdown) {
|
||||
decoder = mDecoder;
|
||||
}
|
||||
return decoder.forget();
|
||||
}
|
||||
|
||||
void MediaOmxReader::ReleaseDecoder()
|
||||
{
|
||||
if (mOmxDecoder.get()) {
|
||||
mOmxDecoder->ReleaseDecoder();
|
||||
}
|
||||
mOmxDecoder.clear();
|
||||
}
|
||||
|
||||
RefPtr<ShutdownPromise>
|
||||
MediaOmxReader::Shutdown()
|
||||
{
|
||||
{
|
||||
MutexAutoLock lock(mShutdownMutex);
|
||||
mIsShutdown = true;
|
||||
}
|
||||
|
||||
RefPtr<ShutdownPromise> p = MediaDecoderReader::Shutdown();
|
||||
|
||||
// Wait for the superclass to finish tearing things down before releasing
|
||||
// the decoder on the main thread.
|
||||
p->Then(AbstractThread::MainThread(), __func__, this, &MediaOmxReader::ReleaseDecoder, &MediaOmxReader::ReleaseDecoder);
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
void MediaOmxReader::ReleaseResources()
|
||||
{
|
||||
mMediaResourceRequest.DisconnectIfExists();
|
||||
mMetadataPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_METADATA_ERR, __func__);
|
||||
|
||||
ResetDecode();
|
||||
// Before freeing a video codec, all video buffers needed to be released
|
||||
// even from graphics pipeline.
|
||||
VideoFrameContainer* container = mDecoder->GetVideoFrameContainer();
|
||||
if (container) {
|
||||
container->ClearCurrentFrame();
|
||||
}
|
||||
if (mOmxDecoder.get()) {
|
||||
mOmxDecoder->ReleaseMediaResources();
|
||||
}
|
||||
}
|
||||
|
||||
nsresult MediaOmxReader::InitOmxDecoder()
|
||||
{
|
||||
if (!mOmxDecoder.get()) {
|
||||
//register sniffers, if they are not registered in this process.
|
||||
DataSource::RegisterDefaultSniffers();
|
||||
|
||||
sp<DataSource> dataSource = new MediaStreamSource(mDecoder->GetResource());
|
||||
dataSource->initCheck();
|
||||
|
||||
mExtractor = MediaExtractor::Create(dataSource);
|
||||
if (!mExtractor.get()) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
mOmxDecoder = new OmxDecoder(mDecoder, OwnerThread());
|
||||
if (!mOmxDecoder->Init(mExtractor)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
mStreamSource = static_cast<MediaStreamSource*>(dataSource.get());
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
RefPtr<MediaDecoderReader::MetadataPromise>
|
||||
MediaOmxReader::AsyncReadMetadata()
|
||||
{
|
||||
MOZ_ASSERT(OnTaskQueue());
|
||||
EnsureActive();
|
||||
|
||||
// Initialize the internal OMX Decoder.
|
||||
nsresult rv = InitOmxDecoder();
|
||||
if (NS_FAILED(rv)) {
|
||||
return MediaDecoderReader::MetadataPromise::CreateAndReject(
|
||||
NS_ERROR_DOM_MEDIA_METADATA_ERR, __func__);
|
||||
}
|
||||
|
||||
bool isMP3 = mDecoder->GetResource()->GetContentType().EqualsASCII(AUDIO_MP3);
|
||||
if (isMP3) {
|
||||
// When read sdcard's file on b2g platform at constructor,
|
||||
// the mDecoder->GetResource()->GetLength() would return -1.
|
||||
// Delay set the total duration on this function.
|
||||
mMP3FrameParser.SetLength(mDecoder->GetResource()->GetLength());
|
||||
ProcessCachedData(0);
|
||||
}
|
||||
|
||||
RefPtr<MediaDecoderReader::MetadataPromise> p = mMetadataPromise.Ensure(__func__);
|
||||
|
||||
RefPtr<MediaOmxReader> self = this;
|
||||
mMediaResourceRequest.Begin(mOmxDecoder->AllocateMediaResources()
|
||||
->Then(OwnerThread(), __func__,
|
||||
[self] (bool) -> void {
|
||||
self->mMediaResourceRequest.Complete();
|
||||
self->HandleResourceAllocated();
|
||||
}, [self] (bool) -> void {
|
||||
self->mMediaResourceRequest.Complete();
|
||||
self->mMetadataPromise.Reject(NS_ERROR_DOM_MEDIA_METADATA_ERR, __func__);
|
||||
}));
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
void MediaOmxReader::HandleResourceAllocated()
|
||||
{
|
||||
EnsureActive();
|
||||
|
||||
// After resources are available, set the metadata.
|
||||
if (!mOmxDecoder->EnsureMetadata()) {
|
||||
mMetadataPromise.Reject(NS_ERROR_DOM_MEDIA_METADATA_ERR, __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
bool isMP3 = mDecoder->GetResource()->GetContentType().EqualsASCII(AUDIO_MP3);
|
||||
if (isMP3 && mMP3FrameParser.IsMP3()) {
|
||||
// Check if the MP3 frame parser found a duration.
|
||||
mLastParserDuration = mMP3FrameParser.GetDuration();
|
||||
}
|
||||
|
||||
if (mLastParserDuration >= 0) {
|
||||
// Prefer the parser duration if we have it.
|
||||
mInfo.mMetadataDuration = Some(TimeUnit::FromMicroseconds(mLastParserDuration));
|
||||
} else {
|
||||
// MP3 parser failed to find a duration.
|
||||
// Set the total duration (the max of the audio and video track).
|
||||
int64_t durationUs;
|
||||
mOmxDecoder->GetDuration(&durationUs);
|
||||
if (durationUs) {
|
||||
mInfo.mMetadataDuration = Some(TimeUnit::FromMicroseconds(durationUs));
|
||||
}
|
||||
}
|
||||
|
||||
if (mOmxDecoder->HasVideo()) {
|
||||
int32_t displayWidth, displayHeight, width, height;
|
||||
mOmxDecoder->GetVideoParameters(&displayWidth, &displayHeight,
|
||||
&width, &height);
|
||||
nsIntRect pictureRect(0, 0, width, height);
|
||||
|
||||
// Validate the container-reported frame and pictureRect sizes. This ensures
|
||||
// that our video frame creation code doesn't overflow.
|
||||
nsIntSize displaySize(displayWidth, displayHeight);
|
||||
nsIntSize frameSize(width, height);
|
||||
if (!IsValidVideoRegion(frameSize, pictureRect, displaySize)) {
|
||||
mMetadataPromise.Reject(NS_ERROR_DOM_MEDIA_METADATA_ERR, __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
// Video track's frame sizes will not overflow. Activate the video track.
|
||||
mHasVideo = true;
|
||||
mInfo.mVideo.mDisplay = displaySize;
|
||||
mPicture = pictureRect;
|
||||
mInitialFrame = frameSize;
|
||||
VideoFrameContainer* container = mDecoder->GetVideoFrameContainer();
|
||||
if (container) {
|
||||
container->ClearCurrentFrame(IntSize(displaySize.width, displaySize.height));
|
||||
}
|
||||
}
|
||||
|
||||
if (mOmxDecoder->HasAudio()) {
|
||||
int32_t numChannels, sampleRate;
|
||||
mOmxDecoder->GetAudioParameters(&numChannels, &sampleRate);
|
||||
mHasAudio = true;
|
||||
mInfo.mAudio.mChannels = numChannels;
|
||||
mInfo.mAudio.mRate = sampleRate;
|
||||
}
|
||||
|
||||
mInfo.mMediaSeekable = mExtractor->flags() & MediaExtractor::CAN_SEEK;
|
||||
|
||||
RefPtr<MetadataHolder> metadata = new MetadataHolder();
|
||||
metadata->mInfo = mInfo;
|
||||
metadata->mTags = nullptr;
|
||||
|
||||
#ifdef MOZ_AUDIO_OFFLOAD
|
||||
CheckAudioOffload();
|
||||
#endif
|
||||
|
||||
mMetadataPromise.Resolve(metadata, __func__);
|
||||
}
|
||||
|
||||
bool MediaOmxReader::DecodeVideoFrame(bool &aKeyframeSkip,
|
||||
int64_t aTimeThreshold)
|
||||
{
|
||||
MOZ_ASSERT(OnTaskQueue());
|
||||
EnsureActive();
|
||||
|
||||
// Record number of frames decoded and parsed. Automatically update the
|
||||
// stats counters using the AutoNotifyDecoded stack-based class.
|
||||
AbstractMediaDecoder::AutoNotifyDecoded a(mDecoder);
|
||||
|
||||
bool doSeek = mVideoSeekTimeUs != -1;
|
||||
if (doSeek) {
|
||||
aTimeThreshold = mVideoSeekTimeUs;
|
||||
}
|
||||
|
||||
TimeStamp start = TimeStamp::Now();
|
||||
|
||||
// Read next frame. Don't let this loop run for too long.
|
||||
while ((TimeStamp::Now() - start) < TimeDuration::FromSeconds(MAX_VIDEO_DECODE_SECONDS)) {
|
||||
MPAPI::VideoFrame frame;
|
||||
frame.mGraphicBuffer = nullptr;
|
||||
frame.mShouldSkip = false;
|
||||
if (!mOmxDecoder->ReadVideo(&frame, aTimeThreshold, aKeyframeSkip, doSeek)) {
|
||||
return false;
|
||||
}
|
||||
doSeek = false;
|
||||
mVideoSeekTimeUs = -1;
|
||||
|
||||
// Ignore empty buffer which stagefright media read will sporadically return
|
||||
if (frame.mSize == 0 && !frame.mGraphicBuffer) {
|
||||
continue;
|
||||
}
|
||||
|
||||
a.mStats.mParsedFrames++;
|
||||
if (frame.mShouldSkip && mSkipCount < MAX_DROPPED_FRAMES) {
|
||||
mSkipCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
mSkipCount = 0;
|
||||
|
||||
aKeyframeSkip = false;
|
||||
|
||||
IntRect picture = mPicture;
|
||||
if (frame.Y.mWidth != mInitialFrame.width ||
|
||||
frame.Y.mHeight != mInitialFrame.height) {
|
||||
|
||||
// Frame size is different from what the container reports. This is legal,
|
||||
// and we will preserve the ratio of the crop rectangle as it
|
||||
// was reported relative to the picture size reported by the container.
|
||||
picture.x = (mPicture.x * frame.Y.mWidth) / mInitialFrame.width;
|
||||
picture.y = (mPicture.y * frame.Y.mHeight) / mInitialFrame.height;
|
||||
picture.width = (frame.Y.mWidth * mPicture.width) / mInitialFrame.width;
|
||||
picture.height = (frame.Y.mHeight * mPicture.height) / mInitialFrame.height;
|
||||
}
|
||||
|
||||
// This is the approximate byte position in the stream.
|
||||
int64_t pos = mStreamSource ? mStreamSource->Tell() : 0;
|
||||
|
||||
RefPtr<VideoData> v;
|
||||
if (!frame.mGraphicBuffer) {
|
||||
|
||||
VideoData::YCbCrBuffer b;
|
||||
b.mPlanes[0].mData = static_cast<uint8_t *>(frame.Y.mData);
|
||||
b.mPlanes[0].mStride = frame.Y.mStride;
|
||||
b.mPlanes[0].mHeight = frame.Y.mHeight;
|
||||
b.mPlanes[0].mWidth = frame.Y.mWidth;
|
||||
b.mPlanes[0].mOffset = frame.Y.mOffset;
|
||||
b.mPlanes[0].mSkip = frame.Y.mSkip;
|
||||
|
||||
b.mPlanes[1].mData = static_cast<uint8_t *>(frame.Cb.mData);
|
||||
b.mPlanes[1].mStride = frame.Cb.mStride;
|
||||
b.mPlanes[1].mHeight = frame.Cb.mHeight;
|
||||
b.mPlanes[1].mWidth = frame.Cb.mWidth;
|
||||
b.mPlanes[1].mOffset = frame.Cb.mOffset;
|
||||
b.mPlanes[1].mSkip = frame.Cb.mSkip;
|
||||
|
||||
b.mPlanes[2].mData = static_cast<uint8_t *>(frame.Cr.mData);
|
||||
b.mPlanes[2].mStride = frame.Cr.mStride;
|
||||
b.mPlanes[2].mHeight = frame.Cr.mHeight;
|
||||
b.mPlanes[2].mWidth = frame.Cr.mWidth;
|
||||
b.mPlanes[2].mOffset = frame.Cr.mOffset;
|
||||
b.mPlanes[2].mSkip = frame.Cr.mSkip;
|
||||
|
||||
v = VideoData::CreateAndCopyData(mInfo.mVideo,
|
||||
mDecoder->GetImageContainer(),
|
||||
pos,
|
||||
frame.mTimeUs,
|
||||
1, // We don't know the duration.
|
||||
b,
|
||||
frame.mKeyFrame,
|
||||
-1,
|
||||
picture);
|
||||
} else {
|
||||
v = VideoData::CreateAndCopyIntoTextureClient(
|
||||
mInfo.mVideo,
|
||||
pos,
|
||||
frame.mTimeUs,
|
||||
1, // We don't know the duration.
|
||||
frame.mGraphicBuffer,
|
||||
frame.mKeyFrame,
|
||||
-1,
|
||||
picture);
|
||||
}
|
||||
|
||||
if (!v) {
|
||||
NS_WARNING("Unable to create VideoData");
|
||||
return false;
|
||||
}
|
||||
|
||||
a.mStats.mDecodedFrames++;
|
||||
NS_ASSERTION(a.mStats.mDecodedFrames <= a.mStats.mParsedFrames, "Expect to decode fewer frames than parsed in OMX decoder...");
|
||||
|
||||
mVideoQueue.Push(v);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MediaOmxReader::NotifyDataArrivedInternal()
|
||||
{
|
||||
MOZ_ASSERT(OnTaskQueue());
|
||||
RefPtr<AbstractMediaDecoder> decoder = SafeGetDecoder();
|
||||
if (!decoder) { // reader has shut down
|
||||
return;
|
||||
}
|
||||
if (HasVideo()) {
|
||||
return;
|
||||
}
|
||||
if (!mMP3FrameParser.NeedsData()) {
|
||||
return;
|
||||
}
|
||||
|
||||
AutoPinned<MediaResource> resource(mDecoder->GetResource());
|
||||
MediaByteRangeSet byteRanges;
|
||||
nsresult rv = resource->GetCachedRanges(byteRanges);
|
||||
|
||||
if (NS_FAILED(rv)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (byteRanges == mLastCachedRanges) {
|
||||
return;
|
||||
}
|
||||
MediaByteRangeSet intervals = byteRanges - mLastCachedRanges;
|
||||
mLastCachedRanges = byteRanges;
|
||||
|
||||
for (const auto& interval : intervals) {
|
||||
RefPtr<MediaByteBuffer> bytes =
|
||||
resource->MediaReadAt(interval.mStart, interval.Length());
|
||||
NS_ENSURE_TRUE_VOID(bytes);
|
||||
mMP3FrameParser.Parse(bytes->Elements(), interval.Length(), interval.mStart);
|
||||
if (!mMP3FrameParser.IsMP3()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
int64_t duration = mMP3FrameParser.GetDuration();
|
||||
if (duration != mLastParserDuration) {
|
||||
mLastParserDuration = duration;
|
||||
decoder->DispatchUpdateEstimatedMediaDuration(mLastParserDuration);
|
||||
}
|
||||
}
|
||||
|
||||
bool MediaOmxReader::DecodeAudioData()
|
||||
{
|
||||
MOZ_ASSERT(OnTaskQueue());
|
||||
EnsureActive();
|
||||
|
||||
// This is the approximate byte position in the stream.
|
||||
int64_t pos = mStreamSource ? mStreamSource->Tell() : 0;
|
||||
|
||||
// Read next frame
|
||||
MPAPI::AudioFrame source;
|
||||
if (!mOmxDecoder->ReadAudio(&source, mAudioSeekTimeUs)) {
|
||||
return false;
|
||||
}
|
||||
mAudioSeekTimeUs = -1;
|
||||
|
||||
// Ignore empty buffer which stagefright media read will sporadically return
|
||||
if (source.mSize == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t frames = source.mSize / (source.mAudioChannels *
|
||||
sizeof(AudioDataValue));
|
||||
|
||||
typedef AudioCompactor::NativeCopy OmxCopy;
|
||||
return mAudioCompactor.Push(pos,
|
||||
source.mTimeUs,
|
||||
source.mAudioSampleRate,
|
||||
frames,
|
||||
source.mAudioChannels,
|
||||
OmxCopy(static_cast<uint8_t *>(source.mData),
|
||||
source.mSize,
|
||||
source.mAudioChannels));
|
||||
}
|
||||
|
||||
RefPtr<MediaDecoderReader::SeekPromise>
|
||||
MediaOmxReader::Seek(SeekTarget aTarget, int64_t aEndTime)
|
||||
{
|
||||
MOZ_ASSERT(OnTaskQueue());
|
||||
EnsureActive();
|
||||
RefPtr<SeekPromise> p = mSeekPromise.Ensure(__func__);
|
||||
|
||||
if (mHasAudio && mHasVideo) {
|
||||
// The OMXDecoder seeks/demuxes audio and video streams separately. So if
|
||||
// we seek both audio and video to aTarget, the audio stream can typically
|
||||
// seek closer to the seek target, since typically every audio block is
|
||||
// a sync point, whereas for video there are only keyframes once every few
|
||||
// seconds. So if we have both audio and video, we must seek the video
|
||||
// stream to the preceeding keyframe first, get the stream time, and then
|
||||
// seek the audio stream to match the video stream's time. Otherwise, the
|
||||
// audio and video streams won't be in sync after the seek.
|
||||
mVideoSeekTimeUs = aTarget.GetTime().ToMicroseconds();
|
||||
|
||||
RefPtr<MediaOmxReader> self = this;
|
||||
mSeekRequest.Begin(DecodeToFirstVideoData()->Then(OwnerThread(), __func__, [self] (MediaData* v) {
|
||||
self->mSeekRequest.Complete();
|
||||
self->mAudioSeekTimeUs = v->mTime;
|
||||
self->mSeekPromise.Resolve(media::TimeUnit::FromMicroseconds(self->mAudioSeekTimeUs), __func__);
|
||||
}, [self, aTarget] () {
|
||||
self->mSeekRequest.Complete();
|
||||
self->mAudioSeekTimeUs = aTarget.GetTime().ToMicroseconds();
|
||||
self->mSeekPromise.Resolve(aTarget.GetTime(), __func__);
|
||||
}));
|
||||
} else {
|
||||
mAudioSeekTimeUs = mVideoSeekTimeUs = aTarget.GetTime().ToMicroseconds();
|
||||
mSeekPromise.Resolve(aTarget.GetTime(), __func__);
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
void MediaOmxReader::SetIdle() {
|
||||
if (!mOmxDecoder.get()) {
|
||||
return;
|
||||
}
|
||||
mOmxDecoder->Pause();
|
||||
}
|
||||
|
||||
void MediaOmxReader::EnsureActive() {
|
||||
if (!mOmxDecoder.get()) {
|
||||
return;
|
||||
}
|
||||
DebugOnly<nsresult> result = mOmxDecoder->Play();
|
||||
NS_ASSERTION(result == NS_OK, "OmxDecoder should be in play state to continue decoding");
|
||||
}
|
||||
|
||||
int64_t MediaOmxReader::ProcessCachedData(int64_t aOffset)
|
||||
{
|
||||
// Could run on decoder thread or IO thread.
|
||||
RefPtr<AbstractMediaDecoder> decoder = SafeGetDecoder();
|
||||
if (!decoder) { // reader has shut down
|
||||
return -1;
|
||||
}
|
||||
// We read data in chunks of 32 KiB. We can reduce this
|
||||
// value if media, such as sdcards, is too slow.
|
||||
// Because of SD card's slowness, need to keep sReadSize to small size.
|
||||
// See Bug 914870.
|
||||
static const int64_t sReadSize = 32 * 1024;
|
||||
|
||||
NS_ASSERTION(!NS_IsMainThread(), "Should not be on main thread.");
|
||||
|
||||
MOZ_ASSERT(decoder->GetResource());
|
||||
int64_t resourceLength = decoder->GetResource()->GetCachedDataEnd(0);
|
||||
NS_ENSURE_TRUE(resourceLength >= 0, -1);
|
||||
|
||||
if (aOffset >= resourceLength) {
|
||||
return 0; // Cache is empty, nothing to do
|
||||
}
|
||||
|
||||
int64_t bufferLength = std::min<int64_t>(resourceLength-aOffset, sReadSize);
|
||||
RefPtr<NotifyDataArrivedRunnable> runnable(
|
||||
new NotifyDataArrivedRunnable(this, bufferLength, aOffset, resourceLength));
|
||||
|
||||
if (OnTaskQueue()) {
|
||||
runnable->Run();
|
||||
} else {
|
||||
OwnerThread()->Dispatch(runnable.forget());
|
||||
}
|
||||
|
||||
return resourceLength - aOffset - bufferLength;
|
||||
}
|
||||
|
||||
android::sp<android::MediaSource> MediaOmxReader::GetAudioOffloadTrack()
|
||||
{
|
||||
if (!mOmxDecoder.get()) {
|
||||
return nullptr;
|
||||
}
|
||||
return mOmxDecoder->GetAudioOffloadTrack();
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
@ -1,127 +0,0 @@
|
||||
/* -*- 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/. */
|
||||
#if !defined(MediaOmxReader_h_)
|
||||
#define MediaOmxReader_h_
|
||||
|
||||
#include "MediaOmxCommonReader.h"
|
||||
#include "MediaResource.h"
|
||||
#include "MediaDecoderReader.h"
|
||||
#include "nsMimeTypes.h"
|
||||
#include "MP3FrameParser.h"
|
||||
#include "nsRect.h"
|
||||
|
||||
#include <ui/GraphicBuffer.h>
|
||||
#include <stagefright/MediaSource.h>
|
||||
|
||||
namespace android {
|
||||
class OmxDecoder;
|
||||
class MOZ_EXPORT MediaExtractor;
|
||||
}
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class AbstractMediaDecoder;
|
||||
|
||||
class MediaOmxReader : public MediaOmxCommonReader
|
||||
{
|
||||
typedef MediaOmxCommonReader::MediaResourcePromise MediaResourcePromise;
|
||||
|
||||
// This mutex is held when accessing the mIsShutdown variable, which is
|
||||
// modified on the decode task queue and read on main and IO threads.
|
||||
Mutex mShutdownMutex;
|
||||
nsCString mType;
|
||||
bool mHasVideo;
|
||||
bool mHasAudio;
|
||||
nsIntRect mPicture;
|
||||
nsIntSize mInitialFrame;
|
||||
int64_t mVideoSeekTimeUs;
|
||||
int64_t mAudioSeekTimeUs;
|
||||
int64_t mLastParserDuration;
|
||||
int32_t mSkipCount;
|
||||
// If mIsShutdown is false, and mShutdownMutex is held, then
|
||||
// AbstractMediaDecoder::mDecoder will be non-null.
|
||||
bool mIsShutdown;
|
||||
MozPromiseHolder<MediaDecoderReader::MetadataPromise> mMetadataPromise;
|
||||
MozPromiseRequestHolder<MediaResourcePromise> mMediaResourceRequest;
|
||||
|
||||
MozPromiseHolder<MediaDecoderReader::SeekPromise> mSeekPromise;
|
||||
MozPromiseRequestHolder<MediaDecoderReader::MediaDataPromise> mSeekRequest;
|
||||
protected:
|
||||
android::sp<android::OmxDecoder> mOmxDecoder;
|
||||
android::sp<android::MediaExtractor> mExtractor;
|
||||
MP3FrameParser mMP3FrameParser;
|
||||
|
||||
// Called by ReadMetadata() during MediaDecoderStateMachine::DecodeMetadata()
|
||||
// on decode thread. It create and initialize the OMX decoder including
|
||||
// setting up custom extractor. The extractor provide the essential
|
||||
// information used for creating OMX decoder such as video/audio codec.
|
||||
virtual nsresult InitOmxDecoder();
|
||||
|
||||
// Called inside DecodeVideoFrame, DecodeAudioData, ReadMetadata and Seek
|
||||
// to activate the decoder automatically.
|
||||
virtual void EnsureActive();
|
||||
|
||||
virtual void HandleResourceAllocated();
|
||||
|
||||
public:
|
||||
MediaOmxReader(AbstractMediaDecoder* aDecoder);
|
||||
~MediaOmxReader();
|
||||
|
||||
protected:
|
||||
void NotifyDataArrivedInternal() override;
|
||||
public:
|
||||
|
||||
nsresult ResetDecode(
|
||||
TrackSet aTracks = TrackSet(TrackInfo::kAudioTrack,
|
||||
TrackInfo::kVideoTrack)) override
|
||||
{
|
||||
mSeekRequest.DisconnectIfExists();
|
||||
mSeekPromise.RejectIfExists(NS_OK, __func__);
|
||||
return MediaDecoderReader::ResetDecode(aTracks);
|
||||
}
|
||||
|
||||
bool DecodeAudioData() override;
|
||||
bool DecodeVideoFrame(bool &aKeyframeSkip, int64_t aTimeThreshold) override;
|
||||
|
||||
void ReleaseResources() override;
|
||||
|
||||
RefPtr<MediaDecoderReader::MetadataPromise> AsyncReadMetadata() override;
|
||||
|
||||
RefPtr<SeekPromise>
|
||||
Seek(SeekTarget aTarget, int64_t aEndTime) override;
|
||||
|
||||
void SetIdle() override;
|
||||
|
||||
RefPtr<ShutdownPromise> Shutdown() override;
|
||||
|
||||
android::sp<android::MediaSource> GetAudioOffloadTrack();
|
||||
|
||||
// This method is intended only for private use but public only for
|
||||
// MozPromise::InvokeCallbackMethod().
|
||||
void ReleaseDecoder();
|
||||
|
||||
private:
|
||||
class ProcessCachedDataTask;
|
||||
class NotifyDataArrivedRunnable;
|
||||
|
||||
bool HasAudio() override { return mHasAudio; }
|
||||
bool HasVideo() override { return mHasVideo; }
|
||||
|
||||
bool IsShutdown() {
|
||||
MutexAutoLock lock(mShutdownMutex);
|
||||
return mIsShutdown;
|
||||
}
|
||||
|
||||
int64_t ProcessCachedData(int64_t aOffset);
|
||||
|
||||
already_AddRefed<AbstractMediaDecoder> SafeGetDecoder();
|
||||
|
||||
MediaByteRangeSet mLastCachedRanges;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif
|
@ -1,71 +0,0 @@
|
||||
/* -*- 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 "MediaStreamSource.h"
|
||||
|
||||
#include <utils/threads.h>
|
||||
|
||||
#include "nsISeekableStream.h"
|
||||
|
||||
namespace android {
|
||||
|
||||
MediaStreamSource::MediaStreamSource(MediaResource *aResource)
|
||||
: mResource(aResource)
|
||||
{
|
||||
}
|
||||
|
||||
MediaStreamSource::~MediaStreamSource()
|
||||
{
|
||||
}
|
||||
|
||||
status_t MediaStreamSource::initCheck() const
|
||||
{
|
||||
return OK;
|
||||
}
|
||||
|
||||
ssize_t MediaStreamSource::readAt(off64_t offset, void *data, size_t size)
|
||||
{
|
||||
char *ptr = static_cast<char *>(data);
|
||||
size_t todo = size;
|
||||
while (todo > 0) {
|
||||
Mutex::Autolock autoLock(mLock);
|
||||
uint32_t bytesRead;
|
||||
if ((offset != mResource.Tell() &&
|
||||
NS_FAILED(mResource.Seek(nsISeekableStream::NS_SEEK_SET, offset))) ||
|
||||
NS_FAILED(mResource.Read(ptr, todo, &bytesRead))) {
|
||||
return ERROR_IO;
|
||||
}
|
||||
|
||||
if (bytesRead == 0) {
|
||||
return size - todo;
|
||||
}
|
||||
|
||||
offset += bytesRead;
|
||||
todo -= bytesRead;
|
||||
ptr += bytesRead;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
status_t MediaStreamSource::getSize(off64_t *size)
|
||||
{
|
||||
uint64_t length = mResource.GetLength();
|
||||
if (length == static_cast<uint64_t>(-1))
|
||||
return ERROR_UNSUPPORTED;
|
||||
|
||||
*size = length;
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
int64_t
|
||||
MediaStreamSource::Tell()
|
||||
{
|
||||
Mutex::Autolock autoLock(mLock);
|
||||
return mResource.Tell();
|
||||
}
|
||||
|
||||
} // namespace android
|
@ -1,47 +0,0 @@
|
||||
/* -*- 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/. */
|
||||
|
||||
#ifndef MEDIA_STREAM_SOURCE_H
|
||||
#define MEDIA_STREAM_SOURCE_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <stagefright/DataSource.h>
|
||||
#include <stagefright/MediaSource.h>
|
||||
|
||||
#include "MediaResource.h"
|
||||
|
||||
namespace android {
|
||||
|
||||
// MediaStreamSource is a DataSource that reads from a MPAPI media stream.
|
||||
class MediaStreamSource : public DataSource {
|
||||
typedef mozilla::MediaResource MediaResource;
|
||||
typedef mozilla::MediaResourceIndex MediaResourceIndex;
|
||||
|
||||
Mutex mLock;
|
||||
MediaResourceIndex mResource;
|
||||
public:
|
||||
MediaStreamSource(MediaResource* aResource);
|
||||
|
||||
status_t initCheck() const override;
|
||||
ssize_t readAt(off64_t offset, void *data, size_t size) override;
|
||||
status_t getSize(off64_t *size) override;
|
||||
uint32_t flags() override {
|
||||
return kWantsPrefetching;
|
||||
}
|
||||
|
||||
int64_t Tell();
|
||||
|
||||
virtual ~MediaStreamSource();
|
||||
|
||||
private:
|
||||
MediaStreamSource(const MediaStreamSource &);
|
||||
MediaStreamSource &operator=(const MediaStreamSource &);
|
||||
};
|
||||
|
||||
} // namespace android
|
||||
|
||||
#endif // MEDIA_STREAM_SOURCE_H
|
@ -1,204 +0,0 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/
|
||||
/* 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 "OMXCodecDescriptorUtil.h"
|
||||
|
||||
namespace android {
|
||||
|
||||
// The utility functions in this file concatenate two AVC/H.264 parameter sets,
|
||||
// sequence parameter set(SPS) and picture parameter set(PPS), into byte stream
|
||||
// format or construct AVC decoder config descriptor blob from them.
|
||||
//
|
||||
// * NAL unit defined in ISO/IEC 14496-10 7.3.1
|
||||
// * SPS defined ISO/IEC 14496-10 7.3.2.1.1
|
||||
// * PPS defined in ISO/IEC 14496-10 7.3.2.2
|
||||
//
|
||||
// Byte stream format:
|
||||
// Start code <0x00 0x00 0x00 0x01> (4 bytes)
|
||||
// --- (SPS) NAL unit ---
|
||||
// ... (3 bits)
|
||||
// NAL unit type <0x07> (5 bits)
|
||||
// SPS (3+ bytes)
|
||||
// Profile (1 byte)
|
||||
// Compatible profiles (1 byte)
|
||||
// Level (1 byte)
|
||||
// ...
|
||||
// --- End ---
|
||||
// Start code <0x00 0x00 0x00 0x01> (4 bytes)
|
||||
// --- (PPS) NAL unit ---
|
||||
// ... (3 bits)
|
||||
// NAL unit type <0x08> (5 bits)
|
||||
// PPS (1+ bytes)
|
||||
// ...
|
||||
// --- End ---
|
||||
//
|
||||
// Descriptor format: (defined in ISO/IEC 14496-15 5.2.4.1.1)
|
||||
// --- Header (5 bytes) ---
|
||||
// Version <0x01> (1 byte)
|
||||
// Profile (1 byte)
|
||||
// Compatible profiles (1 byte)
|
||||
// Level (1 byte)
|
||||
// Reserved <111111> (6 bits)
|
||||
// NAL length type (2 bits)
|
||||
// --- Parameter sets ---
|
||||
// Reserved <111> (3 bits)
|
||||
// Number of SPS (5 bits)
|
||||
// SPS (3+ bytes)
|
||||
// Length (2 bytes)
|
||||
// SPS NAL unit (1+ bytes)
|
||||
// ...
|
||||
// Number of PPS (1 byte)
|
||||
// PPS (3+ bytes)
|
||||
// Length (2 bytes)
|
||||
// PPS NAL unit (1+ bytes)
|
||||
// ...
|
||||
// --- End ---
|
||||
|
||||
// NAL unit start code.
|
||||
static const uint8_t kNALUnitStartCode[] = { 0x00, 0x00, 0x00, 0x01 };
|
||||
|
||||
// NAL unit types.
|
||||
enum {
|
||||
kNALUnitTypeSPS = 0x07, // Value for sequence parameter set.
|
||||
kNALUnitTypePPS = 0x08, // Value for picture parameter set.
|
||||
kNALUnitTypeBad = -1, // Malformed data.
|
||||
};
|
||||
|
||||
// Sequence parameter set or picture parameter set.
|
||||
struct AVCParamSet {
|
||||
AVCParamSet(const uint8_t* aPtr, const size_t aSize)
|
||||
: mPtr(aPtr)
|
||||
, mSize(aSize)
|
||||
{
|
||||
MOZ_ASSERT(mPtr && mSize > 0);
|
||||
}
|
||||
|
||||
size_t Size() {
|
||||
return mSize + 2; // 2 more bytes for length value.
|
||||
}
|
||||
|
||||
// Append 2 bytes length value and NAL unit bitstream to aOutputBuf.
|
||||
void AppendTo(nsTArray<uint8_t>* aOutputBuf)
|
||||
{
|
||||
// 2 bytes length value.
|
||||
uint8_t size[] = {
|
||||
uint8_t((mSize & 0xFF00) >> 8), // MSB.
|
||||
uint8_t(mSize & 0x00FF), // LSB.
|
||||
};
|
||||
aOutputBuf->AppendElements(size, sizeof(size));
|
||||
|
||||
aOutputBuf->AppendElements(mPtr, mSize);
|
||||
}
|
||||
|
||||
const uint8_t* mPtr; // Pointer to NAL unit.
|
||||
const size_t mSize; // NAL unit length in bytes.
|
||||
};
|
||||
|
||||
// Convert SPS and PPS data into decoder config descriptor blob. The generated
|
||||
// blob will be appended to aOutputBuf.
|
||||
static status_t
|
||||
ConvertParamSetsToDescriptorBlob(sp<ABuffer>& aSPS, sp<ABuffer>& aPPS,
|
||||
nsTArray<uint8_t>* aOutputBuf)
|
||||
{
|
||||
// Strip start code in the input.
|
||||
AVCParamSet sps(aSPS->data() + sizeof(kNALUnitStartCode),
|
||||
aSPS->size() - sizeof(kNALUnitStartCode));
|
||||
AVCParamSet pps(aPPS->data() + sizeof(kNALUnitStartCode),
|
||||
aPPS->size() - sizeof(kNALUnitStartCode));
|
||||
size_t paramSetsSize = sps.Size() + pps.Size();
|
||||
|
||||
// Profile/level info in SPS.
|
||||
uint8_t* info = aSPS->data() + 5;
|
||||
|
||||
uint8_t header[] = {
|
||||
0x01, // Version.
|
||||
info[0], // Profile.
|
||||
info[1], // Compatible profiles.
|
||||
info[2], // Level.
|
||||
0xFF, // 6 bits reserved value <111111> + 2 bits NAL length type <11>
|
||||
};
|
||||
|
||||
// Reserve 1 byte for number of SPS & another 1 for number of PPS.
|
||||
aOutputBuf->SetCapacity(sizeof(header) + paramSetsSize + 2);
|
||||
// Build the blob.
|
||||
aOutputBuf->AppendElements(header, sizeof(header)); // 5 bytes Header.
|
||||
aOutputBuf->AppendElement(0xE0 | 1); // 3 bits <111> + 5 bits number of SPS.
|
||||
sps.AppendTo(aOutputBuf); // SPS NALU data.
|
||||
aOutputBuf->AppendElement(1); // 1 byte number of PPS.
|
||||
pps.AppendTo(aOutputBuf); // PPS NALU data.
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
static int
|
||||
NALType(sp<ABuffer>& aBuffer)
|
||||
{
|
||||
if (aBuffer == nullptr) {
|
||||
return kNALUnitTypeBad;
|
||||
}
|
||||
// Start code?
|
||||
uint8_t* data = aBuffer->data();
|
||||
if (aBuffer->size() <= 4 ||
|
||||
memcmp(data, kNALUnitStartCode, sizeof(kNALUnitStartCode))) {
|
||||
return kNALUnitTypeBad;
|
||||
}
|
||||
|
||||
return data[4] & 0x1F;
|
||||
}
|
||||
|
||||
// Generate AVC/H.264 decoder config blob.
|
||||
// See MPEG4Writer::Track::makeAVCCodecSpecificData() and
|
||||
// MPEG4Writer::Track::writeAvccBox() implementation in libstagefright.
|
||||
status_t
|
||||
GenerateAVCDescriptorBlob(sp<AMessage>& aConfigData,
|
||||
nsTArray<uint8_t>* aOutputBuf,
|
||||
OMXVideoEncoder::BlobFormat aFormat)
|
||||
{
|
||||
// Search for parameter sets using key "csd-0" and "csd-1".
|
||||
char key[6] = "csd-";
|
||||
sp<ABuffer> sps;
|
||||
sp<ABuffer> pps;
|
||||
for (int i = 0; i < 2; i++) {
|
||||
snprintf(key + 4, 2, "%d", i);
|
||||
sp<ABuffer> paramSet;
|
||||
bool found = aConfigData->findBuffer(key, ¶mSet);
|
||||
int type = NALType(paramSet);
|
||||
bool valid = ((type == kNALUnitTypeSPS) || (type == kNALUnitTypePPS));
|
||||
|
||||
MOZ_ASSERT(found && valid);
|
||||
if (!found || !valid) {
|
||||
return ERROR_MALFORMED;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case kNALUnitTypeSPS:
|
||||
sps = paramSet;
|
||||
break;
|
||||
case kNALUnitTypePPS:
|
||||
pps = paramSet;
|
||||
break;
|
||||
default:
|
||||
NS_NOTREACHED("Should not get here!");
|
||||
}
|
||||
}
|
||||
|
||||
MOZ_ASSERT(sps != nullptr && pps != nullptr);
|
||||
if (sps == nullptr || pps == nullptr) {
|
||||
return ERROR_MALFORMED;
|
||||
}
|
||||
|
||||
if (aFormat == OMXVideoEncoder::BlobFormat::AVC_NAL) {
|
||||
// SPS + PPS.
|
||||
aOutputBuf->AppendElements(sps->data(), sps->size());
|
||||
aOutputBuf->AppendElements(pps->data(), pps->size());
|
||||
return OK;
|
||||
} else {
|
||||
status_t result = ConvertParamSetsToDescriptorBlob(sps, pps, aOutputBuf);
|
||||
MOZ_ASSERT(result == OK);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace android
|
@ -1,27 +0,0 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/
|
||||
/* 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/. */
|
||||
|
||||
#ifndef OMXCodecDescriptorUtil_h_
|
||||
#define OMXCodecDescriptorUtil_h_
|
||||
|
||||
#include <stagefright/foundation/AMessage.h>
|
||||
#include <stagefright/MediaErrors.h>
|
||||
|
||||
#include <nsTArray.h>
|
||||
|
||||
#include "OMXCodecWrapper.h"
|
||||
|
||||
namespace android {
|
||||
// Generate decoder config blob using aConfigData provided by encoder.
|
||||
// The output will be stored in aOutputBuf.
|
||||
// aFormat specifies the output format: AVC_MP4 is for MP4 file, and AVC_NAL is
|
||||
// for RTP packet used by WebRTC.
|
||||
status_t GenerateAVCDescriptorBlob(sp<AMessage>& aConfigData,
|
||||
nsTArray<uint8_t>* aOutputBuf,
|
||||
OMXVideoEncoder::BlobFormat aFormat);
|
||||
|
||||
}
|
||||
|
||||
#endif // OMXCodecDescriptorUtil_h_
|
@ -1,244 +0,0 @@
|
||||
/* -*- 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/. */
|
||||
|
||||
//#define LOG_NDEBUG 0
|
||||
#define LOG_TAG "OMXCodecProxy"
|
||||
|
||||
#include <binder/IPCThreadState.h>
|
||||
#include <cutils/properties.h>
|
||||
#include <stagefright/foundation/ADebug.h>
|
||||
#include <stagefright/MetaData.h>
|
||||
#include <stagefright/OMXCodec.h>
|
||||
#include <utils/Log.h>
|
||||
|
||||
#include "nsDebug.h"
|
||||
|
||||
#include "OMXCodecProxy.h"
|
||||
|
||||
namespace android {
|
||||
|
||||
// static
|
||||
sp<OMXCodecProxy> OMXCodecProxy::Create(
|
||||
const sp<IOMX> &omx,
|
||||
const sp<MetaData> &meta, bool createEncoder,
|
||||
const sp<MediaSource> &source,
|
||||
const char *matchComponentName,
|
||||
uint32_t flags,
|
||||
const sp<ANativeWindow> &nativeWindow)
|
||||
{
|
||||
sp<OMXCodecProxy> proxy;
|
||||
|
||||
const char *mime;
|
||||
if (!meta->findCString(kKeyMIMEType, &mime)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!strncasecmp(mime, "video/", 6)) {
|
||||
proxy = new OMXCodecProxy(omx, meta, createEncoder, source, matchComponentName, flags, nativeWindow);
|
||||
}
|
||||
return proxy;
|
||||
}
|
||||
|
||||
|
||||
OMXCodecProxy::OMXCodecProxy(
|
||||
const sp<IOMX> &omx,
|
||||
const sp<MetaData> &meta,
|
||||
bool createEncoder,
|
||||
const sp<MediaSource> &source,
|
||||
const char *matchComponentName,
|
||||
uint32_t flags,
|
||||
const sp<ANativeWindow> &nativeWindow)
|
||||
: mOMX(omx),
|
||||
mSrcMeta(meta),
|
||||
mComponentName(nullptr),
|
||||
mIsEncoder(createEncoder),
|
||||
mFlags(flags),
|
||||
mNativeWindow(nativeWindow),
|
||||
mSource(source),
|
||||
mState(ResourceState::START)
|
||||
{
|
||||
}
|
||||
|
||||
OMXCodecProxy::~OMXCodecProxy()
|
||||
{
|
||||
// At first, release mResourceClient's resource to prevent a conflict with
|
||||
// mResourceClient's callback.
|
||||
if (mResourceClient) {
|
||||
mResourceClient->ReleaseResource();
|
||||
mResourceClient = nullptr;
|
||||
}
|
||||
|
||||
mState = ResourceState::END;
|
||||
mCodecPromise.RejectIfExists(true, __func__);
|
||||
|
||||
if (mOMXCodec.get()) {
|
||||
wp<MediaSource> tmp = mOMXCodec;
|
||||
mOMXCodec.clear();
|
||||
while (tmp.promote() != nullptr) {
|
||||
// this value come from stagefrigh's AwesomePlayer.
|
||||
usleep(1000);
|
||||
}
|
||||
}
|
||||
// Complete all pending Binder ipc transactions
|
||||
IPCThreadState::self()->flushCommands();
|
||||
|
||||
mSource.clear();
|
||||
free(mComponentName);
|
||||
mComponentName = nullptr;
|
||||
}
|
||||
|
||||
RefPtr<OMXCodecProxy::CodecPromise>
|
||||
OMXCodecProxy::requestResource()
|
||||
{
|
||||
Mutex::Autolock autoLock(mLock);
|
||||
|
||||
if (mResourceClient) {
|
||||
return CodecPromise::CreateAndReject(true, __func__);
|
||||
}
|
||||
mState = ResourceState::WAITING;
|
||||
|
||||
mozilla::MediaSystemResourceType type = mIsEncoder ? mozilla::MediaSystemResourceType::VIDEO_ENCODER :
|
||||
mozilla::MediaSystemResourceType::VIDEO_DECODER;
|
||||
mResourceClient = new mozilla::MediaSystemResourceClient(type);
|
||||
mResourceClient->SetListener(this);
|
||||
mResourceClient->Acquire();
|
||||
|
||||
RefPtr<CodecPromise> p = mCodecPromise.Ensure(__func__);
|
||||
return p.forget();
|
||||
}
|
||||
|
||||
// Called on ImageBridge thread
|
||||
void
|
||||
OMXCodecProxy::ResourceReserved()
|
||||
{
|
||||
Mutex::Autolock autoLock(mLock);
|
||||
|
||||
if (mState != ResourceState::WAITING) {
|
||||
mCodecPromise.RejectIfExists(true, __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
const char *mime;
|
||||
if (!mSrcMeta->findCString(kKeyMIMEType, &mime)) {
|
||||
mState = ResourceState::END;
|
||||
mCodecPromise.RejectIfExists(true, __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!strncasecmp(mime, "video/", 6)) {
|
||||
sp<MediaSource> codec;
|
||||
mOMXCodec = OMXCodec::Create(mOMX, mSrcMeta, mIsEncoder, mSource, mComponentName, mFlags, mNativeWindow);
|
||||
if (mOMXCodec == nullptr) {
|
||||
mState = ResourceState::END;
|
||||
mCodecPromise.RejectIfExists(true, __func__);
|
||||
return;
|
||||
}
|
||||
// Check if this video is sized such that we're comfortable
|
||||
// possibly using an OMX decoder.
|
||||
int32_t maxWidth, maxHeight;
|
||||
char propValue[PROPERTY_VALUE_MAX];
|
||||
property_get("ro.moz.omx.hw.max_width", propValue, "-1");
|
||||
maxWidth = atoi(propValue);
|
||||
property_get("ro.moz.omx.hw.max_height", propValue, "-1");
|
||||
maxHeight = atoi(propValue);
|
||||
|
||||
int32_t width = -1, height = -1;
|
||||
if (maxWidth > 0 && maxHeight > 0 &&
|
||||
!(mOMXCodec->getFormat()->findInt32(kKeyWidth, &width) &&
|
||||
mOMXCodec->getFormat()->findInt32(kKeyHeight, &height) &&
|
||||
width * height <= maxWidth * maxHeight)) {
|
||||
printf_stderr("Failed to get video size, or it was too large for HW decoder (<w=%d, h=%d> but <maxW=%d, maxH=%d>)",
|
||||
width, height, maxWidth, maxHeight);
|
||||
mOMXCodec.clear();
|
||||
mState = ResourceState::END;
|
||||
mCodecPromise.RejectIfExists(true, __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mOMXCodec->start() != OK) {
|
||||
NS_WARNING("Couldn't start OMX video source");
|
||||
mOMXCodec.clear();
|
||||
mState = ResourceState::END;
|
||||
mCodecPromise.RejectIfExists(true, __func__);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
mState = ResourceState::ACQUIRED;
|
||||
mCodecPromise.ResolveIfExists(true, __func__);
|
||||
}
|
||||
|
||||
// Called on ImageBridge thread
|
||||
void
|
||||
OMXCodecProxy::ResourceReserveFailed()
|
||||
{
|
||||
Mutex::Autolock autoLock(mLock);
|
||||
mState = ResourceState::NOT_ACQUIRED;
|
||||
mCodecPromise.RejectIfExists(true, __func__);
|
||||
}
|
||||
|
||||
status_t
|
||||
OMXCodecProxy::start(MetaData *params)
|
||||
{
|
||||
Mutex::Autolock autoLock(mLock);
|
||||
|
||||
if (mState != ResourceState::ACQUIRED) {
|
||||
return NO_INIT;
|
||||
}
|
||||
CHECK(mOMXCodec.get() != nullptr);
|
||||
return mOMXCodec->start();
|
||||
}
|
||||
|
||||
status_t
|
||||
OMXCodecProxy::stop()
|
||||
{
|
||||
Mutex::Autolock autoLock(mLock);
|
||||
|
||||
if (mState != ResourceState::ACQUIRED) {
|
||||
return NO_INIT;
|
||||
}
|
||||
CHECK(mOMXCodec.get() != nullptr);
|
||||
return mOMXCodec->stop();
|
||||
}
|
||||
|
||||
sp<MetaData>
|
||||
OMXCodecProxy::getFormat()
|
||||
{
|
||||
Mutex::Autolock autoLock(mLock);
|
||||
|
||||
if (mState != ResourceState::ACQUIRED) {
|
||||
sp<MetaData> meta = new MetaData;
|
||||
return meta;
|
||||
}
|
||||
CHECK(mOMXCodec.get() != nullptr);
|
||||
return mOMXCodec->getFormat();
|
||||
}
|
||||
|
||||
status_t
|
||||
OMXCodecProxy::read(MediaBuffer **buffer, const ReadOptions *options)
|
||||
{
|
||||
Mutex::Autolock autoLock(mLock);
|
||||
|
||||
if (mState != ResourceState::ACQUIRED) {
|
||||
return NO_INIT;
|
||||
}
|
||||
CHECK(mOMXCodec.get() != nullptr);
|
||||
return mOMXCodec->read(buffer, options);
|
||||
}
|
||||
|
||||
status_t
|
||||
OMXCodecProxy::pause()
|
||||
{
|
||||
Mutex::Autolock autoLock(mLock);
|
||||
|
||||
if (mState != ResourceState::ACQUIRED) {
|
||||
return NO_INIT;
|
||||
}
|
||||
CHECK(mOMXCodec.get() != nullptr);
|
||||
return mOMXCodec->pause();
|
||||
}
|
||||
|
||||
} // namespace android
|
@ -1,101 +0,0 @@
|
||||
/* -*- 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/. */
|
||||
#ifndef OMX_CODEC_PROXY_DECODER_H_
|
||||
#define OMX_CODEC_PROXY_DECODER_H_
|
||||
|
||||
|
||||
#include <android/native_window.h>
|
||||
#include <media/IOMX.h>
|
||||
#include <stagefright/MediaBuffer.h>
|
||||
#include <stagefright/MediaSource.h>
|
||||
#include <utils/threads.h>
|
||||
|
||||
#include "mozilla/media/MediaSystemResourceClient.h"
|
||||
#include "mozilla/MozPromise.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
|
||||
namespace android {
|
||||
|
||||
struct MetaData;
|
||||
|
||||
class OMXCodecProxy : public MediaSource
|
||||
, public mozilla::MediaSystemResourceReservationListener
|
||||
{
|
||||
public:
|
||||
typedef mozilla::MozPromise<bool /* aIgnored */, bool /* aIgnored */, /* IsExclusive = */ true> CodecPromise;
|
||||
|
||||
// Enumeration for the valid resource allcoation states
|
||||
enum class ResourceState : int8_t {
|
||||
START,
|
||||
WAITING,
|
||||
ACQUIRED,
|
||||
NOT_ACQUIRED,
|
||||
END
|
||||
};
|
||||
|
||||
static sp<OMXCodecProxy> Create(
|
||||
const sp<IOMX> &omx,
|
||||
const sp<MetaData> &meta, bool createEncoder,
|
||||
const sp<MediaSource> &source,
|
||||
const char *matchComponentName = nullptr,
|
||||
uint32_t flags = 0,
|
||||
const sp<ANativeWindow> &nativeWindow = nullptr);
|
||||
|
||||
RefPtr<CodecPromise> requestResource();
|
||||
|
||||
// MediaSystemResourceReservationListener
|
||||
void ResourceReserved() override;
|
||||
void ResourceReserveFailed() override;
|
||||
|
||||
// MediaSource
|
||||
status_t start(MetaData *params = nullptr) override;
|
||||
status_t stop() override;
|
||||
|
||||
sp<MetaData> getFormat() override;
|
||||
|
||||
status_t read(
|
||||
MediaBuffer **buffer, const ReadOptions *options = nullptr) override;
|
||||
|
||||
status_t pause() override;
|
||||
|
||||
protected:
|
||||
OMXCodecProxy(
|
||||
const sp<IOMX> &omx,
|
||||
const sp<MetaData> &meta,
|
||||
bool createEncoder,
|
||||
const sp<MediaSource> &source,
|
||||
const char *matchComponentName,
|
||||
uint32_t flags,
|
||||
const sp<ANativeWindow> &nativeWindow);
|
||||
|
||||
virtual ~OMXCodecProxy();
|
||||
|
||||
private:
|
||||
OMXCodecProxy(const OMXCodecProxy &);
|
||||
OMXCodecProxy &operator=(const OMXCodecProxy &);
|
||||
|
||||
Mutex mLock;
|
||||
|
||||
sp<IOMX> mOMX;
|
||||
sp<MetaData> mSrcMeta;
|
||||
char *mComponentName;
|
||||
bool mIsEncoder;
|
||||
// Flags specified in the creation of the codec.
|
||||
uint32_t mFlags;
|
||||
sp<ANativeWindow> mNativeWindow;
|
||||
|
||||
sp<MediaSource> mSource;
|
||||
|
||||
sp<MediaSource> mOMXCodec;
|
||||
|
||||
RefPtr<mozilla::MediaSystemResourceClient> mResourceClient;
|
||||
ResourceState mState;
|
||||
mozilla::MozPromiseHolder<CodecPromise> mCodecPromise;
|
||||
};
|
||||
|
||||
} // namespace android
|
||||
|
||||
#endif // OMX_CODEC_PROXY_DECODER_H_
|
File diff suppressed because it is too large
Load Diff
@ -1,368 +0,0 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/
|
||||
/* 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/. */
|
||||
|
||||
#ifndef OMXCodecWrapper_h_
|
||||
#define OMXCodecWrapper_h_
|
||||
|
||||
#include <gui/Surface.h>
|
||||
#include <utils/RefBase.h>
|
||||
#include <stagefright/foundation/ABuffer.h>
|
||||
#include <stagefright/foundation/AMessage.h>
|
||||
#include <stagefright/MediaCodec.h>
|
||||
|
||||
#include "AudioSegment.h"
|
||||
#include "GonkNativeWindow.h"
|
||||
#include "mozilla/media/MediaSystemResourceClient.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
|
||||
#include <speex/speex_resampler.h>
|
||||
|
||||
namespace android {
|
||||
|
||||
// Wrapper class for managing HW codec reservations
|
||||
class OMXCodecReservation : public RefBase
|
||||
{
|
||||
public:
|
||||
OMXCodecReservation(bool aEncoder) : mOwned(false)
|
||||
{
|
||||
mType = aEncoder ? mozilla::MediaSystemResourceType::VIDEO_ENCODER :
|
||||
mozilla::MediaSystemResourceType::VIDEO_DECODER;
|
||||
}
|
||||
|
||||
virtual ~OMXCodecReservation()
|
||||
{
|
||||
ReleaseOMXCodec();
|
||||
}
|
||||
|
||||
/** Reserve the Encode or Decode resource for this instance */
|
||||
virtual bool ReserveOMXCodec();
|
||||
|
||||
/** Release the Encode or Decode resource for this instance */
|
||||
virtual void ReleaseOMXCodec();
|
||||
|
||||
private:
|
||||
mozilla::MediaSystemResourceType mType;
|
||||
bool mOwned; // We already own this resource
|
||||
|
||||
RefPtr<mozilla::MediaSystemResourceClient> mClient;
|
||||
};
|
||||
|
||||
|
||||
class OMXAudioEncoder;
|
||||
class OMXVideoEncoder;
|
||||
|
||||
/**
|
||||
* This class (and its subclasses) wraps the video and audio codec from
|
||||
* MediaCodec API in libstagefright. Currently only AVC/H.264 video encoder and
|
||||
* AAC audio encoder are supported.
|
||||
*
|
||||
* OMXCodecWrapper has static creator functions that returns actual codec
|
||||
* instances for different types of codec supported and serves as superclass to
|
||||
* provide a function to read encoded data as byte array from codec. Two
|
||||
* subclasses, OMXAudioEncoder and OMXVideoEncoder, respectively provides
|
||||
* functions for encoding data from audio and video track.
|
||||
*
|
||||
* A typical usage is as follows:
|
||||
* - Call one of the creator function Create...() to get either a
|
||||
* OMXAudioEncoder or OMXVideoEncoder object.
|
||||
* - Configure codec by providing characteristics of input raw data, such as
|
||||
* video frame width and height, using Configure().
|
||||
* - Send raw data (and notify end of stream) with Encode().
|
||||
* - Get encoded data through GetNextEncodedFrame().
|
||||
* - Repeat previous 2 steps until end of stream.
|
||||
* - Destroy the object.
|
||||
*
|
||||
* The lifecycle of underlying OMX codec is binded with construction and
|
||||
* destruction of OMXCodecWrapper and subclass objects. For some types of
|
||||
* codecs, such as HW accelerated AVC/H.264 encoder, there can be only one
|
||||
* instance system-wise at a time, attempting to create another instance will
|
||||
* fail.
|
||||
*/
|
||||
class OMXCodecWrapper
|
||||
{
|
||||
public:
|
||||
// Codec types.
|
||||
enum CodecType {
|
||||
AAC_ENC, // AAC encoder.
|
||||
AMR_NB_ENC, // AMR_NB encoder.
|
||||
AVC_ENC, // AVC/H.264 encoder.
|
||||
EVRC_ENC, // EVRC encoder
|
||||
TYPE_COUNT
|
||||
};
|
||||
|
||||
// Input and output flags.
|
||||
enum {
|
||||
// For Encode() and Encode, it indicates the end of input stream;
|
||||
// For GetNextEncodedFrame(), it indicates the end of output
|
||||
// stream.
|
||||
BUFFER_EOS = MediaCodec::BUFFER_FLAG_EOS,
|
||||
// For GetNextEncodedFrame(). It indicates the output buffer is an I-frame.
|
||||
BUFFER_SYNC_FRAME = MediaCodec::BUFFER_FLAG_SYNCFRAME,
|
||||
// For GetNextEncodedFrame(). It indicates that the output buffer contains
|
||||
// codec specific configuration info. (SPS & PPS for AVC/H.264;
|
||||
// DecoderSpecificInfo for AAC)
|
||||
BUFFER_CODEC_CONFIG = MediaCodec::BUFFER_FLAG_CODECCONFIG,
|
||||
};
|
||||
|
||||
// Hard-coded values for AAC DecoderConfigDescriptor in libstagefright.
|
||||
// See MPEG4Writer::Track::writeMp4aEsdsBox()
|
||||
// Exposed for the need of MP4 container writer.
|
||||
enum {
|
||||
kAACBitrate = 96000, // kbps
|
||||
kAACFrameSize = 768, // bytes
|
||||
kAACFrameDuration = 1024, // How many samples per AAC frame.
|
||||
};
|
||||
|
||||
/** Create a AAC audio encoder. Returns nullptr when failed. */
|
||||
static OMXAudioEncoder* CreateAACEncoder();
|
||||
|
||||
/** Create a AMR audio encoder. Returns nullptr when failed. */
|
||||
static OMXAudioEncoder* CreateAMRNBEncoder();
|
||||
|
||||
/** Create a EVRC audio encoder. Returns nullptr when failed. */
|
||||
static OMXAudioEncoder* CreateEVRCEncoder();
|
||||
|
||||
/** Create a AVC/H.264 video encoder. Returns nullptr when failed. */
|
||||
static OMXVideoEncoder* CreateAVCEncoder();
|
||||
|
||||
virtual ~OMXCodecWrapper();
|
||||
|
||||
/**
|
||||
* Get the next available encoded data from MediaCodec. The data will be
|
||||
* copied into aOutputBuf array, with its timestamp (in microseconds) in
|
||||
* aOutputTimestamp.
|
||||
* Wait at most aTimeout microseconds to dequeue a output buffer.
|
||||
*/
|
||||
nsresult GetNextEncodedFrame(nsTArray<uint8_t>* aOutputBuf,
|
||||
int64_t* aOutputTimestamp, int* aOutputFlags,
|
||||
int64_t aTimeOut);
|
||||
/*
|
||||
* Get the codec type
|
||||
*/
|
||||
int GetCodecType() { return mCodecType; }
|
||||
protected:
|
||||
/**
|
||||
* See whether the object has been initialized successfully and is ready to
|
||||
* use.
|
||||
*/
|
||||
virtual bool IsValid() { return mCodec != nullptr; }
|
||||
|
||||
/**
|
||||
* Construct codec specific configuration blob with given data aData generated
|
||||
* by media codec and append it into aOutputBuf. Needed by MP4 container
|
||||
* writer for generating decoder config box, or WebRTC for generating RTP
|
||||
* packets. Returns OK if succeed.
|
||||
*/
|
||||
virtual status_t AppendDecoderConfig(nsTArray<uint8_t>* aOutputBuf,
|
||||
ABuffer* aData) = 0;
|
||||
|
||||
/**
|
||||
* Append encoded frame data generated by media codec (stored in aData and
|
||||
* is aSize bytes long) into aOutputBuf. Subclasses can override this function
|
||||
* to process the data for specific container writer.
|
||||
*/
|
||||
virtual void AppendFrame(nsTArray<uint8_t>* aOutputBuf,
|
||||
const uint8_t* aData, size_t aSize)
|
||||
{
|
||||
aOutputBuf->AppendElements(aData, aSize);
|
||||
}
|
||||
|
||||
private:
|
||||
// Hide these. User should always use creator functions to get a media codec.
|
||||
OMXCodecWrapper() = delete;
|
||||
OMXCodecWrapper(const OMXCodecWrapper&) = delete;
|
||||
OMXCodecWrapper& operator=(const OMXCodecWrapper&) = delete;
|
||||
|
||||
/**
|
||||
* Create a media codec of given type. It will be a AVC/H.264 video encoder if
|
||||
* aCodecType is CODEC_AVC_ENC, or AAC audio encoder if aCodecType is
|
||||
* CODEC_AAC_ENC.
|
||||
*/
|
||||
OMXCodecWrapper(CodecType aCodecType);
|
||||
|
||||
// For subclasses to access hidden constructor and implementation details.
|
||||
friend class OMXAudioEncoder;
|
||||
friend class OMXVideoEncoder;
|
||||
|
||||
/**
|
||||
* Start the media codec.
|
||||
*/
|
||||
status_t Start();
|
||||
|
||||
/**
|
||||
* Stop the media codec.
|
||||
*/
|
||||
status_t Stop();
|
||||
|
||||
// The actual codec instance provided by libstagefright.
|
||||
sp<MediaCodec> mCodec;
|
||||
|
||||
// A dedicate message loop with its own thread used by MediaCodec.
|
||||
sp<ALooper> mLooper;
|
||||
|
||||
Vector<sp<ABuffer> > mInputBufs; // MediaCodec buffers to hold input data.
|
||||
Vector<sp<ABuffer> > mOutputBufs; // MediaCodec buffers to hold output data.
|
||||
|
||||
int mCodecType;
|
||||
bool mStarted; // Has MediaCodec been started?
|
||||
bool mAMRCSDProvided;
|
||||
bool mEVRCCSDProvided;
|
||||
};
|
||||
|
||||
/**
|
||||
* Audio encoder.
|
||||
*/
|
||||
class OMXAudioEncoder final : public OMXCodecWrapper
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Configure audio codec parameters and start media codec. It must be called
|
||||
* before calling Encode() and GetNextEncodedFrame().
|
||||
* aReSamplingRate = 0 means no resampler required
|
||||
*/
|
||||
nsresult Configure(int aChannelCount, int aInputSampleRate, int aEncodedSampleRate);
|
||||
|
||||
/**
|
||||
* Encode 16-bit PCM audio samples stored in aSegment. To notify end of
|
||||
* stream, set aInputFlags to BUFFER_EOS. Since encoder has limited buffers,
|
||||
* this function might not be able to encode all chunks in one call, however
|
||||
* it will remove chunks it consumes from aSegment.
|
||||
* aSendEOS is the output to tell the caller EOS signal sent into MediaCodec
|
||||
* because the signal might not be sent due to the dequeueInputBuffer timeout.
|
||||
* And the value of aSendEOS won't be set to any default value, only set to
|
||||
* true when EOS signal sent into MediaCodec.
|
||||
*/
|
||||
nsresult Encode(mozilla::AudioSegment& aSegment, int aInputFlags = 0,
|
||||
bool* aSendEOS = nullptr);
|
||||
|
||||
~OMXAudioEncoder();
|
||||
protected:
|
||||
virtual status_t AppendDecoderConfig(nsTArray<uint8_t>* aOutputBuf,
|
||||
ABuffer* aData) override;
|
||||
private:
|
||||
// Hide these. User should always use creator functions to get a media codec.
|
||||
OMXAudioEncoder() = delete;
|
||||
OMXAudioEncoder(const OMXAudioEncoder&) = delete;
|
||||
OMXAudioEncoder& operator=(const OMXAudioEncoder&) = delete;
|
||||
|
||||
/**
|
||||
* Create a audio codec. It will be a AAC encoder if aCodecType is
|
||||
* CODEC_AAC_ENC.
|
||||
*/
|
||||
OMXAudioEncoder(CodecType aCodecType)
|
||||
: OMXCodecWrapper(aCodecType)
|
||||
, mResampler(nullptr)
|
||||
, mChannels(0)
|
||||
, mResamplingRatio(0)
|
||||
, mTimestamp(0)
|
||||
, mSampleDuration(0) {}
|
||||
|
||||
// For creator function to access hidden constructor.
|
||||
friend class OMXCodecWrapper;
|
||||
friend class InputBufferHelper;
|
||||
|
||||
/**
|
||||
* If the input sample rate does not divide 48kHz evenly, the input data are
|
||||
* resampled.
|
||||
*/
|
||||
SpeexResamplerState* mResampler;
|
||||
// Number of audio channels.
|
||||
size_t mChannels;
|
||||
|
||||
float mResamplingRatio;
|
||||
// The total duration of audio samples that have been encoded in microseconds.
|
||||
int64_t mTimestamp;
|
||||
// Time per audio sample in microseconds.
|
||||
int64_t mSampleDuration;
|
||||
};
|
||||
|
||||
/**
|
||||
* Video encoder.
|
||||
*/
|
||||
class OMXVideoEncoder final : public OMXCodecWrapper
|
||||
{
|
||||
public:
|
||||
// Types of output blob format.
|
||||
enum BlobFormat {
|
||||
AVC_MP4, // MP4 file config descripter (defined in ISO/IEC 14496-15 5.2.4.1.1)
|
||||
AVC_NAL // NAL (Network Abstract Layer) (defined in ITU-T H.264 7.4.1)
|
||||
};
|
||||
|
||||
/**
|
||||
* Configure video codec parameters and start media codec. It must be called
|
||||
* before calling Encode() and GetNextEncodedFrame().
|
||||
* aBlobFormat specifies output blob format provided by encoder. It can be
|
||||
* AVC_MP4 or AVC_NAL.
|
||||
* Configure sets up most format value to values appropriate for camera use.
|
||||
* ConfigureDirect lets the caller determine all the defaults.
|
||||
*/
|
||||
nsresult Configure(int aWidth, int aHeight, int aFrameRate,
|
||||
BlobFormat aBlobFormat = BlobFormat::AVC_MP4);
|
||||
nsresult ConfigureDirect(sp<AMessage>& aFormat,
|
||||
BlobFormat aBlobFormat = BlobFormat::AVC_MP4);
|
||||
|
||||
/**
|
||||
* Encode a aWidth pixels wide and aHeight pixels tall video frame of
|
||||
* semi-planar YUV420 format stored in the buffer of aImage. aTimestamp gives
|
||||
* the frame timestamp/presentation time (in microseconds). To notify end of
|
||||
* stream, set aInputFlags to BUFFER_EOS.
|
||||
* aSendEOS is the output to tell the caller EOS signal sent into MediaCodec
|
||||
* because the signal might not be sent due to the dequeueInputBuffer timeout.
|
||||
* And the value of aSendEOS won't be set to any default value, only set to
|
||||
* true when EOS signal sent into MediaCodec.
|
||||
*/
|
||||
nsresult Encode(const mozilla::layers::Image* aImage, int aWidth, int aHeight,
|
||||
int64_t aTimestamp, int aInputFlags = 0,
|
||||
bool* aSendEOS = nullptr);
|
||||
|
||||
#if ANDROID_VERSION >= 18
|
||||
/** Set encoding bitrate (in kbps). */
|
||||
nsresult SetBitrate(int32_t aKbps);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Ask codec to generate an instantaneous decoding refresh (IDR) frame
|
||||
* (defined in ISO/IEC 14496-10).
|
||||
*/
|
||||
nsresult RequestIDRFrame();
|
||||
|
||||
protected:
|
||||
virtual status_t AppendDecoderConfig(nsTArray<uint8_t>* aOutputBuf,
|
||||
ABuffer* aData) override;
|
||||
|
||||
// If configured to output MP4 format blob, AVC/H.264 encoder has to replace
|
||||
// NAL unit start code with the unit length as specified in
|
||||
// ISO/IEC 14496-15 5.2.3.
|
||||
virtual void AppendFrame(nsTArray<uint8_t>* aOutputBuf,
|
||||
const uint8_t* aData, size_t aSize) override;
|
||||
|
||||
private:
|
||||
// Hide these. User should always use creator functions to get a media codec.
|
||||
OMXVideoEncoder() = delete;
|
||||
OMXVideoEncoder(const OMXVideoEncoder&) = delete;
|
||||
OMXVideoEncoder& operator=(const OMXVideoEncoder&) = delete;
|
||||
|
||||
/**
|
||||
* Create a video codec. It will be a AVC/H.264 encoder if aCodecType is
|
||||
* CODEC_AVC_ENC.
|
||||
*/
|
||||
OMXVideoEncoder(CodecType aCodecType)
|
||||
: OMXCodecWrapper(aCodecType)
|
||||
, mWidth(0)
|
||||
, mHeight(0)
|
||||
, mBlobFormat(BlobFormat::AVC_MP4)
|
||||
{}
|
||||
|
||||
// For creator function to access hidden constructor.
|
||||
friend class OMXCodecWrapper;
|
||||
|
||||
int mWidth;
|
||||
int mHeight;
|
||||
BlobFormat mBlobFormat;
|
||||
};
|
||||
|
||||
} // namespace android
|
||||
|
||||
#endif // OMXCodecWrapper_h_
|
@ -1,940 +0,0 @@
|
||||
/* -*- 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 <unistd.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include "base/basictypes.h"
|
||||
#include <cutils/properties.h>
|
||||
#include <stagefright/foundation/ADebug.h>
|
||||
#include <stagefright/foundation/AMessage.h>
|
||||
#include <stagefright/MediaExtractor.h>
|
||||
#include <stagefright/MetaData.h>
|
||||
#include <stagefright/OMXClient.h>
|
||||
#include <stagefright/OMXCodec.h>
|
||||
#include <OMX.h>
|
||||
#if MOZ_WIDGET_GONK && ANDROID_VERSION >= 17
|
||||
#include <ui/Fence.h>
|
||||
#endif
|
||||
|
||||
#if defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 17
|
||||
#include <gui/Surface.h>
|
||||
#else
|
||||
#include <gui/SurfaceTextureClient.h>
|
||||
#endif
|
||||
|
||||
#include "mozilla/layers/GrallocTextureClient.h"
|
||||
#include "mozilla/layers/TextureClient.h"
|
||||
#include "mozilla/Types.h"
|
||||
#include "mozilla/Monitor.h"
|
||||
#include "nsMimeTypes.h"
|
||||
#include "MPAPI.h"
|
||||
#include "mozilla/Logging.h"
|
||||
|
||||
#include "GonkNativeWindow.h"
|
||||
#include "OMXCodecProxy.h"
|
||||
#include "OmxDecoder.h"
|
||||
|
||||
#include <android/log.h>
|
||||
#define OD_LOG(...) __android_log_print(ANDROID_LOG_DEBUG, "OmxDecoder", __VA_ARGS__)
|
||||
|
||||
#undef LOG
|
||||
mozilla::LazyLogModule gOmxDecoderLog("OmxDecoder");
|
||||
#define LOG(type, msg...) MOZ_LOG(gOmxDecoderLog, type, (msg))
|
||||
|
||||
using namespace MPAPI;
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::gfx;
|
||||
using namespace mozilla::layers;
|
||||
using namespace android;
|
||||
|
||||
OmxDecoder::OmxDecoder(AbstractMediaDecoder *aDecoder,
|
||||
mozilla::TaskQueue* aTaskQueue) :
|
||||
mDecoder(aDecoder),
|
||||
mDisplayWidth(0),
|
||||
mDisplayHeight(0),
|
||||
mVideoWidth(0),
|
||||
mVideoHeight(0),
|
||||
mVideoColorFormat(0),
|
||||
mVideoStride(0),
|
||||
mVideoSliceHeight(0),
|
||||
mVideoRotation(0),
|
||||
mAudioChannels(-1),
|
||||
mAudioSampleRate(-1),
|
||||
mDurationUs(-1),
|
||||
mLastSeekTime(-1),
|
||||
mVideoBuffer(nullptr),
|
||||
mAudioBuffer(nullptr),
|
||||
mIsVideoSeeking(false),
|
||||
mAudioMetadataRead(false),
|
||||
mTaskQueue(aTaskQueue),
|
||||
mAudioPaused(false),
|
||||
mVideoPaused(false)
|
||||
{
|
||||
mLooper = new ALooper;
|
||||
mLooper->setName("OmxDecoder");
|
||||
|
||||
mReflector = new AHandlerReflector<OmxDecoder>(this);
|
||||
// Register AMessage handler to ALooper.
|
||||
mLooper->registerHandler(mReflector);
|
||||
// Start ALooper thread.
|
||||
mLooper->start();
|
||||
}
|
||||
|
||||
OmxDecoder::~OmxDecoder()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
ReleaseMediaResources();
|
||||
|
||||
// unregister AMessage handler from ALooper.
|
||||
mLooper->unregisterHandler(mReflector->id());
|
||||
// Stop ALooper thread.
|
||||
mLooper->stop();
|
||||
}
|
||||
|
||||
static sp<IOMX> sOMX = nullptr;
|
||||
static sp<IOMX> GetOMX()
|
||||
{
|
||||
if(sOMX.get() == nullptr) {
|
||||
sOMX = new OMX;
|
||||
}
|
||||
return sOMX;
|
||||
}
|
||||
|
||||
bool
|
||||
OmxDecoder::Init(sp<MediaExtractor>& extractor) {
|
||||
sp<MetaData> meta = extractor->getMetaData();
|
||||
|
||||
ssize_t audioTrackIndex = -1;
|
||||
ssize_t videoTrackIndex = -1;
|
||||
|
||||
for (size_t i = 0; i < extractor->countTracks(); ++i) {
|
||||
sp<MetaData> 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;
|
||||
}
|
||||
}
|
||||
|
||||
if (videoTrackIndex == -1 && audioTrackIndex == -1) {
|
||||
NS_WARNING("OMX decoder could not find video or audio tracks");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (videoTrackIndex != -1 && mDecoder->GetImageContainer()) {
|
||||
mVideoTrack = extractor->getTrack(videoTrackIndex);
|
||||
}
|
||||
|
||||
if (audioTrackIndex != -1) {
|
||||
mAudioTrack = extractor->getTrack(audioTrackIndex);
|
||||
|
||||
#ifdef MOZ_AUDIO_OFFLOAD
|
||||
// mAudioTrack is be used by OMXCodec. For offloaded audio track, using same
|
||||
// object gives undetermined behavior. So get a new track
|
||||
mAudioOffloadTrack = extractor->getTrack(audioTrackIndex);
|
||||
#endif
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
OmxDecoder::EnsureMetadata() {
|
||||
// calculate duration
|
||||
int64_t totalDurationUs = 0;
|
||||
int64_t durationUs = 0;
|
||||
if (mVideoTrack.get() && mVideoTrack->getFormat()->findInt64(kKeyDuration, &durationUs)) {
|
||||
if (durationUs > totalDurationUs)
|
||||
totalDurationUs = durationUs;
|
||||
}
|
||||
if (mAudioTrack.get()) {
|
||||
durationUs = -1;
|
||||
sp<MetaData> meta = mAudioTrack->getFormat();
|
||||
|
||||
if ((durationUs == -1) && meta->findInt64(kKeyDuration, &durationUs)) {
|
||||
if (durationUs > totalDurationUs) {
|
||||
totalDurationUs = durationUs;
|
||||
}
|
||||
}
|
||||
}
|
||||
mDurationUs = totalDurationUs;
|
||||
|
||||
// read video metadata
|
||||
if (mVideoSource.get() && !SetVideoFormat()) {
|
||||
NS_WARNING("Couldn't set OMX video format");
|
||||
return false;
|
||||
}
|
||||
|
||||
// read audio metadata
|
||||
if (mAudioSource.get()) {
|
||||
// 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
|
||||
status_t err = mAudioSource->read(&mAudioBuffer);
|
||||
if (err != INFO_FORMAT_CHANGED) {
|
||||
if (err != OK) {
|
||||
NS_WARNING("Couldn't read audio buffer from OMX decoder");
|
||||
return false;
|
||||
}
|
||||
sp<MetaData> meta = mAudioSource->getFormat();
|
||||
if (!meta->findInt32(kKeyChannelCount, &mAudioChannels) ||
|
||||
!meta->findInt32(kKeySampleRate, &mAudioSampleRate)) {
|
||||
NS_WARNING("Couldn't get audio metadata from OMX decoder");
|
||||
return false;
|
||||
}
|
||||
mAudioMetadataRead = true;
|
||||
}
|
||||
else if (!SetAudioFormat()) {
|
||||
NS_WARNING("Couldn't set audio format");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool isInEmulator()
|
||||
{
|
||||
char propQemu[PROPERTY_VALUE_MAX];
|
||||
property_get("ro.kernel.qemu", propQemu, "");
|
||||
return !strncmp(propQemu, "1", 1);
|
||||
}
|
||||
|
||||
RefPtr<mozilla::MediaOmxCommonReader::MediaResourcePromise>
|
||||
OmxDecoder::AllocateMediaResources()
|
||||
{
|
||||
RefPtr<MediaResourcePromise> p = mMediaResourcePromise.Ensure(__func__);
|
||||
|
||||
if ((mVideoTrack != nullptr) && (mVideoSource == nullptr)) {
|
||||
// OMXClient::connect() always returns OK and abort's fatally if
|
||||
// it can't connect.
|
||||
OMXClient client;
|
||||
DebugOnly<status_t> err = client.connect();
|
||||
NS_ASSERTION(err == OK, "Failed to connect to OMX in mediaserver.");
|
||||
sp<IOMX> omx = client.interface();
|
||||
|
||||
#if defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 21
|
||||
sp<IGraphicBufferProducer> producer;
|
||||
sp<IGonkGraphicBufferConsumer> consumer;
|
||||
GonkBufferQueue::createBufferQueue(&producer, &consumer);
|
||||
mNativeWindow = new GonkNativeWindow(consumer);
|
||||
#else
|
||||
mNativeWindow = new GonkNativeWindow();
|
||||
#endif
|
||||
|
||||
#if defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 21
|
||||
mNativeWindowClient = new Surface(producer);
|
||||
#elif defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 17
|
||||
mNativeWindowClient = new Surface(mNativeWindow->getBufferQueue());
|
||||
#else
|
||||
mNativeWindowClient = new SurfaceTextureClient(mNativeWindow);
|
||||
#endif
|
||||
|
||||
// Experience with OMX codecs is that only the HW decoders are
|
||||
// worth bothering with, at least on the platforms where this code
|
||||
// is currently used, and for formats this code is currently used
|
||||
// for (h.264). So if we don't get a hardware decoder, just give
|
||||
// up.
|
||||
#ifdef MOZ_OMX_WEBM_DECODER
|
||||
int flags = 0;//fallback to omx sw decoder if there is no hw decoder
|
||||
#else
|
||||
int flags = kHardwareCodecsOnly;
|
||||
#endif//MOZ_OMX_WEBM_DECODER
|
||||
|
||||
if (isInEmulator()) {
|
||||
// If we are in emulator, allow to fall back to software.
|
||||
flags = 0;
|
||||
}
|
||||
mVideoSource =
|
||||
OMXCodecProxy::Create(omx,
|
||||
mVideoTrack->getFormat(),
|
||||
false, // decoder
|
||||
mVideoTrack,
|
||||
nullptr,
|
||||
flags,
|
||||
mNativeWindowClient);
|
||||
if (mVideoSource == nullptr) {
|
||||
NS_WARNING("Couldn't create OMX video source");
|
||||
mMediaResourcePromise.Reject(true, __func__);
|
||||
return p;
|
||||
} else {
|
||||
sp<OmxDecoder> self = this;
|
||||
mVideoCodecRequest.Begin(mVideoSource->requestResource()
|
||||
->Then(OwnerThread(), __func__,
|
||||
[self] (bool) -> void {
|
||||
self->mVideoCodecRequest.Complete();
|
||||
self->mMediaResourcePromise.ResolveIfExists(true, __func__);
|
||||
}, [self] (bool) -> void {
|
||||
self->mVideoCodecRequest.Complete();
|
||||
self->mMediaResourcePromise.RejectIfExists(true, __func__);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
if ((mAudioTrack != nullptr) && (mAudioSource == nullptr)) {
|
||||
// OMXClient::connect() always returns OK and abort's fatally if
|
||||
// it can't connect.
|
||||
OMXClient client;
|
||||
DebugOnly<status_t> err = client.connect();
|
||||
NS_ASSERTION(err == OK, "Failed to connect to OMX in mediaserver.");
|
||||
sp<IOMX> omx = client.interface();
|
||||
|
||||
const char *audioMime = nullptr;
|
||||
sp<MetaData> meta = mAudioTrack->getFormat();
|
||||
if (!meta->findCString(kKeyMIMEType, &audioMime)) {
|
||||
mMediaResourcePromise.Reject(true, __func__);
|
||||
return p;
|
||||
}
|
||||
if (!strcasecmp(audioMime, "audio/raw")) {
|
||||
mAudioSource = mAudioTrack;
|
||||
} else {
|
||||
// try to load hardware codec in mediaserver process.
|
||||
int flags = kHardwareCodecsOnly;
|
||||
mAudioSource = OMXCodec::Create(omx,
|
||||
mAudioTrack->getFormat(),
|
||||
false, // decoder
|
||||
mAudioTrack,
|
||||
nullptr,
|
||||
flags);
|
||||
}
|
||||
|
||||
if (mAudioSource == nullptr) {
|
||||
// try to load software codec in this process.
|
||||
int flags = kSoftwareCodecsOnly;
|
||||
mAudioSource = OMXCodec::Create(GetOMX(),
|
||||
mAudioTrack->getFormat(),
|
||||
false, // decoder
|
||||
mAudioTrack,
|
||||
nullptr,
|
||||
flags);
|
||||
if (mAudioSource == nullptr) {
|
||||
NS_WARNING("Couldn't create OMX audio source");
|
||||
mMediaResourcePromise.Reject(true, __func__);
|
||||
return p;
|
||||
}
|
||||
}
|
||||
if (mAudioSource->start() != OK) {
|
||||
NS_WARNING("Couldn't start OMX audio source");
|
||||
mAudioSource.clear();
|
||||
mMediaResourcePromise.Reject(true, __func__);
|
||||
return p;
|
||||
}
|
||||
}
|
||||
if (!mVideoSource.get()) {
|
||||
// No resource allocation wait.
|
||||
mMediaResourcePromise.Resolve(true, __func__);
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
OmxDecoder::ReleaseMediaResources() {
|
||||
mVideoCodecRequest.DisconnectIfExists();
|
||||
mMediaResourcePromise.RejectIfExists(true, __func__);
|
||||
|
||||
ReleaseVideoBuffer();
|
||||
ReleaseAudioBuffer();
|
||||
|
||||
{
|
||||
Mutex::Autolock autoLock(mPendingVideoBuffersLock);
|
||||
MOZ_ASSERT(mPendingRecycleTexutreClients.empty());
|
||||
// Release all pending recycle TextureClients, if they are not recycled yet.
|
||||
// This should not happen. See Bug 1042308.
|
||||
if (!mPendingRecycleTexutreClients.empty()) {
|
||||
printf_stderr("OmxDecoder::ReleaseMediaResources -- TextureClients are not recycled yet\n");
|
||||
for (std::set<TextureClient*>::iterator it=mPendingRecycleTexutreClients.begin();
|
||||
it!=mPendingRecycleTexutreClients.end(); it++)
|
||||
{
|
||||
GrallocTextureData* client = static_cast<GrallocTextureData*>((*it)->GetInternalData());
|
||||
(*it)->ClearRecycleCallback();
|
||||
if (client->GetMediaBuffer()) {
|
||||
mPendingVideoBuffers.push(BufferItem(client->GetMediaBuffer(), (*it)->GetAndResetReleaseFenceHandle()));
|
||||
}
|
||||
}
|
||||
mPendingRecycleTexutreClients.clear();
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// Free all pending video buffers.
|
||||
Mutex::Autolock autoLock(mSeekLock);
|
||||
ReleaseAllPendingVideoBuffersLocked();
|
||||
}
|
||||
|
||||
if (mVideoSource.get()) {
|
||||
mVideoSource->stop();
|
||||
mVideoSource.clear();
|
||||
}
|
||||
|
||||
if (mAudioSource.get()) {
|
||||
mAudioSource->stop();
|
||||
mAudioSource.clear();
|
||||
}
|
||||
|
||||
mNativeWindowClient.clear();
|
||||
mNativeWindow.clear();
|
||||
|
||||
// Reset this variable to make the first seek go to the previous keyframe
|
||||
// when resuming
|
||||
mLastSeekTime = -1;
|
||||
}
|
||||
|
||||
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 (!mVideoTrack.get() || !mVideoTrack->getFormat()->findInt32(kKeyDisplayWidth, &mDisplayWidth)) {
|
||||
mDisplayWidth = mVideoWidth;
|
||||
NS_WARNING("display width not available, assuming width");
|
||||
}
|
||||
|
||||
if (!mVideoTrack.get() || !mVideoTrack->getFormat()->findInt32(kKeyDisplayHeight, &mDisplayHeight)) {
|
||||
mDisplayHeight = mVideoHeight;
|
||||
NS_WARNING("display height not available, assuming height");
|
||||
}
|
||||
|
||||
if (!mVideoSource->getFormat()->findInt32(kKeyStride, &mVideoStride)) {
|
||||
mVideoStride = mVideoWidth;
|
||||
NS_WARNING("stride not available, assuming width");
|
||||
}
|
||||
|
||||
if (!mVideoSource->getFormat()->findInt32(kKeySliceHeight, &mVideoSliceHeight)) {
|
||||
mVideoSliceHeight = mVideoHeight;
|
||||
NS_WARNING("slice height not available, assuming height");
|
||||
}
|
||||
|
||||
// Since ICS, valid video side is caluculated from kKeyCropRect.
|
||||
// kKeyWidth means decoded video buffer width.
|
||||
// kKeyHeight means decoded video buffer height.
|
||||
// On some hardwares, decoded video buffer and valid video size are different.
|
||||
int32_t crop_left, crop_top, crop_right, crop_bottom;
|
||||
if (mVideoSource->getFormat()->findRect(kKeyCropRect,
|
||||
&crop_left,
|
||||
&crop_top,
|
||||
&crop_right,
|
||||
&crop_bottom)) {
|
||||
mVideoWidth = crop_right - crop_left + 1;
|
||||
mVideoHeight = crop_bottom - crop_top + 1;
|
||||
}
|
||||
|
||||
if (!mVideoSource->getFormat()->findInt32(kKeyRotation, &mVideoRotation)) {
|
||||
mVideoRotation = 0;
|
||||
NS_WARNING("rotation not available, assuming 0");
|
||||
}
|
||||
|
||||
LOG(LogLevel::Debug, "display width: %d display height %d width: %d height: %d component: %s format: %d stride: %d sliceHeight: %d rotation: %d",
|
||||
mDisplayWidth, mDisplayHeight, 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(LogLevel::Debug, "channelCount: %d sampleRate: %d",
|
||||
mAudioChannels, mAudioSampleRate);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
OmxDecoder::ReleaseDecoder()
|
||||
{
|
||||
mDecoder = nullptr;
|
||||
}
|
||||
|
||||
void
|
||||
OmxDecoder::ReleaseVideoBuffer() {
|
||||
if (mVideoBuffer) {
|
||||
mVideoBuffer->release();
|
||||
mVideoBuffer = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
OmxDecoder::ReleaseAudioBuffer() {
|
||||
if (mAudioBuffer) {
|
||||
mAudioBuffer->release();
|
||||
mAudioBuffer = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
OmxDecoder::PlanarYUV420Frame(VideoFrame *aFrame, int64_t aTimeUs, void *aData, size_t aSize, bool aKeyFrame) {
|
||||
void *y = aData;
|
||||
void *u = static_cast<uint8_t *>(y) + mVideoStride * mVideoSliceHeight;
|
||||
void *v = static_cast<uint8_t *>(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<uint8_t *>(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;
|
||||
|
||||
aFrame->mGraphicBuffer = nullptr;
|
||||
|
||||
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:
|
||||
LOG(LogLevel::Debug, "Unknown video color format %08x", mVideoColorFormat);
|
||||
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, static_cast<char *>(aData) + aDataOffset, aSize, aAudioChannels, aAudioSampleRate);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
OmxDecoder::ReadVideo(VideoFrame *aFrame, int64_t aTimeUs,
|
||||
bool aKeyframeSkip, bool aDoSeek)
|
||||
{
|
||||
if (!mVideoSource.get())
|
||||
return false;
|
||||
|
||||
ReleaseVideoBuffer();
|
||||
|
||||
status_t err;
|
||||
|
||||
if (aDoSeek) {
|
||||
{
|
||||
Mutex::Autolock autoLock(mSeekLock);
|
||||
ReleaseAllPendingVideoBuffersLocked();
|
||||
mIsVideoSeeking = true;
|
||||
}
|
||||
MediaSource::ReadOptions options;
|
||||
MediaSource::ReadOptions::SeekMode seekMode;
|
||||
// If the last timestamp of decoded frame is smaller than seekTime,
|
||||
// seek to next key frame. Otherwise seek to the previos one.
|
||||
OD_LOG("SeekTime: %lld, mLastSeekTime:%lld", aTimeUs, mLastSeekTime);
|
||||
if (mLastSeekTime == -1 || mLastSeekTime > aTimeUs) {
|
||||
seekMode = MediaSource::ReadOptions::SEEK_PREVIOUS_SYNC;
|
||||
} else {
|
||||
seekMode = MediaSource::ReadOptions::SEEK_NEXT_SYNC;
|
||||
}
|
||||
mLastSeekTime = aTimeUs;
|
||||
bool findNextBuffer = true;
|
||||
while (findNextBuffer) {
|
||||
options.setSeekTo(aTimeUs, seekMode);
|
||||
findNextBuffer = false;
|
||||
if (mIsVideoSeeking) {
|
||||
err = mVideoSource->read(&mVideoBuffer, &options);
|
||||
Mutex::Autolock autoLock(mSeekLock);
|
||||
mIsVideoSeeking = false;
|
||||
PostReleaseVideoBuffer(nullptr, FenceHandle());
|
||||
}
|
||||
else {
|
||||
err = mVideoSource->read(&mVideoBuffer);
|
||||
}
|
||||
|
||||
// If there is no next Keyframe, jump to the previous key frame.
|
||||
if (err == ERROR_END_OF_STREAM && seekMode == MediaSource::ReadOptions::SEEK_NEXT_SYNC) {
|
||||
seekMode = MediaSource::ReadOptions::SEEK_PREVIOUS_SYNC;
|
||||
findNextBuffer = true;
|
||||
{
|
||||
Mutex::Autolock autoLock(mSeekLock);
|
||||
mIsVideoSeeking = true;
|
||||
}
|
||||
continue;
|
||||
} else if (err != OK) {
|
||||
OD_LOG("Unexpected error when seeking to %lld", aTimeUs);
|
||||
break;
|
||||
}
|
||||
// For some codecs, the length of first decoded frame after seek is 0.
|
||||
// Need to ignore it and continue to find the next one
|
||||
if (mVideoBuffer->range_length() == 0) {
|
||||
PostReleaseVideoBuffer(mVideoBuffer, FenceHandle());
|
||||
findNextBuffer = true;
|
||||
}
|
||||
}
|
||||
aDoSeek = false;
|
||||
} else {
|
||||
err = mVideoSource->read(&mVideoBuffer);
|
||||
}
|
||||
|
||||
aFrame->mSize = 0;
|
||||
|
||||
if (err == OK) {
|
||||
int64_t timeUs;
|
||||
int32_t unreadable;
|
||||
int32_t keyFrame;
|
||||
|
||||
size_t length = mVideoBuffer->range_length();
|
||||
|
||||
if (!mVideoBuffer->meta_data()->findInt64(kKeyTime, &timeUs) ) {
|
||||
NS_WARNING("OMX decoder did not return frame time");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!mVideoBuffer->meta_data()->findInt32(kKeyIsSyncFrame, &keyFrame)) {
|
||||
keyFrame = 0;
|
||||
}
|
||||
|
||||
if (!mVideoBuffer->meta_data()->findInt32(kKeyIsUnreadable, &unreadable)) {
|
||||
unreadable = 0;
|
||||
}
|
||||
|
||||
RefPtr<mozilla::layers::TextureClient> textureClient;
|
||||
if ((mVideoBuffer->graphicBuffer().get())) {
|
||||
textureClient = mNativeWindow->getTextureClientFromBuffer(mVideoBuffer->graphicBuffer().get());
|
||||
}
|
||||
|
||||
if (textureClient) {
|
||||
// Manually increment reference count to keep MediaBuffer alive
|
||||
// during TextureClient is in use.
|
||||
mVideoBuffer->add_ref();
|
||||
static_cast<GrallocTextureData*>(textureClient->GetInternalData())->SetMediaBuffer(mVideoBuffer);
|
||||
// Set recycle callback for TextureClient
|
||||
textureClient->SetRecycleCallback(OmxDecoder::RecycleCallback, this);
|
||||
{
|
||||
Mutex::Autolock autoLock(mPendingVideoBuffersLock);
|
||||
// Store pending recycle TextureClient.
|
||||
MOZ_ASSERT(mPendingRecycleTexutreClients.find(textureClient) == mPendingRecycleTexutreClients.end());
|
||||
mPendingRecycleTexutreClients.insert(textureClient);
|
||||
}
|
||||
|
||||
aFrame->mGraphicBuffer = textureClient;
|
||||
aFrame->mRotation = mVideoRotation;
|
||||
aFrame->mTimeUs = timeUs;
|
||||
aFrame->mKeyFrame = keyFrame;
|
||||
aFrame->Y.mWidth = mVideoWidth;
|
||||
aFrame->Y.mHeight = mVideoHeight;
|
||||
// Release to hold video buffer in OmxDecoder more.
|
||||
// MediaBuffer's ref count is changed from 2 to 1.
|
||||
ReleaseVideoBuffer();
|
||||
} else if (length > 0) {
|
||||
char *data = static_cast<char *>(mVideoBuffer->data()) + mVideoBuffer->range_offset();
|
||||
|
||||
if (unreadable) {
|
||||
LOG(LogLevel::Debug, "video frame is unreadable");
|
||||
}
|
||||
|
||||
if (!ToVideoFrame(aFrame, timeUs, data, length, keyFrame)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Check if this frame is valid or not. If not, skip it.
|
||||
if ((aKeyframeSkip && timeUs < aTimeUs) || length == 0) {
|
||||
aFrame->mShouldSkip = true;
|
||||
}
|
||||
}
|
||||
else if (err == INFO_FORMAT_CHANGED) {
|
||||
// If the format changed, update our cached info.
|
||||
if (!SetVideoFormat()) {
|
||||
return false;
|
||||
} else {
|
||||
return ReadVideo(aFrame, aTimeUs, aKeyframeSkip, aDoSeek);
|
||||
}
|
||||
}
|
||||
else if (err == ERROR_END_OF_STREAM) {
|
||||
return false;
|
||||
}
|
||||
else if (err == -ETIMEDOUT) {
|
||||
LOG(LogLevel::Debug, "OmxDecoder::ReadVideo timed out, will retry");
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
// UNKNOWN_ERROR is sometimes is used to mean "out of memory", but
|
||||
// regardless, don't keep trying to decode if the decoder doesn't want to.
|
||||
LOG(LogLevel::Debug, "OmxDecoder::ReadVideo failed, err=%d", err);
|
||||
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;
|
||||
aFrame->mSize = 0;
|
||||
|
||||
if (err == OK && mAudioBuffer && 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) {
|
||||
// If the format changed, update our cached info.
|
||||
if (!SetAudioFormat()) {
|
||||
return false;
|
||||
} else {
|
||||
return ReadAudio(aFrame, aSeekTimeUs);
|
||||
}
|
||||
}
|
||||
else if (err == ERROR_END_OF_STREAM) {
|
||||
if (aFrame->mSize == 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (err == -ETIMEDOUT) {
|
||||
LOG(LogLevel::Debug, "OmxDecoder::ReadAudio timed out, will retry");
|
||||
return true;
|
||||
}
|
||||
else if (err != OK) {
|
||||
LOG(LogLevel::Debug, "OmxDecoder::ReadAudio failed, err=%d", err);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
nsresult
|
||||
OmxDecoder::Play()
|
||||
{
|
||||
if (!mVideoPaused && !mAudioPaused) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
if (mVideoPaused && mVideoSource.get() && mVideoSource->start() != OK) {
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
mVideoPaused = false;
|
||||
|
||||
if (mAudioPaused && mAudioSource.get() && mAudioSource->start() != OK) {
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
mAudioPaused = false;
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// AOSP didn't give implementation on OMXCodec::Pause() and not define
|
||||
// OMXCodec::Start() should be called for resuming the decoding. Currently
|
||||
// it is customized by a specific open source repository only.
|
||||
// ToDo The one not supported OMXCodec::Pause() should return error code here,
|
||||
// so OMXCodec::Start() doesn't be called again for resuming. But if someone
|
||||
// implement the OMXCodec::Pause() and need a following OMXCodec::Read() with
|
||||
// seek option (define in MediaSource.h) then it is still not supported here.
|
||||
// We need to fix it until it is really happened.
|
||||
void
|
||||
OmxDecoder::Pause()
|
||||
{
|
||||
/* The implementation of OMXCodec::pause is flawed.
|
||||
* OMXCodec::start will not restore from the paused state and result in
|
||||
* buffer timeout which causes timeouts in mochitests.
|
||||
* Since there is not power consumption problem in emulator, we will just
|
||||
* return when running in emulator to fix timeouts in mochitests.
|
||||
*/
|
||||
if (isInEmulator()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mVideoPaused || mAudioPaused) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mVideoSource.get() && mVideoSource->pause() == OK) {
|
||||
mVideoPaused = true;
|
||||
}
|
||||
|
||||
if (mAudioSource.get() && mAudioSource->pause() == OK) {
|
||||
mAudioPaused = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Called on ALooper thread.
|
||||
void
|
||||
OmxDecoder::onMessageReceived(const sp<AMessage> &msg)
|
||||
{
|
||||
switch (msg->what()) {
|
||||
case kNotifyPostReleaseVideoBuffer:
|
||||
{
|
||||
Mutex::Autolock autoLock(mSeekLock);
|
||||
// Free pending video buffers when OmxDecoder is not seeking video.
|
||||
// If OmxDecoder is seeking video, the buffers are freed on seek exit.
|
||||
if (!mIsVideoSeeking) {
|
||||
ReleaseAllPendingVideoBuffersLocked();
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
TRESPASS();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
OmxDecoder::PostReleaseVideoBuffer(MediaBuffer *aBuffer, const FenceHandle& aReleaseFenceHandle)
|
||||
{
|
||||
{
|
||||
Mutex::Autolock autoLock(mPendingVideoBuffersLock);
|
||||
if (aBuffer) {
|
||||
mPendingVideoBuffers.push(BufferItem(aBuffer, aReleaseFenceHandle));
|
||||
}
|
||||
}
|
||||
|
||||
sp<AMessage> notify =
|
||||
new AMessage(kNotifyPostReleaseVideoBuffer, mReflector->id());
|
||||
// post AMessage to OmxDecoder via ALooper.
|
||||
notify->post();
|
||||
}
|
||||
|
||||
void
|
||||
OmxDecoder::ReleaseAllPendingVideoBuffersLocked()
|
||||
{
|
||||
Vector<BufferItem> releasingVideoBuffers;
|
||||
{
|
||||
Mutex::Autolock autoLock(mPendingVideoBuffersLock);
|
||||
|
||||
int size = mPendingVideoBuffers.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
releasingVideoBuffers.push(mPendingVideoBuffers[i]);
|
||||
}
|
||||
mPendingVideoBuffers.clear();
|
||||
}
|
||||
// Free all pending video buffers without holding mPendingVideoBuffersLock.
|
||||
int size = releasingVideoBuffers.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
MediaBuffer *buffer;
|
||||
buffer = releasingVideoBuffers[i].mMediaBuffer;
|
||||
#if defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 17
|
||||
RefPtr<FenceHandle::FdObj> fdObj = releasingVideoBuffers.editItemAt(i).mReleaseFenceHandle.GetAndResetFdObj();
|
||||
int fenceFd = fdObj->GetAndResetFd();
|
||||
|
||||
MOZ_ASSERT(buffer->refcount() == 1);
|
||||
// This code expect MediaBuffer's ref count is 1.
|
||||
// Return gralloc buffer to ANativeWindow
|
||||
ANativeWindow* window = static_cast<ANativeWindow*>(mNativeWindowClient.get());
|
||||
window->cancelBuffer(window,
|
||||
buffer->graphicBuffer().get(),
|
||||
fenceFd);
|
||||
// Mark MediaBuffer as rendered.
|
||||
// When gralloc buffer is directly returned to ANativeWindow,
|
||||
// this mark is necesary.
|
||||
sp<MetaData> metaData = buffer->meta_data();
|
||||
metaData->setInt32(kKeyRendered, 1);
|
||||
#endif
|
||||
// Return MediaBuffer to OMXCodec.
|
||||
buffer->release();
|
||||
}
|
||||
releasingVideoBuffers.clear();
|
||||
}
|
||||
|
||||
void
|
||||
OmxDecoder::RecycleCallbackImp(TextureClient* aClient)
|
||||
{
|
||||
aClient->ClearRecycleCallback();
|
||||
{
|
||||
Mutex::Autolock autoLock(mPendingVideoBuffersLock);
|
||||
if (mPendingRecycleTexutreClients.find(aClient) == mPendingRecycleTexutreClients.end()) {
|
||||
printf_stderr("OmxDecoder::RecycleCallbackImp -- TextureClient is not pending recycle\n");
|
||||
return;
|
||||
}
|
||||
mPendingRecycleTexutreClients.erase(aClient);
|
||||
GrallocTextureData* grallocData = static_cast<GrallocTextureData*>(aClient->GetInternalData());
|
||||
if (grallocData->GetMediaBuffer()) {
|
||||
mPendingVideoBuffers.push(BufferItem(grallocData->GetMediaBuffer(), aClient->GetAndResetReleaseFenceHandle()));
|
||||
}
|
||||
}
|
||||
sp<AMessage> notify =
|
||||
new AMessage(kNotifyPostReleaseVideoBuffer, mReflector->id());
|
||||
// post AMessage to OmxDecoder via ALooper.
|
||||
notify->post();
|
||||
}
|
||||
|
||||
/* static */ void
|
||||
OmxDecoder::RecycleCallback(TextureClient* aClient, void* aClosure)
|
||||
{
|
||||
MOZ_ASSERT(aClient && !aClient->IsDead());
|
||||
OmxDecoder* decoder = static_cast<OmxDecoder*>(aClosure);
|
||||
decoder->RecycleCallbackImp(aClient);
|
||||
}
|
@ -1,224 +0,0 @@
|
||||
#include <set>
|
||||
#include <stagefright/foundation/ABase.h>
|
||||
#include <stagefright/foundation/AHandlerReflector.h>
|
||||
#include <stagefright/foundation/ALooper.h>
|
||||
#include <utils/RefBase.h>
|
||||
#include <stagefright/MediaExtractor.h>
|
||||
|
||||
#include "GonkNativeWindow.h"
|
||||
#include "mozilla/layers/FenceUtils.h"
|
||||
#include "MP3FrameParser.h"
|
||||
#include "MPAPI.h"
|
||||
#include "MediaOmxCommonReader.h"
|
||||
#include "AbstractMediaDecoder.h"
|
||||
#include "OMXCodecProxy.h"
|
||||
|
||||
namespace android {
|
||||
class OmxDecoder;
|
||||
};
|
||||
|
||||
namespace android {
|
||||
|
||||
class OmxDecoder : public RefBase {
|
||||
typedef MPAPI::AudioFrame AudioFrame;
|
||||
typedef MPAPI::VideoFrame VideoFrame;
|
||||
typedef mozilla::MP3FrameParser MP3FrameParser;
|
||||
typedef mozilla::MediaResource MediaResource;
|
||||
typedef mozilla::AbstractMediaDecoder AbstractMediaDecoder;
|
||||
typedef mozilla::layers::FenceHandle FenceHandle;
|
||||
typedef mozilla::layers::TextureClient TextureClient;
|
||||
typedef mozilla::MediaOmxCommonReader::MediaResourcePromise MediaResourcePromise;
|
||||
|
||||
enum {
|
||||
kPreferSoftwareCodecs = 1,
|
||||
kSoftwareCodecsOnly = 8,
|
||||
kHardwareCodecsOnly = 16,
|
||||
};
|
||||
|
||||
enum {
|
||||
kNotifyPostReleaseVideoBuffer = 'noti',
|
||||
};
|
||||
|
||||
AbstractMediaDecoder *mDecoder;
|
||||
sp<GonkNativeWindow> mNativeWindow;
|
||||
sp<ANativeWindow> mNativeWindowClient;
|
||||
|
||||
sp<MediaSource> mVideoTrack;
|
||||
sp<OMXCodecProxy> mVideoSource;
|
||||
sp<MediaSource> mAudioOffloadTrack;
|
||||
sp<MediaSource> mAudioTrack;
|
||||
sp<MediaSource> mAudioSource;
|
||||
int32_t mDisplayWidth;
|
||||
int32_t mDisplayHeight;
|
||||
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;
|
||||
int64_t mLastSeekTime;
|
||||
|
||||
VideoFrame mVideoFrame;
|
||||
AudioFrame mAudioFrame;
|
||||
MP3FrameParser mMP3FrameParser;
|
||||
bool mIsMp3;
|
||||
|
||||
// Lifetime of these should be handled by OMXCodec, as long as we release
|
||||
// them after use: see ReleaseVideoBuffer(), ReleaseAudioBuffer()
|
||||
MediaBuffer *mVideoBuffer;
|
||||
MediaBuffer *mAudioBuffer;
|
||||
|
||||
struct BufferItem {
|
||||
BufferItem()
|
||||
: mMediaBuffer(nullptr)
|
||||
{
|
||||
}
|
||||
BufferItem(MediaBuffer* aMediaBuffer, const FenceHandle& aReleaseFenceHandle)
|
||||
: mMediaBuffer(aMediaBuffer)
|
||||
, mReleaseFenceHandle(aReleaseFenceHandle) {
|
||||
}
|
||||
|
||||
MediaBuffer* mMediaBuffer;
|
||||
// a fence will signal when the current buffer is no longer being read.
|
||||
FenceHandle mReleaseFenceHandle;
|
||||
};
|
||||
|
||||
// Hold video's MediaBuffers that are released during video seeking.
|
||||
// The holded MediaBuffers are released soon after seek completion.
|
||||
// OMXCodec does not accept MediaBuffer during seeking. If MediaBuffer is
|
||||
// returned to OMXCodec during seeking, OMXCodec calls assert.
|
||||
Vector<BufferItem> mPendingVideoBuffers;
|
||||
|
||||
// Hold TextureClients that are waiting to be recycled.
|
||||
std::set<TextureClient*> mPendingRecycleTexutreClients;
|
||||
|
||||
// The lock protects mPendingVideoBuffers and mPendingRecycleTexutreClients.
|
||||
Mutex mPendingVideoBuffersLock;
|
||||
|
||||
// Show if OMXCodec is seeking.
|
||||
bool mIsVideoSeeking;
|
||||
// The lock protects video MediaBuffer release()'s pending operations called
|
||||
// from multiple threads. The pending operations happen only during video
|
||||
// seeking. Holding mSeekLock long time could affect to video rendering.
|
||||
// Holding time should be minimum.
|
||||
Mutex mSeekLock;
|
||||
|
||||
// ALooper is a message loop used in stagefright.
|
||||
// It creates a thread for messages and handles messages in the thread.
|
||||
// ALooper is a clone of Looper in android Java.
|
||||
// http://developer.android.com/reference/android/os/Looper.html
|
||||
sp<ALooper> mLooper;
|
||||
// deliver a message to a wrapped object(OmxDecoder).
|
||||
// AHandlerReflector is similar to Handler in android Java.
|
||||
// http://developer.android.com/reference/android/os/Handler.html
|
||||
sp<AHandlerReflector<OmxDecoder> > mReflector;
|
||||
|
||||
// 'true' if a read from the audio stream was done while reading the metadata
|
||||
bool mAudioMetadataRead;
|
||||
|
||||
RefPtr<mozilla::TaskQueue> mTaskQueue;
|
||||
|
||||
mozilla::MozPromiseRequestHolder<OMXCodecProxy::CodecPromise> mVideoCodecRequest;
|
||||
mozilla::MozPromiseHolder<MediaResourcePromise> mMediaResourcePromise;
|
||||
|
||||
void ReleaseVideoBuffer();
|
||||
void ReleaseAudioBuffer();
|
||||
// Call with mSeekLock held.
|
||||
void ReleaseAllPendingVideoBuffersLocked();
|
||||
|
||||
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);
|
||||
|
||||
//True if decoder is in a paused state
|
||||
bool mAudioPaused;
|
||||
bool mVideoPaused;
|
||||
|
||||
mozilla::TaskQueue* OwnerThread() const
|
||||
{
|
||||
return mTaskQueue;
|
||||
}
|
||||
|
||||
public:
|
||||
explicit OmxDecoder(AbstractMediaDecoder *aDecoder, mozilla::TaskQueue* aTaskQueue);
|
||||
~OmxDecoder();
|
||||
|
||||
// The MediaExtractor provides essential information for creating OMXCodec
|
||||
// instance. Such as video/audio codec, we can retrieve them through the
|
||||
// MediaExtractor::getTrackMetaData().
|
||||
// In general cases, the extractor is created by a sp<DataSource> which
|
||||
// connect to a MediaResource like ChannelMediaResource.
|
||||
// Data is read from the MediaResource to create a suitable extractor which
|
||||
// extracts data from a container.
|
||||
// Note: RTSP requires a custom extractor because it doesn't have a container.
|
||||
bool Init(sp<MediaExtractor>& extractor);
|
||||
|
||||
// Called after resources(video/audio codec) are allocated, set the
|
||||
// mDurationUs and video/audio metadata.
|
||||
bool EnsureMetadata();
|
||||
|
||||
RefPtr<MediaResourcePromise> AllocateMediaResources();
|
||||
void ReleaseMediaResources();
|
||||
bool SetVideoFormat();
|
||||
bool SetAudioFormat();
|
||||
|
||||
void ReleaseDecoder();
|
||||
|
||||
void GetDuration(int64_t *durationUs) {
|
||||
*durationUs = mDurationUs;
|
||||
}
|
||||
|
||||
void GetVideoParameters(int32_t* aDisplayWidth, int32_t* aDisplayHeight,
|
||||
int32_t* aWidth, int32_t* aHeight) {
|
||||
*aDisplayWidth = mDisplayWidth;
|
||||
*aDisplayHeight = mDisplayHeight;
|
||||
*aWidth = mVideoWidth;
|
||||
*aHeight = mVideoHeight;
|
||||
}
|
||||
|
||||
void GetAudioParameters(int32_t *numChannels, int32_t *sampleRate) {
|
||||
*numChannels = mAudioChannels;
|
||||
*sampleRate = mAudioSampleRate;
|
||||
}
|
||||
|
||||
bool HasVideo() {
|
||||
return mVideoSource != nullptr;
|
||||
}
|
||||
|
||||
bool HasAudio() {
|
||||
return mAudioSource != nullptr;
|
||||
}
|
||||
|
||||
bool ReadVideo(VideoFrame *aFrame, int64_t aSeekTimeUs,
|
||||
bool aKeyframeSkip = false,
|
||||
bool aDoSeek = false);
|
||||
bool ReadAudio(AudioFrame *aFrame, int64_t aSeekTimeUs);
|
||||
|
||||
//Change decoder into a playing state
|
||||
nsresult Play();
|
||||
|
||||
//Change decoder into a paused state
|
||||
void Pause();
|
||||
|
||||
// Post kNotifyPostReleaseVideoBuffer message to OmxDecoder via ALooper.
|
||||
void PostReleaseVideoBuffer(MediaBuffer *aBuffer, const FenceHandle& aReleaseFenceHandle);
|
||||
// Receive a message from AHandlerReflector.
|
||||
// Called on ALooper thread.
|
||||
void onMessageReceived(const sp<AMessage> &msg);
|
||||
|
||||
sp<MediaSource> GetAudioOffloadTrack() { return mAudioOffloadTrack; }
|
||||
|
||||
void RecycleCallbackImp(TextureClient* aClient);
|
||||
|
||||
static void RecycleCallback(TextureClient* aClient, void* aClosure);
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -1,111 +0,0 @@
|
||||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
EXPORTS += [
|
||||
'AudioOffloadPlayerBase.h',
|
||||
'MediaOmxCommonDecoder.h',
|
||||
'MediaOmxCommonReader.h',
|
||||
'MediaOmxDecoder.h',
|
||||
'MediaOmxReader.h',
|
||||
]
|
||||
|
||||
SOURCES += [
|
||||
'MediaOmxCommonDecoder.cpp',
|
||||
'MediaOmxCommonReader.cpp',
|
||||
'MediaOmxDecoder.cpp',
|
||||
'MediaOmxReader.cpp',
|
||||
'MediaStreamSource.cpp',
|
||||
'OMXCodecProxy.cpp',
|
||||
'OmxDecoder.cpp',
|
||||
]
|
||||
|
||||
if CONFIG['MOZ_AUDIO_OFFLOAD']:
|
||||
EXPORTS += [
|
||||
'AudioOffloadPlayer.h',
|
||||
'AudioOutput.h',
|
||||
'GonkAudioSink.h',
|
||||
]
|
||||
SOURCES += [
|
||||
'AudioOffloadPlayer.cpp',
|
||||
'AudioOutput.cpp',
|
||||
]
|
||||
|
||||
if CONFIG['MOZ_OMX_ENCODER']:
|
||||
EXPORTS += [
|
||||
'OMXCodecWrapper.h',
|
||||
]
|
||||
SOURCES += [
|
||||
'OMXCodecDescriptorUtil.cpp',
|
||||
'OMXCodecWrapper.cpp',
|
||||
]
|
||||
LOCAL_INCLUDES += ['/media/libyuv/include']
|
||||
|
||||
if 'rtsp' in CONFIG['NECKO_PROTOCOLS']:
|
||||
EXPORTS += [
|
||||
'RtspExtractor.h',
|
||||
'RtspOmxDecoder.h',
|
||||
'RtspOmxReader.h',
|
||||
]
|
||||
SOURCES += [
|
||||
'RtspExtractor.cpp',
|
||||
'RtspOmxDecoder.cpp',
|
||||
'RtspOmxReader.cpp',
|
||||
]
|
||||
|
||||
if CONFIG['ANDROID_VERSION'] >= '18':
|
||||
EXPORTS += [
|
||||
'I420ColorConverterHelper.h',
|
||||
'MediaCodecProxy.h',
|
||||
]
|
||||
SOURCES += [
|
||||
'I420ColorConverterHelper.cpp',
|
||||
'MediaCodecProxy.cpp',
|
||||
]
|
||||
|
||||
include('/ipc/chromium/chromium-config.mozbuild')
|
||||
|
||||
# Suppress some GCC/clang warnings being treated as errors:
|
||||
# - about attributes on forward declarations for types that are already
|
||||
# defined, which complains about an important MOZ_EXPORT for android::AString
|
||||
# - about multi-character constants which are used in codec-related code
|
||||
# and are part of Android's libstagefright API style.
|
||||
if CONFIG['GNU_CC'] or CONFIG['CLANG_CL']:
|
||||
CXXFLAGS += [
|
||||
'-Wno-error=attributes',
|
||||
'-Wno-multichar'
|
||||
]
|
||||
|
||||
FINAL_LIBRARY = 'xul'
|
||||
LOCAL_INCLUDES += [
|
||||
'/dom/base',
|
||||
'/dom/html',
|
||||
'/ipc/chromium/src',
|
||||
]
|
||||
|
||||
if CONFIG['ANDROID_VERSION'] == '15':
|
||||
LOCAL_INCLUDES += [
|
||||
'%' + '%s/%s' % (CONFIG['ANDROID_SOURCE'], d) for d in [
|
||||
'dalvik/libnativehelper/include/nativehelper',
|
||||
'frameworks/base/include',
|
||||
'frameworks/base/include/binder',
|
||||
'frameworks/base/include/media',
|
||||
'frameworks/base/include/media/stagefright/openmax',
|
||||
'frameworks/base/include/utils',
|
||||
'frameworks/base/media/libstagefright/include',
|
||||
'hardware/libhardware/include',
|
||||
]
|
||||
]
|
||||
else:
|
||||
LOCAL_INCLUDES += [
|
||||
'%' + '%s/%s' % (CONFIG['ANDROID_SOURCE'], d) for d in [
|
||||
'frameworks/av/include/media',
|
||||
'frameworks/native/include',
|
||||
'frameworks/native/opengl/include',
|
||||
]
|
||||
]
|
||||
|
||||
if CONFIG['ANDROID_VERSION'] > '15':
|
||||
DEFINES['MOZ_OMX_WEBM_DECODER'] = True
|
@ -137,7 +137,6 @@ var haveMp4 = (getPref("media.wmf.enabled") && IsWindowsVistaOrLater()) ||
|
||||
IsMacOSSnowLeopardOrLater() ||
|
||||
(IsSupportedAndroid() &&
|
||||
(IsJellyBeanOrLater() || getPref("media.plugins.enabled"))) ||
|
||||
getPref("media.omx.enabled") ||
|
||||
(IsLinux() && getPref("media.ffmpeg.enabled"));
|
||||
|
||||
check_mp4(document.getElementById('v'), haveMp4);
|
||||
|
@ -91,7 +91,6 @@ SpecialPowers.pushPrefEnv(
|
||||
{
|
||||
"set": [
|
||||
["media.encoder.webm.enabled", false],
|
||||
["media.encoder.omx.enabled", false]
|
||||
]
|
||||
}, startTest);
|
||||
|
||||
|
@ -584,9 +584,6 @@ pref("media.webspeech.synth.enabled", false);
|
||||
#ifdef MOZ_WEBM_ENCODER
|
||||
pref("media.encoder.webm.enabled", true);
|
||||
#endif
|
||||
#ifdef MOZ_OMX_ENCODER
|
||||
pref("media.encoder.omx.enabled", true);
|
||||
#endif
|
||||
|
||||
// Whether to autostart a media element with an |autoplay| attribute
|
||||
pref("media.autoplay.enabled", true);
|
||||
|
@ -108,26 +108,16 @@ if test -n "$gonkdir"; then
|
||||
15)
|
||||
CPPFLAGS="-I$gonkdir/frameworks/base/opengl/include -I$gonkdir/frameworks/base/native/include -I$gonkdir/frameworks/base/include -I$gonkdir/frameworks/base/services/camera -I$gonkdir/frameworks/base/include/media/ -I$gonkdir/frameworks/base/include/media/stagefright -I$gonkdir/frameworks/base/include/media/stagefright/openmax -I$gonkdir/frameworks/base/media/libstagefright/rtsp -I$gonkdir/frameworks/base/media/libstagefright/include -I$gonkdir/external/dbus -I$gonkdir/external/bluetooth/bluez/lib -I$gonkdir/dalvik/libnativehelper/include/nativehelper $CPPFLAGS"
|
||||
MOZ_NFC=1
|
||||
MOZ_OMX_DECODER=1
|
||||
AC_SUBST(MOZ_OMX_DECODER)
|
||||
MOZ_SECUREELEMENT=1
|
||||
;;
|
||||
17|18)
|
||||
CPPFLAGS="-I$gonkdir/frameworks/native/include -I$gonkdir/frameworks/av/include -I$gonkdir/frameworks/av/include/media -I$gonkdir/frameworks/av/include/camera -I$gonkdir/frameworks/native/include/media/openmax -I$gonkdir/frameworks/av/media/libstagefright/include $CPPFLAGS"
|
||||
MOZ_NFC=1
|
||||
MOZ_OMX_DECODER=1
|
||||
AC_SUBST(MOZ_OMX_DECODER)
|
||||
MOZ_OMX_ENCODER=1
|
||||
AC_SUBST(MOZ_OMX_ENCODER)
|
||||
AC_DEFINE(MOZ_OMX_ENCODER)
|
||||
MOZ_SECUREELEMENT=1
|
||||
;;
|
||||
19)
|
||||
CPPFLAGS="-I$gonkdir/frameworks/native/include -I$gonkdir/frameworks/av/include -I$gonkdir/frameworks/av/include/media -I$gonkdir/frameworks/av/include/camera -I$gonkdir/frameworks/native/include/media/openmax -I$gonkdir/frameworks/av/media/libstagefright/include $CPPFLAGS"
|
||||
MOZ_NFC=1
|
||||
MOZ_OMX_DECODER=1
|
||||
MOZ_OMX_ENCODER=1
|
||||
AC_DEFINE(MOZ_OMX_ENCODER)
|
||||
MOZ_AUDIO_OFFLOAD=1
|
||||
MOZ_SECUREELEMENT=1
|
||||
AC_SUBST(MOZ_AUDIO_OFFLOAD)
|
||||
@ -136,9 +126,6 @@ if test -n "$gonkdir"; then
|
||||
21|22)
|
||||
CPPFLAGS="-I$gonkdir/frameworks/native/include -I$gonkdir/frameworks/av/include -I$gonkdir/frameworks/av/include/media -I$gonkdir/frameworks/av/include/camera -I$gonkdir/frameworks/native/include/media/openmax -I$gonkdir/frameworks/av/media/libstagefright/include $CPPFLAGS"
|
||||
MOZ_AUDIO_OFFLOAD=1
|
||||
MOZ_OMX_DECODER=1
|
||||
MOZ_OMX_ENCODER=1
|
||||
AC_DEFINE(MOZ_OMX_ENCODER)
|
||||
AC_SUBST(MOZ_AUDIO_OFFLOAD)
|
||||
AC_DEFINE(MOZ_AUDIO_OFFLOAD)
|
||||
MOZ_NFC=1
|
||||
|
@ -87,9 +87,6 @@ DEFINES['HAVE_OFF64_T'] = True
|
||||
DEFINES['SK_BUILD_FOR_ANDROID_NDK'] = True
|
||||
DEFINES['HAVE_POSIX_CLOCKS'] = True
|
||||
|
||||
if CONFIG['MOZ_OMX_DECODER']:
|
||||
DEFINES['MOZ_OMX_DECODER'] = True
|
||||
|
||||
LOCAL_INCLUDES += [
|
||||
'%' + '%s/%s' % (CONFIG['ANDROID_SOURCE'], d) for d in [
|
||||
'frameworks/native/opengl/include',
|
||||
|
@ -48,7 +48,7 @@ elif CONFIG['ANDROID_VERSION'] in ('17', '18'):
|
||||
'GonkNativeWindowJB.h',
|
||||
]
|
||||
|
||||
if CONFIG['MOZ_OMX_DECODER'] or CONFIG['MOZ_WEBRTC']:
|
||||
if CONFIG['MOZ_WEBRTC']:
|
||||
if CONFIG['ANDROID_VERSION'] >= '21':
|
||||
SOURCES += [
|
||||
'GonkBufferQueueLL/GonkBufferItem.cpp',
|
||||
|
@ -889,9 +889,6 @@ nsAppShell::Init()
|
||||
printf("*** This is stdout. Most of the useful output will be in logcat.\n");
|
||||
printf("***\n");
|
||||
printf("*****************************************************************\n");
|
||||
#if ANDROID_VERSION >= 18 && defined(MOZ_OMX_DECODER)
|
||||
android::FakeSurfaceComposer::instantiate();
|
||||
#endif
|
||||
GonkPermissionService::instantiate();
|
||||
|
||||
// Causes the kernel timezone to be set, which in turn causes the
|
||||
|
Loading…
Reference in New Issue
Block a user