summaryrefslogtreecommitdiff
path: root/media/base
diff options
context:
space:
mode:
authorTorne (Richard Coles) <torne@google.com>2013-10-31 11:16:26 +0000
committerTorne (Richard Coles) <torne@google.com>2013-10-31 11:16:26 +0000
commit1e9bf3e0803691d0a228da41fc608347b6db4340 (patch)
treeab2e5565f71b4219b3da406e19f16fe306704ef5 /media/base
parentf10b58d5bc6ae3e74076fc4ccca14cbc57ef805c (diff)
downloadchromium_org-1e9bf3e0803691d0a228da41fc608347b6db4340.tar.gz
Merge from Chromium at DEPS revision 232015
This commit was generated by merge_to_master.py. Change-Id: If86767ad396b9e2e1a4c1e9df1427daea29703ef
Diffstat (limited to 'media/base')
-rw-r--r--media/base/android/demuxer_android.h16
-rw-r--r--media/base/android/java/src/org/chromium/media/MediaCodecBridge.java13
-rw-r--r--media/base/android/java/src/org/chromium/media/WebAudioMediaCodecBridge.java36
-rw-r--r--media/base/android/media_codec_bridge.cc47
-rw-r--r--media/base/android/media_codec_bridge.h21
-rw-r--r--media/base/android/media_decoder_job.cc52
-rw-r--r--media/base/android/media_decoder_job.h22
-rw-r--r--media/base/android/media_source_player.cc184
-rw-r--r--media/base/android/media_source_player.h42
-rw-r--r--media/base/android/media_source_player_unittest.cc988
-rw-r--r--media/base/audio_bus_perftest.cc52
-rw-r--r--media/base/audio_bus_unittest.cc56
-rw-r--r--media/base/audio_converter_perftest.cc79
-rw-r--r--media/base/audio_converter_unittest.cc108
-rw-r--r--media/base/media_switches.cc14
-rw-r--r--media/base/media_switches.h4
-rw-r--r--media/base/sinc_resampler.h2
-rw-r--r--media/base/sinc_resampler_perftest.cc76
-rw-r--r--media/base/sinc_resampler_unittest.cc77
-rw-r--r--media/base/vector_math_perftest.cc124
-rw-r--r--media/base/vector_math_unittest.cc184
21 files changed, 1520 insertions, 677 deletions
diff --git a/media/base/android/demuxer_android.h b/media/base/android/demuxer_android.h
index 579154518c..865dc9d33f 100644
--- a/media/base/android/demuxer_android.h
+++ b/media/base/android/demuxer_android.h
@@ -32,7 +32,13 @@ class MEDIA_EXPORT DemuxerAndroid {
virtual void RequestDemuxerData(media::DemuxerStream::Type type) = 0;
// Called to request the demuxer to seek to a particular media time.
- virtual void RequestDemuxerSeek(const base::TimeDelta& time_to_seek) = 0;
+ // |is_browser_seek| is true if the renderer is not previously expecting this
+ // seek and must coordinate with other regular seeks. Browser seek existence
+ // should be hidden as much as possible from the renderer player and web apps.
+ // TODO(wolenetz): Instead of doing browser seek, replay cached data since
+ // last keyframe. See http://crbug.com/304234.
+ virtual void RequestDemuxerSeek(const base::TimeDelta& time_to_seek,
+ bool is_browser_seek) = 0;
};
// Defines the client callback interface.
@@ -50,7 +56,13 @@ class MEDIA_EXPORT DemuxerAndroidClient {
virtual void OnDemuxerDataAvailable(const DemuxerData& params) = 0;
// Called in response to RequestDemuxerSeek().
- virtual void OnDemuxerSeekDone() = 0;
+ // If this is in response to a request with |is_browser_seek| set to true,
+ // then |actual_browser_seek_time| may differ from the requested
+ // |time_to_seek|, and reflects the actual time seeked to by the demuxer.
+ // For regular demuxer seeks, |actual_browser_seek_time| is kNoTimestamp() and
+ // should be ignored by browser player.
+ virtual void OnDemuxerSeekDone(
+ const base::TimeDelta& actual_browser_seek_time) = 0;
// Called whenever the demuxer has detected a duration change.
virtual void OnDemuxerDurationChanged(base::TimeDelta duration) = 0;
diff --git a/media/base/android/java/src/org/chromium/media/MediaCodecBridge.java b/media/base/android/java/src/org/chromium/media/MediaCodecBridge.java
index d9bb48b839..74bec56745 100644
--- a/media/base/android/java/src/org/chromium/media/MediaCodecBridge.java
+++ b/media/base/android/java/src/org/chromium/media/MediaCodecBridge.java
@@ -81,11 +81,13 @@ class MediaCodecBridge {
* This class represents supported android codec information.
*/
private static class CodecInfo {
- private final String mCodecType;
+ private final String mCodecType; // e.g. "video/x-vnd.on2.vp8".
+ private final String mCodecName; // e.g. "OMX.google.vp8.decoder".
private final boolean mIsSecureDecoderSupported;
- private CodecInfo(String codecType, boolean isSecureDecoderSupported) {
+ private CodecInfo(String codecType, String codecName, boolean isSecureDecoderSupported) {
mCodecType = codecType;
+ mCodecName = codecName;
mIsSecureDecoderSupported = isSecureDecoderSupported;
}
@@ -93,6 +95,9 @@ class MediaCodecBridge {
private String codecType() { return mCodecType; }
@CalledByNative("CodecInfo")
+ private String codecName() { return mCodecName; }
+
+ @CalledByNative("CodecInfo")
private boolean isSecureDecoderSupported() { return mIsSecureDecoderSupported; }
}
@@ -159,8 +164,8 @@ class MediaCodecBridge {
}
for (int j = 0; j < supportedTypes.length; ++j) {
if (!CodecInfoMap.containsKey(supportedTypes[j]) || secureDecoderSupported) {
- CodecInfoMap.put(supportedTypes[j],
- new CodecInfo(supportedTypes[j], secureDecoderSupported));
+ CodecInfoMap.put(supportedTypes[j], new CodecInfo(
+ supportedTypes[j], codecString, secureDecoderSupported));
}
}
}
diff --git a/media/base/android/java/src/org/chromium/media/WebAudioMediaCodecBridge.java b/media/base/android/java/src/org/chromium/media/WebAudioMediaCodecBridge.java
index 1de7e42b8d..17720e6671 100644
--- a/media/base/android/java/src/org/chromium/media/WebAudioMediaCodecBridge.java
+++ b/media/base/android/java/src/org/chromium/media/WebAudioMediaCodecBridge.java
@@ -21,7 +21,6 @@ import org.chromium.base.JNINamespace;
@JNINamespace("media")
class WebAudioMediaCodecBridge {
- private static final boolean DEBUG = true;
static final String LOG_TAG = "WebAudioMediaCodec";
// TODO(rtoy): What is the correct timeout value for reading
// from a file in memory?
@@ -81,18 +80,8 @@ class WebAudioMediaCodecBridge {
}
}
- if (DEBUG) {
- Log.d(LOG_TAG, "Tracks: " + extractor.getTrackCount()
- + " Rate: " + sampleRate
- + " Channels: " + inputChannelCount
- + " Mime: " + mime
- + " Duration: " + durationMicroseconds + " microsec");
- }
-
- nativeInitializeDestination(nativeMediaCodecBridge,
- inputChannelCount,
- sampleRate,
- durationMicroseconds);
+ Log.d(LOG_TAG, "Initial: Tracks: " + extractor.getTrackCount() +
+ " Format: " + format);
// Create decoder
MediaCodec codec = MediaCodec.createDecoderByType(mime);
@@ -107,6 +96,7 @@ class WebAudioMediaCodecBridge {
boolean sawInputEOS = false;
boolean sawOutputEOS = false;
+ boolean destinationInitialized = false;
// Keep processing until the output is done.
while (!sawOutputEOS) {
@@ -145,7 +135,24 @@ class WebAudioMediaCodecBridge {
if (outputBufIndex >= 0) {
ByteBuffer buf = codecOutputBuffers[outputBufIndex];
- if (info.size > 0) {
+ if (!destinationInitialized) {
+ // Initialize the destination as late as possible to
+ // catch any changes in format. But be sure to
+ // initialize it BEFORE we send any decoded audio,
+ // and only initialize once.
+ Log.d(LOG_TAG, "Final: Rate: " + sampleRate +
+ " Channels: " + inputChannelCount +
+ " Mime: " + mime +
+ " Duration: " + durationMicroseconds + " microsec");
+
+ nativeInitializeDestination(nativeMediaCodecBridge,
+ inputChannelCount,
+ sampleRate,
+ durationMicroseconds);
+ destinationInitialized = true;
+ }
+
+ if (destinationInitialized && info.size > 0) {
nativeOnChunkDecoded(nativeMediaCodecBridge, buf, info.size,
inputChannelCount, outputChannelCount);
}
@@ -161,6 +168,7 @@ class WebAudioMediaCodecBridge {
} else if (outputBufIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
MediaFormat newFormat = codec.getOutputFormat();
outputChannelCount = newFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
+ sampleRate = newFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE);
Log.d(LOG_TAG, "output format changed to " + newFormat);
}
}
diff --git a/media/base/android/media_codec_bridge.cc b/media/base/android/media_codec_bridge.cc
index 75d103c353..1c4928aad9 100644
--- a/media/base/android/media_codec_bridge.cc
+++ b/media/base/android/media_codec_bridge.cc
@@ -15,6 +15,7 @@
#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/safe_numerics.h"
+#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "jni/MediaCodecBridge_jni.h"
#include "media/base/bit_reader.h"
@@ -29,7 +30,7 @@ namespace media {
enum { kBufferFlagEndOfStream = 4 };
-static const std::string AudioCodecToAndroidMimeType(const AudioCodec codec) {
+static const std::string AudioCodecToAndroidMimeType(const AudioCodec& codec) {
switch (codec) {
case kCodecMP3:
return "audio/mpeg";
@@ -42,7 +43,7 @@ static const std::string AudioCodecToAndroidMimeType(const AudioCodec codec) {
}
}
-static const std::string VideoCodecToAndroidMimeType(const VideoCodec codec) {
+static const std::string VideoCodecToAndroidMimeType(const VideoCodec& codec) {
switch (codec) {
case kCodecH264:
return "video/avc";
@@ -106,6 +107,7 @@ void MediaCodecBridge::GetCodecsInfo(
return;
std::string mime_type;
+ std::string codec_name;
ScopedJavaLocalRef<jobjectArray> j_codec_info_array =
Java_MediaCodecBridge_getCodecsInfo(env);
jsize len = env->GetArrayLength(j_codec_info_array.obj());
@@ -115,8 +117,11 @@ void MediaCodecBridge::GetCodecsInfo(
ScopedJavaLocalRef<jstring> j_codec_type =
Java_CodecInfo_codecType(env, j_info.obj());
ConvertJavaStringToUTF8(env, j_codec_type.obj(), &mime_type);
+ ScopedJavaLocalRef<jstring> j_codec_name =
+ Java_CodecInfo_codecName(env, j_info.obj());
CodecsInfo info;
info.codecs = AndroidMimeTypeToCodecType(mime_type);
+ ConvertJavaStringToUTF8(env, j_codec_name.obj(), &info.name);
info.secure_decoder_supported =
Java_CodecInfo_isSecureDecoderSupported(env, j_info.obj());
codecs_info->push_back(info);
@@ -139,6 +144,22 @@ bool MediaCodecBridge::CanDecode(const std::string& codec, bool is_secure) {
return false;
}
+// static
+bool MediaCodecBridge::IsKnownUnaccelerated(const std::string& mime_type) {
+ std::string codec_type = AndroidMimeTypeToCodecType(mime_type);
+ std::vector<media::MediaCodecBridge::CodecsInfo> codecs_info;
+ media::MediaCodecBridge::GetCodecsInfo(&codecs_info);
+ for (size_t i = 0; i < codecs_info.size(); ++i) {
+ if (codecs_info[i].codecs == codec_type) {
+ // It would be nice if MediaCodecInfo externalized some notion of
+ // HW-acceleration but it doesn't. Android Media guidance is that the
+ // prefix below is always used for SW decoders, so that's what we use.
+ return StartsWithASCII(codecs_info[i].name, "OMX.google.", true);
+ }
+ }
+ return true;
+}
+
MediaCodecBridge::MediaCodecBridge(const std::string& mime, bool is_secure) {
JNIEnv* env = AttachCurrentThread();
CHECK(env);
@@ -312,7 +333,7 @@ AudioCodecBridge::AudioCodecBridge(const std::string& mime)
}
bool AudioCodecBridge::Start(
- const AudioCodec codec, int sample_rate, int channel_count,
+ const AudioCodec& codec, int sample_rate, int channel_count,
const uint8* extra_data, size_t extra_data_size, bool play_audio,
jobject media_crypto) {
JNIEnv* env = AttachCurrentThread();
@@ -343,7 +364,7 @@ bool AudioCodecBridge::Start(
}
bool AudioCodecBridge::ConfigureMediaFormat(
- jobject j_format, const AudioCodec codec, const uint8* extra_data,
+ jobject j_format, const AudioCodec& codec, const uint8* extra_data,
size_t extra_data_size) {
if (extra_data_size == 0)
return true;
@@ -470,7 +491,7 @@ VideoCodecBridge::VideoCodecBridge(const std::string& mime, bool is_secure)
}
bool VideoCodecBridge::Start(
- const VideoCodec codec, const gfx::Size& size, jobject surface,
+ const VideoCodec& codec, const gfx::Size& size, jobject surface,
jobject media_crypto) {
JNIEnv* env = AttachCurrentThread();
@@ -495,17 +516,29 @@ bool VideoCodecBridge::Start(
return StartInternal();
}
-AudioCodecBridge* AudioCodecBridge::Create(const AudioCodec codec) {
+AudioCodecBridge* AudioCodecBridge::Create(const AudioCodec& codec) {
const std::string mime = AudioCodecToAndroidMimeType(codec);
return mime.empty() ? NULL : new AudioCodecBridge(mime);
}
-VideoCodecBridge* VideoCodecBridge::Create(const VideoCodec codec,
+// static
+bool AudioCodecBridge::IsKnownUnaccelerated(const AudioCodec& codec) {
+ return MediaCodecBridge::IsKnownUnaccelerated(
+ AudioCodecToAndroidMimeType(codec));
+}
+
+VideoCodecBridge* VideoCodecBridge::Create(const VideoCodec& codec,
bool is_secure) {
const std::string mime = VideoCodecToAndroidMimeType(codec);
return mime.empty() ? NULL : new VideoCodecBridge(mime, is_secure);
}
+// static
+bool VideoCodecBridge::IsKnownUnaccelerated(const VideoCodec& codec) {
+ return MediaCodecBridge::IsKnownUnaccelerated(
+ VideoCodecToAndroidMimeType(codec));
+}
+
bool MediaCodecBridge::RegisterMediaCodecBridge(JNIEnv* env) {
return RegisterNativesImpl(env);
}
diff --git a/media/base/android/media_codec_bridge.h b/media/base/android/media_codec_bridge.h
index 15b707c18a..64c73571c6 100644
--- a/media/base/android/media_codec_bridge.h
+++ b/media/base/android/media_codec_bridge.h
@@ -54,6 +54,7 @@ class MEDIA_EXPORT MediaCodecBridge {
// need more specific codecs separated by comma. (e.g. "vp8" -> "vp8, vp8.0")
struct CodecsInfo {
std::string codecs;
+ std::string name;
bool secure_decoder_supported;
};
@@ -137,6 +138,10 @@ class MEDIA_EXPORT MediaCodecBridge {
static bool RegisterMediaCodecBridge(JNIEnv* env);
protected:
+ // Returns true if |mime_type| is known to be unaccelerated (i.e. backed by a
+ // software codec instead of a hardware one).
+ static bool IsKnownUnaccelerated(const std::string& mime_type);
+
MediaCodecBridge(const std::string& mime, bool is_secure);
// Calls start() against the media codec instance. Used in StartXXX() after
@@ -163,10 +168,13 @@ class AudioCodecBridge : public MediaCodecBridge {
public:
// Returns an AudioCodecBridge instance if |codec| is supported, or a NULL
// pointer otherwise.
- static AudioCodecBridge* Create(const AudioCodec codec);
+ static AudioCodecBridge* Create(const AudioCodec& codec);
+
+ // See MediaCodecBridge::IsKnownUnaccelerated().
+ static bool IsKnownUnaccelerated(const AudioCodec& codec);
// Start the audio codec bridge.
- bool Start(const AudioCodec codec, int sample_rate, int channel_count,
+ bool Start(const AudioCodec& codec, int sample_rate, int channel_count,
const uint8* extra_data, size_t extra_data_size,
bool play_audio, jobject media_crypto) WARN_UNUSED_RESULT;
@@ -181,7 +189,7 @@ class AudioCodecBridge : public MediaCodecBridge {
explicit AudioCodecBridge(const std::string& mime);
// Configure the java MediaFormat object with the extra codec data passed in.
- bool ConfigureMediaFormat(jobject j_format, const AudioCodec codec,
+ bool ConfigureMediaFormat(jobject j_format, const AudioCodec& codec,
const uint8* extra_data, size_t extra_data_size);
};
@@ -189,11 +197,14 @@ class MEDIA_EXPORT VideoCodecBridge : public MediaCodecBridge {
public:
// Returns an VideoCodecBridge instance if |codec| is supported, or a NULL
// pointer otherwise.
- static VideoCodecBridge* Create(const VideoCodec codec, bool is_secure);
+ static VideoCodecBridge* Create(const VideoCodec& codec, bool is_secure);
+
+ // See MediaCodecBridge::IsKnownUnaccelerated().
+ static bool IsKnownUnaccelerated(const VideoCodec& codec);
// Start the video codec bridge.
// TODO(qinmin): Pass codec specific data if available.
- bool Start(const VideoCodec codec, const gfx::Size& size, jobject surface,
+ bool Start(const VideoCodec& codec, const gfx::Size& size, jobject surface,
jobject media_crypto);
private:
diff --git a/media/base/android/media_decoder_job.cc b/media/base/android/media_decoder_job.cc
index 59db63a6e8..cd8d04cfe3 100644
--- a/media/base/android/media_decoder_job.cc
+++ b/media/base/android/media_decoder_job.cc
@@ -28,6 +28,7 @@ MediaDecoderJob::MediaDecoderJob(
media_codec_bridge_(media_codec_bridge),
needs_flush_(false),
input_eos_encountered_(false),
+ prerolling_(true),
weak_this_(this),
request_data_cb_(request_data_cb),
access_unit_index_(0),
@@ -76,7 +77,7 @@ void MediaDecoderJob::Prefetch(const base::Closure& prefetch_cb) {
bool MediaDecoderJob::Decode(
const base::TimeTicks& start_time_ticks,
const base::TimeDelta& start_presentation_timestamp,
- const MediaDecoderJob::DecoderCallback& callback) {
+ const DecoderCallback& callback) {
DCHECK(decode_cb_.is_null());
DCHECK(on_data_received_cb_.is_null());
DCHECK(ui_loop_->BelongsToCurrentThread());
@@ -121,10 +122,22 @@ void MediaDecoderJob::Flush() {
on_data_received_cb_.Reset();
}
+void MediaDecoderJob::BeginPrerolling(
+ const base::TimeDelta& preroll_timestamp) {
+ DVLOG(1) << __FUNCTION__ << "(" << preroll_timestamp.InSecondsF() << ")";
+ DCHECK(ui_loop_->BelongsToCurrentThread());
+ DCHECK(!is_decoding());
+
+ preroll_timestamp_ = preroll_timestamp;
+ prerolling_ = true;
+}
+
void MediaDecoderJob::Release() {
DCHECK(ui_loop_->BelongsToCurrentThread());
- destroy_pending_ = is_decoding();
+ // If the decoder job is not waiting for data, and is still decoding, we
+ // cannot delete the job immediately.
+ destroy_pending_ = on_data_received_cb_.is_null() && is_decoding();
request_data_cb_.Reset();
on_data_received_cb_.Reset();
@@ -221,6 +234,19 @@ void MediaDecoderJob::DecodeNextAccessUnit(
DCHECK(ui_loop_->BelongsToCurrentThread());
DCHECK(!decode_cb_.is_null());
+ // If the first access unit is a config change, request the player to dequeue
+ // the input buffer again so that it can request config data.
+ if (received_data_.access_units[access_unit_index_].status ==
+ DemuxerStream::kConfigChanged) {
+ ui_loop_->PostTask(FROM_HERE,
+ base::Bind(&MediaDecoderJob::OnDecodeCompleted,
+ base::Unretained(this),
+ MEDIA_CODEC_DEQUEUE_INPUT_AGAIN_LATER,
+ kNoTimestamp(),
+ 0));
+ return;
+ }
+
decoder_loop_->PostTask(FROM_HERE, base::Bind(
&MediaDecoderJob::DecodeInternal, base::Unretained(this),
received_data_.access_units[access_unit_index_],
@@ -282,11 +308,9 @@ void MediaDecoderJob::DecodeInternal(
&output_eos_encountered);
if (status != MEDIA_CODEC_OK) {
- if (status == MEDIA_CODEC_OUTPUT_BUFFERS_CHANGED) {
- if (media_codec_bridge_->GetOutputBuffers())
- status = MEDIA_CODEC_OK;
- else
- status = MEDIA_CODEC_ERROR;
+ if (status == MEDIA_CODEC_OUTPUT_BUFFERS_CHANGED &&
+ !media_codec_bridge_->GetOutputBuffers()) {
+ status = MEDIA_CODEC_ERROR;
}
callback.Run(status, kNoTimestamp(), 0);
return;
@@ -299,9 +323,12 @@ void MediaDecoderJob::DecodeInternal(
status = MEDIA_CODEC_INPUT_END_OF_STREAM;
// Check whether we need to render the output.
- // TODO(qinmin): comparing |unit.timestamp| with |preroll_timestamp_| is not
- // accurate due to data reordering. Need to use the |presentation_timestamp|
- // for video, and use |size| to calculate the timestamp for audio.
+ // TODO(qinmin): comparing most recently queued input's |unit.timestamp| with
+ // |preroll_timestamp_| is not accurate due to data reordering and possible
+ // input queueing without immediate dequeue when |input_status| !=
+ // |MEDIA_CODEC_OK|. Need to use the |presentation_timestamp| for video, and
+ // use |size| to calculate the timestamp for audio. See
+ // http://crbug.com/310823 and b/11356652.
bool render_output = unit.timestamp >= preroll_timestamp_ &&
(status != MEDIA_CODEC_OUTPUT_END_OF_STREAM || size != 0u);
base::TimeDelta time_to_render;
@@ -350,6 +377,11 @@ void MediaDecoderJob::OnDecodeCompleted(
}
DCHECK(!decode_cb_.is_null());
+
+ // If output was queued for rendering, then we have completed prerolling.
+ if (presentation_timestamp != kNoTimestamp())
+ prerolling_ = false;
+
switch (status) {
case MEDIA_CODEC_OK:
case MEDIA_CODEC_DEQUEUE_OUTPUT_AGAIN_LATER:
diff --git a/media/base/android/media_decoder_job.h b/media/base/android/media_decoder_job.h
index ea3956ebec..e9530b39e0 100644
--- a/media/base/android/media_decoder_job.h
+++ b/media/base/android/media_decoder_job.h
@@ -27,6 +27,9 @@ class MediaDecoderJob {
// Callback when a decoder job finishes its work. Args: whether decode
// finished successfully, presentation time, audio output bytes.
+ // If the presentation time is equal to kNoTimestamp(), the decoder job
+ // skipped rendering of the decoded output and the callback target should
+ // update its clock to avoid introducing extra delays to the next frame.
typedef base::Callback<void(MediaCodecStatus, const base::TimeDelta&,
size_t)> DecoderCallback;
// Callback when a decoder job finishes releasing the output buffer.
@@ -66,9 +69,10 @@ class MediaDecoderJob {
// Flush the decoder.
void Flush();
- void set_preroll_timestamp(const base::TimeDelta& preroll_timestamp) {
- preroll_timestamp_ = preroll_timestamp;
- }
+ // Enter prerolling state. The job must not currently be decoding.
+ void BeginPrerolling(const base::TimeDelta& preroll_timestamp);
+
+ bool prerolling() const { return prerolling_; }
bool is_decoding() const { return !decode_cb_.is_null(); }
@@ -120,9 +124,8 @@ class MediaDecoderJob {
const DecoderCallback& callback);
// Called on the UI thread to indicate that one decode cycle has completed.
- // If the |presentation_timestamp| is equal to kNoTimestamp(),
- // the caller should ignore the output of this frame and update its clock to
- // avoid introducing extra delays to the next frame.
+ // Completes any pending job destruction or any pending decode stop. If
+ // destruction was not pending, passes its arguments to |decode_cb_|.
void OnDecodeCompleted(MediaCodecStatus status,
const base::TimeDelta& presentation_timestamp,
size_t audio_output_bytes);
@@ -149,6 +152,13 @@ class MediaDecoderJob {
// is not very accurate.
base::TimeDelta preroll_timestamp_;
+ // Indicates prerolling state. If true, this job has not yet decoded output
+ // that it will render, since the most recent of job construction or
+ // BeginPrerolling(). If false, |preroll_timestamp_| has been reached.
+ // TODO(qinmin): Comparing access unit's timestamp with |preroll_timestamp_|
+ // is not very accurate.
+ bool prerolling_;
+
// Weak pointer passed to media decoder jobs for callbacks. It is bounded to
// the decoder thread.
base::WeakPtrFactory<MediaDecoderJob> weak_this_;
diff --git a/media/base/android/media_source_player.cc b/media/base/android/media_source_player.cc
index 5f992ddc51..8e23a91629 100644
--- a/media/base/android/media_source_player.cc
+++ b/media/base/android/media_source_player.cc
@@ -79,7 +79,9 @@ MediaSourcePlayer::MediaSourcePlayer(
is_video_encrypted_(false),
volume_(-1.0),
clock_(&default_tick_clock_),
- surface_ever_set_(false),
+ next_video_data_is_iframe_(true),
+ doing_browser_seek_(false),
+ pending_seek_(false),
reconfig_audio_decoder_(false),
reconfig_video_decoder_(false),
weak_this_(this),
@@ -108,52 +110,53 @@ void MediaSourcePlayer::SetVideoSurface(gfx::ScopedJavaSurface surface) {
// processed.
if (IsEventPending(SURFACE_CHANGE_EVENT_PENDING))
return;
- SetPendingEvent(SURFACE_CHANGE_EVENT_PENDING);
-
- // Setting a new surface will require a new MediaCodec to be created.
- // Request a seek so that the new decoder will decode an I-frame first.
- // Or otherwise, the new MediaCodec might crash. See b/8950387.
- if (!surface_ever_set_) {
- // Don't work-around to reach I-frame if we never previously set
- // |surface_|. In this case, we never fed data to decoder, and next data
- // should begin with I-frame.
- surface_ever_set_ = true;
-
- // If seek is already pending, process of the pending surface change
- // event will occur in OnDemuxerSeekDone(). Otherwise, we need to
- // trigger pending event processing now.
- if (!IsEventPending(SEEK_EVENT_PENDING))
- ProcessPendingEvents();
+ // Eventual processing of surface change will take care of feeding the new
+ // video decoder initially with I-frame. See b/8950387.
+ SetPendingEvent(SURFACE_CHANGE_EVENT_PENDING);
+ // If seek is already pending, processing of the pending surface change
+ // event will occur in OnDemuxerSeekDone().
+ if (IsEventPending(SEEK_EVENT_PENDING))
return;
- }
- if (IsEventPending(SEEK_EVENT_PENDING)) {
- // Waiting for the seek to finish.
+ // If video config change is already pending, processing of the pending
+ // surface change event will occur in OnDemuxerConfigsAvailable().
+ if (reconfig_video_decoder_ && IsEventPending(CONFIG_CHANGE_EVENT_PENDING))
return;
- }
- // TODO(wolenetz): We need to either coordinate seek with renderer or
- // add functionality to know which data contains I-frames and only begin
- // feeding a new decoder with such frames. The following will likely cause
- // ChunkDemuxer to fail in renderer because it is not setup to expect seek.
- ScheduleSeekEventAndStopDecoding();
+ // Otherwise we need to trigger pending event processing now.
+ ProcessPendingEvents();
}
-void MediaSourcePlayer::ScheduleSeekEventAndStopDecoding() {
+void MediaSourcePlayer::ScheduleSeekEventAndStopDecoding(
+ const base::TimeDelta& seek_time) {
+ DVLOG(1) << __FUNCTION__ << "(" << seek_time.InSecondsF() << ")";
+ DCHECK(!IsEventPending(SEEK_EVENT_PENDING));
+
+ pending_seek_ = false;
+
+ clock_.SetTime(seek_time, seek_time);
+ if (audio_timestamp_helper_)
+ audio_timestamp_helper_->SetBaseTimestamp(seek_time);
+
if (audio_decoder_job_ && audio_decoder_job_->is_decoding())
audio_decoder_job_->StopDecode();
if (video_decoder_job_ && video_decoder_job_->is_decoding())
video_decoder_job_->StopDecode();
- if (IsEventPending(SEEK_EVENT_PENDING))
- return;
-
SetPendingEvent(SEEK_EVENT_PENDING);
ProcessPendingEvents();
}
+void MediaSourcePlayer::BrowserSeekToCurrentTime() {
+ DVLOG(1) << __FUNCTION__;
+
+ DCHECK(!IsEventPending(SEEK_EVENT_PENDING));
+ doing_browser_seek_ = true;
+ ScheduleSeekEventAndStopDecoding(GetCurrentTime());
+}
+
bool MediaSourcePlayer::Seekable() {
// If the duration TimeDelta, converted to milliseconds from microseconds,
// is >= 2^31, then the media is assumed to be unbounded and unseekable.
@@ -201,10 +204,20 @@ int MediaSourcePlayer::GetVideoHeight() {
void MediaSourcePlayer::SeekTo(const base::TimeDelta& timestamp) {
DVLOG(1) << __FUNCTION__ << "(" << timestamp.InSecondsF() << ")";
- clock_.SetTime(timestamp, timestamp);
- if (audio_timestamp_helper_)
- audio_timestamp_helper_->SetBaseTimestamp(timestamp);
- ScheduleSeekEventAndStopDecoding();
+ if (IsEventPending(SEEK_EVENT_PENDING)) {
+ DCHECK(doing_browser_seek_) << "SeekTo while SeekTo in progress";
+ DCHECK(!pending_seek_) << "SeekTo while SeekTo pending browser seek";
+
+ // There is a browser seek currently in progress to obtain I-frame to feed
+ // a newly constructed video decoder. Remember this real seek request so
+ // it can be initiated once OnDemuxerSeekDone() occurs for the browser seek.
+ pending_seek_ = true;
+ pending_seek_time_ = timestamp;
+ return;
+ }
+
+ doing_browser_seek_ = false;
+ ScheduleSeekEventAndStopDecoding(timestamp);
}
base::TimeDelta MediaSourcePlayer::GetCurrentTime() {
@@ -317,13 +330,8 @@ void MediaSourcePlayer::OnDemuxerConfigsAvailable(
if (reconfig_audio_decoder_)
ConfigureAudioDecoderJob();
- // If there is a pending surface change, we can merge it with the config
- // change.
- if (reconfig_video_decoder_) {
- if (IsEventPending(SURFACE_CHANGE_EVENT_PENDING))
- ClearPendingEvent(SURFACE_CHANGE_EVENT_PENDING);
+ if (reconfig_video_decoder_)
ConfigureVideoDecoderJob();
- }
ClearPendingEvent(CONFIG_CHANGE_EVENT_PENDING);
@@ -336,10 +344,13 @@ void MediaSourcePlayer::OnDemuxerConfigsAvailable(
void MediaSourcePlayer::OnDemuxerDataAvailable(const DemuxerData& data) {
DVLOG(1) << __FUNCTION__ << "(" << data.type << ")";
DCHECK_LT(0u, data.access_units.size());
- if (data.type == DemuxerStream::AUDIO && audio_decoder_job_)
+ if (data.type == DemuxerStream::AUDIO && audio_decoder_job_) {
audio_decoder_job_->OnDataReceived(data);
- else if (data.type == DemuxerStream::VIDEO && video_decoder_job_)
- video_decoder_job_->OnDataReceived(data);
+ } else if (data.type == DemuxerStream::VIDEO) {
+ next_video_data_is_iframe_ = false;
+ if (video_decoder_job_)
+ video_decoder_job_->OnDataReceived(data);
+ }
}
void MediaSourcePlayer::OnDemuxerDurationChanged(base::TimeDelta duration) {
@@ -384,20 +395,51 @@ void MediaSourcePlayer::SetDrmBridge(MediaDrmBridge* drm_bridge) {
StartInternal();
}
-void MediaSourcePlayer::OnDemuxerSeekDone() {
+void MediaSourcePlayer::OnDemuxerSeekDone(
+ const base::TimeDelta& actual_browser_seek_time) {
DVLOG(1) << __FUNCTION__;
ClearPendingEvent(SEEK_EVENT_PENDING);
+
+ next_video_data_is_iframe_ = true;
+
+ if (pending_seek_) {
+ DVLOG(1) << __FUNCTION__ << "processing pending seek";
+ DCHECK(doing_browser_seek_);
+ pending_seek_ = false;
+ SeekTo(pending_seek_time_);
+ return;
+ }
+
+ // It is possible that a browser seek to I-frame had to seek to a buffered
+ // I-frame later than the requested one due to data removal or GC. Update
+ // player clock to the actual seek target.
+ if (doing_browser_seek_) {
+ DCHECK(actual_browser_seek_time != kNoTimestamp());
+ // A browser seek must not jump into the past. Ideally, it seeks to the
+ // requested time, but it might jump into the future.
+ DCHECK(actual_browser_seek_time >= GetCurrentTime());
+ DVLOG(1) << __FUNCTION__ << " : setting clock to actual browser seek time: "
+ << actual_browser_seek_time.InSecondsF();
+ clock_.SetTime(actual_browser_seek_time, actual_browser_seek_time);
+ if (audio_timestamp_helper_)
+ audio_timestamp_helper_->SetBaseTimestamp(actual_browser_seek_time);
+ }
+
base::TimeDelta current_time = GetCurrentTime();
- manager()->OnSeekComplete(player_id(), current_time);
// TODO(qinmin): Simplify the logic by using |start_presentation_timestamp_|
// to preroll media decoder jobs. Currently |start_presentation_timestamp_|
// is calculated from decoder output, while preroll relies on the access
// unit's timestamp. There are some differences between the two.
+ preroll_timestamp_ = current_time;
if (audio_decoder_job_)
- audio_decoder_job_->set_preroll_timestamp(current_time);
+ audio_decoder_job_->BeginPrerolling(preroll_timestamp_);
if (video_decoder_job_)
- video_decoder_job_->set_preroll_timestamp(current_time);
+ video_decoder_job_->BeginPrerolling(preroll_timestamp_);
+
+ if (!doing_browser_seek_)
+ manager()->OnSeekComplete(player_id(), current_time);
+
ProcessPendingEvents();
}
@@ -436,7 +478,7 @@ void MediaSourcePlayer::ProcessPendingEvents() {
if (IsEventPending(SEEK_EVENT_PENDING)) {
DVLOG(1) << __FUNCTION__ << " : Handling SEEK_EVENT";
ClearDecodingData();
- demuxer_->RequestDemuxerSeek(GetCurrentTime());
+ demuxer_->RequestDemuxerSeek(GetCurrentTime(), doing_browser_seek_);
return;
}
@@ -450,9 +492,15 @@ void MediaSourcePlayer::ProcessPendingEvents() {
if (IsEventPending(SURFACE_CHANGE_EVENT_PENDING)) {
DVLOG(1) << __FUNCTION__ << " : Handling SURFACE_CHANGE_EVENT.";
+ // Setting a new surface will require a new MediaCodec to be created.
video_decoder_job_.reset();
ConfigureVideoDecoderJob();
- ClearPendingEvent(SURFACE_CHANGE_EVENT_PENDING);
+
+ // Return early if we can't successfully configure a new video decoder job
+ // yet, except continue processing other pending events if |surface_| is
+ // empty.
+ if (!video_decoder_job_ && !surface_.IsEmpty())
+ return;
}
if (IsEventPending(PREFETCH_REQUEST_EVENT_PENDING)) {
@@ -485,7 +533,6 @@ void MediaSourcePlayer::MediaDecoderCallback(
bool is_audio, MediaCodecStatus status,
const base::TimeDelta& presentation_timestamp, size_t audio_output_bytes) {
DVLOG(1) << __FUNCTION__ << ": " << is_audio << ", " << status;
- DCHECK(!is_waiting_for_key_);
// TODO(xhwang): Drop IntToString() when http://crbug.com/303899 is fixed.
if (is_audio) {
@@ -599,6 +646,11 @@ void MediaSourcePlayer::DecodeMoreVideo() {
// Failed to start the next decode.
// Wait for demuxer ready message.
reconfig_video_decoder_ = true;
+
+ // After this detection of video config change, next video data received
+ // will begin with I-frame.
+ next_video_data_is_iframe_ = true;
+
SetPendingEvent(CONFIG_CHANGE_EVENT_PENDING);
ProcessPendingEvents();
}
@@ -651,6 +703,8 @@ void MediaSourcePlayer::ConfigureAudioDecoderJob() {
DCHECK(!audio_decoder_job_ || !audio_decoder_job_->is_decoding());
+ DVLOG(1) << __FUNCTION__ << " : creating new audio decoder job";
+
audio_decoder_job_.reset(AudioDecoderJob::Create(
audio_codec_, sampling_rate_, num_channels_, &audio_extra_data_[0],
audio_extra_data_.size(), media_crypto.obj(),
@@ -659,6 +713,7 @@ void MediaSourcePlayer::ConfigureAudioDecoderJob() {
if (audio_decoder_job_) {
SetVolumeInternal();
+ audio_decoder_job_->BeginPrerolling(preroll_timestamp_);
reconfig_audio_decoder_ = false;
}
}
@@ -666,19 +721,39 @@ void MediaSourcePlayer::ConfigureAudioDecoderJob() {
void MediaSourcePlayer::ConfigureVideoDecoderJob() {
if (!HasVideo() || surface_.IsEmpty()) {
video_decoder_job_.reset();
+ if (IsEventPending(SURFACE_CHANGE_EVENT_PENDING))
+ ClearPendingEvent(SURFACE_CHANGE_EVENT_PENDING);
return;
}
- // Create video decoder job only if config changes.
+ // Create video decoder job only if config changes or we don't have a job.
if (video_decoder_job_ && !reconfig_video_decoder_)
return;
+ if (reconfig_video_decoder_) {
+ // No hack browser seek should be required. I-Frame must be next.
+ DCHECK(next_video_data_is_iframe_) << "Received video data between "
+ << "detecting video config change and reconfiguring video decoder";
+ }
+
+ // If uncertain that video I-frame data is next and there is no seek already
+ // in process, request browser demuxer seek so the new decoder will decode
+ // an I-frame first. Otherwise, the new MediaCodec might crash. See b/8950387.
+ // TODO(wolenetz): Instead of doing hack browser seek, replay cached data
+ // since last keyframe. See http://crbug.com/304234.
+ if (!next_video_data_is_iframe_ && !IsEventPending(SEEK_EVENT_PENDING)) {
+ BrowserSeekToCurrentTime();
+ return;
+ }
+
base::android::ScopedJavaLocalRef<jobject> media_crypto = GetMediaCrypto();
if (is_video_encrypted_ && media_crypto.is_null())
return;
DCHECK(!video_decoder_job_ || !video_decoder_job_->is_decoding());
+ DVLOG(1) << __FUNCTION__ << " : creating new video decoder job";
+
// Release the old VideoDecoderJob first so the surface can get released.
// Android does not allow 2 MediaCodec instances use the same surface.
video_decoder_job_.reset();
@@ -693,8 +768,13 @@ void MediaSourcePlayer::ConfigureVideoDecoderJob() {
base::Bind(&DemuxerAndroid::RequestDemuxerData,
base::Unretained(demuxer_.get()),
DemuxerStream::VIDEO)));
- if (video_decoder_job_)
+ if (video_decoder_job_) {
+ video_decoder_job_->BeginPrerolling(preroll_timestamp_);
reconfig_video_decoder_ = false;
+ }
+
+ if (IsEventPending(SURFACE_CHANGE_EVENT_PENDING))
+ ClearPendingEvent(SURFACE_CHANGE_EVENT_PENDING);
// Inform the fullscreen view the player is ready.
// TODO(qinmin): refactor MediaPlayerBridge so that we have a better way
diff --git a/media/base/android/media_source_player.h b/media/base/android/media_source_player.h
index 61215b4390..3c16689f7d 100644
--- a/media/base/android/media_source_player.h
+++ b/media/base/android/media_source_player.h
@@ -70,7 +70,8 @@ class MEDIA_EXPORT MediaSourcePlayer : public MediaPlayerAndroid,
// DemuxerAndroidClient implementation.
virtual void OnDemuxerConfigsAvailable(const DemuxerConfigs& params) OVERRIDE;
virtual void OnDemuxerDataAvailable(const DemuxerData& params) OVERRIDE;
- virtual void OnDemuxerSeekDone() OVERRIDE;
+ virtual void OnDemuxerSeekDone(
+ const base::TimeDelta& actual_browser_seek_time) OVERRIDE;
virtual void OnDemuxerDurationChanged(base::TimeDelta duration) OVERRIDE;
private:
@@ -127,8 +128,18 @@ class MEDIA_EXPORT MediaSourcePlayer : public MediaPlayerAndroid,
void StartStarvationCallback(const base::TimeDelta& presentation_timestamp);
// Schedules a seek event in |pending_events_| and calls StopDecode() on all
- // the MediaDecoderJobs.
- void ScheduleSeekEventAndStopDecoding();
+ // the MediaDecoderJobs. Sets clock to |seek_time|, and resets
+ // |pending_seek_|. There must not already be a seek event in
+ // |pending_events_|.
+ void ScheduleSeekEventAndStopDecoding(const base::TimeDelta& seek_time);
+
+ // Schedules a browser seek event. We must not currently be processing any
+ // seek. Note that there is possibility that browser seek of renderer demuxer
+ // may unexpectedly stall due to lack of buffered data at or after the browser
+ // seek time.
+ // TODO(wolenetz): Instead of doing hack browser seek, replay cached data
+ // since last keyframe. See http://crbug.com/304234.
+ void BrowserSeekToCurrentTime();
// Helper function to set the volume.
void SetVolumeInternal();
@@ -197,8 +208,25 @@ class MEDIA_EXPORT MediaSourcePlayer : public MediaPlayerAndroid,
// The surface object currently owned by the player.
gfx::ScopedJavaSurface surface_;
- // Tracks whether or not the player has previously ever set |surface_|.
- bool surface_ever_set_;
+ // Track whether or not the player has received any video data since the most
+ // recent of player construction, end of last seek, or receiving and
+ // detecting a |kConfigChanged| access unit from the demuxer.
+ // If no such video data has been received, the next video data begins with
+ // an I-frame. Otherwise, we have no such guarantee.
+ bool next_video_data_is_iframe_;
+
+ // Flag that is true if doing a hack browser seek or false if doing a
+ // regular seek. Only valid when |SEEK_EVENT_PENDING| is pending.
+ // TODO(wolenetz): Instead of doing hack browser seek, replay cached data
+ // since last keyframe. See http://crbug.com/304234.
+ bool doing_browser_seek_;
+
+ // If already doing a browser seek when a regular seek request arrives,
+ // these fields remember the regular seek so OnDemuxerSeekDone() can trigger
+ // it when the browser seek is done. These are only valid when
+ // |SEEK_EVENT_PENDING| is pending.
+ bool pending_seek_;
+ base::TimeDelta pending_seek_time_;
// Decoder jobs.
scoped_ptr<AudioDecoderJob, MediaDecoderJob::Deleter> audio_decoder_job_;
@@ -207,6 +235,10 @@ class MEDIA_EXPORT MediaSourcePlayer : public MediaPlayerAndroid,
bool reconfig_audio_decoder_;
bool reconfig_video_decoder_;
+ // Track the most recent preroll target. Decoder re-creation needs this to
+ // resume any in-progress preroll.
+ base::TimeDelta preroll_timestamp_;
+
// A cancelable task that is posted when the audio decoder starts requesting
// new data. This callback runs if no data arrives before the timeout period
// elapses.
diff --git a/media/base/android/media_source_player_unittest.cc b/media/base/android/media_source_player_unittest.cc
index 8826848dd9..e329ac6eee 100644
--- a/media/base/android/media_source_player_unittest.cc
+++ b/media/base/android/media_source_player_unittest.cc
@@ -92,23 +92,31 @@ class MockDemuxerAndroid : public DemuxerAndroid {
explicit MockDemuxerAndroid(base::MessageLoop* message_loop)
: message_loop_(message_loop),
num_data_requests_(0),
- num_seek_requests_(0) {}
+ num_seek_requests_(0),
+ num_browser_seek_requests_(0),
+ num_config_requests_(0) {}
virtual ~MockDemuxerAndroid() {}
virtual void Initialize(DemuxerAndroidClient* client) OVERRIDE {}
- virtual void RequestDemuxerConfigs() OVERRIDE {}
+ virtual void RequestDemuxerConfigs() OVERRIDE {
+ num_config_requests_++;
+ }
virtual void RequestDemuxerData(DemuxerStream::Type type) OVERRIDE {
num_data_requests_++;
if (message_loop_->is_running())
message_loop_->Quit();
}
- virtual void RequestDemuxerSeek(
- const base::TimeDelta& time_to_seek) OVERRIDE {
+ virtual void RequestDemuxerSeek(const base::TimeDelta& time_to_seek,
+ bool is_browser_seek) OVERRIDE {
num_seek_requests_++;
+ if (is_browser_seek)
+ num_browser_seek_requests_++;
}
int num_data_requests() const { return num_data_requests_; }
int num_seek_requests() const { return num_seek_requests_; }
+ int num_browser_seek_requests() const { return num_browser_seek_requests_; }
+ int num_config_requests() const { return num_config_requests_; }
private:
base::MessageLoop* message_loop_;
@@ -116,9 +124,15 @@ class MockDemuxerAndroid : public DemuxerAndroid {
// The number of encoded data requests this object has seen.
int num_data_requests_;
- // The number of seek requests this object has seen.
+ // The number of regular and browser seek requests this object has seen.
int num_seek_requests_;
+ // The number of browser seek requests this object has seen.
+ int num_browser_seek_requests_;
+
+ // The number of demuxer config requests this object has seen.
+ int num_config_requests_;
+
DISALLOW_COPY_AND_ASSIGN(MockDemuxerAndroid);
};
@@ -127,7 +141,8 @@ class MediaSourcePlayerTest : public testing::Test {
MediaSourcePlayerTest()
: manager_(&message_loop_),
demuxer_(new MockDemuxerAndroid(&message_loop_)),
- player_(0, &manager_, scoped_ptr<DemuxerAndroid>(demuxer_)) {}
+ player_(0, &manager_, scoped_ptr<DemuxerAndroid>(demuxer_)),
+ surface_texture_a_is_next_(true) {}
virtual ~MediaSourcePlayerTest() {}
protected:
@@ -141,8 +156,18 @@ class MediaSourcePlayerTest : public testing::Test {
player_.video_decoder_job_.get());
}
- // Starts an audio decoder job.
- void StartAudioDecoderJob() {
+ // Get the per-job prerolling status from the MediaSourcePlayer's job matching
+ // |is_audio|. Caller must guard against NPE if the player's job is NULL.
+ bool IsPrerolling(bool is_audio) {
+ return GetMediaDecoderJob(is_audio)->prerolling();
+ }
+
+ // Get the preroll timestamp from the MediaSourcePlayer.
+ base::TimeDelta GetPrerollTimestamp() {
+ return player_.preroll_timestamp_;
+ }
+
+ DemuxerConfigs CreateAudioDemuxerConfigs() {
DemuxerConfigs configs;
configs.audio_codec = kCodecVorbis;
configs.audio_channels = 2;
@@ -153,38 +178,67 @@ class MediaSourcePlayerTest : public testing::Test {
configs.audio_extra_data = std::vector<uint8>(
buffer->data(),
buffer->data() + buffer->data_size());
- Start(configs);
+ return configs;
}
- void StartVideoDecoderJob() {
+ // Starts an audio decoder job.
+ void StartAudioDecoderJob() {
+ Start(CreateAudioDemuxerConfigs());
+ }
+
+ DemuxerConfigs CreateVideoDemuxerConfigs() {
DemuxerConfigs configs;
configs.video_codec = kCodecVP8;
configs.video_size = gfx::Size(320, 240);
configs.is_video_encrypted = false;
configs.duration_ms = kDefaultDurationInMs;
- Start(configs);
+ return configs;
+ }
+
+ DemuxerConfigs CreateAudioVideoDemuxerConfigs() {
+ DemuxerConfigs configs = CreateAudioDemuxerConfigs();
+ configs.video_codec = kCodecVP8;
+ configs.video_size = gfx::Size(320, 240);
+ configs.is_video_encrypted = false;
+ return configs;
+ }
+
+ void StartVideoDecoderJob() {
+ Start(CreateVideoDemuxerConfigs());
}
// Starts decoding the data.
void Start(const DemuxerConfigs& configs) {
player_.OnDemuxerConfigsAvailable(configs);
player_.Start();
+ EXPECT_TRUE(player_.IsPlaying());
+ }
+
+ AccessUnit CreateAccessUnitWithData(bool is_audio, int audio_packet_id) {
+ AccessUnit unit;
+
+ unit.status = DemuxerStream::kOk;
+ scoped_refptr<DecoderBuffer> buffer = ReadTestDataFile(
+ is_audio ? base::StringPrintf("vorbis-packet-%d", audio_packet_id)
+ : "vp8-I-frame-320x240");
+ unit.data = std::vector<uint8>(
+ buffer->data(), buffer->data() + buffer->data_size());
+
+ if (is_audio) {
+ // Vorbis needs 4 extra bytes padding on Android to decode properly. Check
+ // NuMediaExtractor.cpp in Android source code.
+ uint8 padding[4] = { 0xff , 0xff , 0xff , 0xff };
+ unit.data.insert(unit.data.end(), padding, padding + 4);
+ }
+
+ return unit;
}
DemuxerData CreateReadFromDemuxerAckForAudio(int packet_id) {
DemuxerData data;
data.type = DemuxerStream::AUDIO;
data.access_units.resize(1);
- data.access_units[0].status = DemuxerStream::kOk;
- scoped_refptr<DecoderBuffer> buffer = ReadTestDataFile(
- base::StringPrintf("vorbis-packet-%d", packet_id));
- data.access_units[0].data = std::vector<uint8>(
- buffer->data(), buffer->data() + buffer->data_size());
- // Vorbis needs 4 extra bytes padding on Android to decode properly. Check
- // NuMediaExtractor.cpp in Android source code.
- uint8 padding[4] = { 0xff , 0xff , 0xff , 0xff };
- data.access_units[0].data.insert(
- data.access_units[0].data.end(), padding, padding + 4);
+ data.access_units[0] = CreateAccessUnitWithData(true, packet_id);
return data;
}
@@ -192,23 +246,216 @@ class MediaSourcePlayerTest : public testing::Test {
DemuxerData data;
data.type = DemuxerStream::VIDEO;
data.access_units.resize(1);
- data.access_units[0].status = DemuxerStream::kOk;
- scoped_refptr<DecoderBuffer> buffer =
- ReadTestDataFile("vp8-I-frame-320x240");
- data.access_units[0].data = std::vector<uint8>(
- buffer->data(), buffer->data() + buffer->data_size());
+ data.access_units[0] = CreateAccessUnitWithData(false, 0);
return data;
}
DemuxerData CreateEOSAck(bool is_audio) {
- DemuxerData data;
- data.type = is_audio ? DemuxerStream::AUDIO : DemuxerStream::VIDEO;
- data.access_units.resize(1);
- data.access_units[0].status = DemuxerStream::kOk;
- data.access_units[0].end_of_stream = true;
- return data;
+ DemuxerData data;
+ data.type = is_audio ? DemuxerStream::AUDIO : DemuxerStream::VIDEO;
+ data.access_units.resize(1);
+ data.access_units[0].status = DemuxerStream::kOk;
+ data.access_units[0].end_of_stream = true;
+ return data;
+ }
+
+ DemuxerData CreateAbortedAck(bool is_audio) {
+ DemuxerData data;
+ data.type = is_audio ? DemuxerStream::AUDIO : DemuxerStream::VIDEO;
+ data.access_units.resize(1);
+ data.access_units[0].status = DemuxerStream::kAborted;
+ return data;
+ }
+
+ // Seek, including simulated receipt of |kAborted| read between SeekTo()
+ // and OnDemuxerSeekDone(). Use this helper method only when the player
+ // already has created the decoder job.
+ void SeekPlayer(bool is_audio, const base::TimeDelta& seek_time) {
+ EXPECT_TRUE(GetMediaDecoderJob(is_audio));
+
+ int original_num_seeks = demuxer_->num_seek_requests();
+ int original_num_data_requests = demuxer_->num_data_requests();
+
+ // Initiate a seek. Skip the round-trip of requesting seek from renderer.
+ // Instead behave as if the renderer has asked us to seek.
+ player_.SeekTo(seek_time);
+
+ // Verify that the seek does not occur until previously outstanding data
+ // request is satisfied.
+ EXPECT_EQ(original_num_seeks, demuxer_->num_seek_requests());
+
+ // Simulate seeking causes the demuxer to abort the outstanding read caused
+ // by the seek.
+ player_.OnDemuxerDataAvailable(CreateAbortedAck(is_audio));
+
+ // Verify that the seek is requested now that the outstanding read is
+ // completed by aborted access unit.
+ EXPECT_EQ(original_num_seeks + 1, demuxer_->num_seek_requests());
+
+ // Send back the seek done notification. This should trigger the player to
+ // call OnReadFromDemuxer() again.
+ EXPECT_EQ(original_num_data_requests, demuxer_->num_data_requests());
+ player_.OnDemuxerSeekDone(kNoTimestamp());
+ EXPECT_EQ(original_num_data_requests + 1, demuxer_->num_data_requests());
+
+ // No other seek should have been requested.
+ EXPECT_EQ(original_num_seeks + 1, demuxer_->num_seek_requests());
+ }
+
+ DemuxerData CreateReadFromDemuxerAckWithConfigChanged(bool is_audio,
+ int config_unit_index) {
+ DemuxerData data;
+ data.type = is_audio ? DemuxerStream::AUDIO : DemuxerStream::VIDEO;
+ data.access_units.resize(config_unit_index + 1);
+
+ for (int i = 0; i < config_unit_index; ++i)
+ data.access_units[i] = CreateAccessUnitWithData(is_audio, i);
+
+ data.access_units[config_unit_index].status = DemuxerStream::kConfigChanged;
+ return data;
+ }
+
+ // Valid only for video-only player tests. If |trigger_with_release_start| is
+ // true, triggers the browser seek with a Release() + video data received +
+ // Start() with a new surface. If false, triggers the browser seek by
+ // setting a new video surface after beginning decode of received video data.
+ // Such data receipt causes possibility that an I-frame is not next, and
+ // browser seek results once decode completes and surface change processing
+ // begins.
+ void BrowserSeekPlayer(bool trigger_with_release_start) {
+ int expected_num_data_requests = demuxer_->num_data_requests();
+ int expected_num_seek_requests = demuxer_->num_seek_requests();
+ int expected_num_browser_seek_requests =
+ demuxer_->num_browser_seek_requests();
+
+ EXPECT_FALSE(GetMediaDecoderJob(false));
+ CreateNextTextureAndSetVideoSurface();
+ StartVideoDecoderJob();
+ EXPECT_TRUE(GetMediaDecoderJob(false));
+ expected_num_data_requests++;
+ EXPECT_EQ(expected_num_data_requests, demuxer_->num_data_requests());
+
+ if (trigger_with_release_start) {
+ player_.Release();
+
+ // Simulate demuxer's response to the video data request.
+ player_.OnDemuxerDataAvailable(CreateReadFromDemuxerAckForVideo());
+ EXPECT_FALSE(GetMediaDecoderJob(false));
+ EXPECT_FALSE(player_.IsPlaying());
+ EXPECT_EQ(expected_num_seek_requests, demuxer_->num_seek_requests());
+ EXPECT_EQ(expected_num_data_requests, demuxer_->num_data_requests());
+
+ CreateNextTextureAndSetVideoSurface();
+ player_.Start();
+ } else {
+ // Simulate demuxer's response to the video data request.
+ player_.OnDemuxerDataAvailable(CreateReadFromDemuxerAckForVideo());
+
+ // While the decoder is decoding, trigger a browser seek by changing
+ // surface. Demuxer does not know of browser seek in advance, so no
+ // |kAborted| data is required (though |kAborted| can certainly occur for
+ // any pending read in reality due to renderer preparing for a regular
+ // seek).
+ CreateNextTextureAndSetVideoSurface();
+
+ // Browser seek should not begin until decoding has completed.
+ EXPECT_TRUE(GetMediaDecoderJob(false));
+ EXPECT_EQ(expected_num_seek_requests, demuxer_->num_seek_requests());
+ EXPECT_EQ(expected_num_browser_seek_requests,
+ demuxer_->num_browser_seek_requests());
+
+ // Wait for the decoder job to finish decoding and be reset pending the
+ // browser seek.
+ while (GetMediaDecoderJob(false) &&
+ GetMediaDecoderJob(false)->is_decoding()) {
+ message_loop_.RunUntilIdle();
+ }
}
+ EXPECT_FALSE(GetMediaDecoderJob(false));
+ EXPECT_TRUE(player_.IsPlaying());
+
+ // Only one browser seek should have been initiated, and no further data
+ // should have been requested.
+ expected_num_seek_requests++;
+ expected_num_browser_seek_requests++;
+ EXPECT_EQ(expected_num_seek_requests, demuxer_->num_seek_requests());
+ EXPECT_EQ(expected_num_browser_seek_requests,
+ demuxer_->num_browser_seek_requests());
+ EXPECT_EQ(expected_num_data_requests, demuxer_->num_seek_requests());
+ }
+
+ // Creates a new decoder job and feeds it data ending with a |kConfigChanged|
+ // access unit. If |config_unit_in_prefetch| is true, sends feeds the config
+ // change AU in response to the job's first read request (prefetch). If
+ // false, regular data is fed and decoded prior to feeding the config change
+ // AU in response to the second data request (after prefetch completed).
+ // |config_unit_index| controls which access unit is |kConfigChanged|.
+ void StartConfigChange(bool is_audio,
+ bool config_unit_in_prefetch,
+ int config_unit_index) {
+ int expected_num_data_requests = demuxer_->num_data_requests();
+ int expected_num_config_requests = demuxer_->num_config_requests();
+
+ EXPECT_FALSE(GetMediaDecoderJob(is_audio));
+ if (is_audio) {
+ StartAudioDecoderJob();
+ } else {
+ CreateNextTextureAndSetVideoSurface();
+ StartVideoDecoderJob();
+ }
+ EXPECT_TRUE(GetMediaDecoderJob(is_audio));
+ expected_num_data_requests++;
+ EXPECT_EQ(expected_num_data_requests, demuxer_->num_data_requests());
+ EXPECT_EQ(expected_num_config_requests, demuxer_->num_config_requests());
+
+ // Feed and decode a standalone access unit so the player exits prefetch.
+ if (!config_unit_in_prefetch) {
+ if (is_audio)
+ player_.OnDemuxerDataAvailable(CreateReadFromDemuxerAckForAudio(0));
+ else
+ player_.OnDemuxerDataAvailable(CreateReadFromDemuxerAckForVideo());
+
+ message_loop_.Run();
+
+ // We should have completed the prefetch phase at this point.
+ EXPECT_TRUE(GetMediaDecoderJob(is_audio));
+ expected_num_data_requests++;
+ EXPECT_EQ(expected_num_data_requests, demuxer_->num_data_requests());
+ EXPECT_EQ(expected_num_config_requests, demuxer_->num_config_requests());
+ }
+
+ // Feed and decode access units with data for any units prior to
+ // |config_unit_index|, and a |kConfigChanged| unit at that index.
+ // Player should prepare to reconfigure the decoder job, and should request
+ // new demuxer configs.
+ player_.OnDemuxerDataAvailable(
+ CreateReadFromDemuxerAckWithConfigChanged(is_audio, config_unit_index));
+ while (GetMediaDecoderJob(is_audio)->is_decoding())
+ message_loop_.RunUntilIdle();
+
+ expected_num_config_requests++;
+ EXPECT_TRUE(player_.IsPlaying());
+ EXPECT_TRUE(GetMediaDecoderJob(is_audio));
+ EXPECT_EQ(expected_num_data_requests, demuxer_->num_data_requests());
+ EXPECT_EQ(expected_num_config_requests, demuxer_->num_config_requests());
+ }
+
+ void CreateNextTextureAndSetVideoSurface() {
+ gfx::SurfaceTexture* surface_texture;
+ if (surface_texture_a_is_next_) {
+ surface_texture_a_ = new gfx::SurfaceTexture(next_texture_id_++);
+ surface_texture = surface_texture_a_.get();
+ } else {
+ surface_texture_b_ = new gfx::SurfaceTexture(next_texture_id_++);
+ surface_texture = surface_texture_b_.get();
+ }
+
+ surface_texture_a_is_next_ = !surface_texture_a_is_next_;
+ gfx::ScopedJavaSurface surface = gfx::ScopedJavaSurface(surface_texture);
+ player_.SetVideoSurface(surface.Pass());
+ }
+
base::TimeTicks StartTimeTicks() {
return player_.start_time_ticks_;
}
@@ -221,23 +468,23 @@ class MediaSourcePlayerTest : public testing::Test {
scheme_uuid, security_level, container, codecs);
}
- void CreateAndSetVideoSurface() {
- surface_texture_ = new gfx::SurfaceTexture(0);
- gfx::ScopedJavaSurface surface = gfx::ScopedJavaSurface(
- surface_texture_.get());
- player_.SetVideoSurface(surface.Pass());
- }
-
protected:
base::MessageLoop message_loop_;
MockMediaPlayerManager manager_;
MockDemuxerAndroid* demuxer_; // Owned by |player_|.
MediaSourcePlayer player_;
+
// We need to keep the surface texture while the decoder is actively decoding.
// Otherwise, it may trigger unexpected crashes on some devices. To switch
- // surfaces, tests need to create their own surface texture without releasing
- // this one if they previously called CreateAndSetVideoSurface().
- scoped_refptr<gfx::SurfaceTexture> surface_texture_;
+ // surfaces, tests need to create a new surface texture without releasing
+ // their previous one. In CreateNextTextureAndSetVideoSurface(), we toggle
+ // between two surface textures, only replacing the N-2 texture. Assumption is
+ // that no more than N-1 texture is in use by decoder when
+ // CreateNextTextureAndSetVideoSurface() is called.
+ scoped_refptr<gfx::SurfaceTexture> surface_texture_a_;
+ scoped_refptr<gfx::SurfaceTexture> surface_texture_b_;
+ bool surface_texture_a_is_next_;
+ int next_texture_id_;
DISALLOW_COPY_AND_ASSIGN(MediaSourcePlayerTest);
};
@@ -249,24 +496,23 @@ TEST_F(MediaSourcePlayerTest, StartAudioDecoderWithValidConfig) {
StartAudioDecoderJob();
EXPECT_TRUE(GetMediaDecoderJob(true));
EXPECT_EQ(1, demuxer_->num_data_requests());
+ EXPECT_EQ(0, demuxer_->num_seek_requests());
}
TEST_F(MediaSourcePlayerTest, StartAudioDecoderWithInvalidConfig) {
SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE();
// Test audio decoder job will not be created when failed to start the codec.
- DemuxerConfigs configs;
- configs.audio_codec = kCodecVorbis;
- configs.audio_channels = 2;
- configs.audio_sampling_rate = 44100;
- configs.is_audio_encrypted = false;
- configs.duration_ms = kDefaultDurationInMs;
+ DemuxerConfigs configs = CreateAudioDemuxerConfigs();
+ // Replace with invalid |audio_extra_data|
+ configs.audio_extra_data.clear();
uint8 invalid_codec_data[] = { 0x00, 0xff, 0xff, 0xff, 0xff };
configs.audio_extra_data.insert(configs.audio_extra_data.begin(),
invalid_codec_data, invalid_codec_data + 4);
Start(configs);
- EXPECT_EQ(NULL, GetMediaDecoderJob(true));
+ EXPECT_FALSE(GetMediaDecoderJob(true));
EXPECT_EQ(0, demuxer_->num_data_requests());
+ EXPECT_EQ(0, demuxer_->num_seek_requests());
}
TEST_F(MediaSourcePlayerTest, StartVideoCodecWithValidSurface) {
@@ -275,17 +521,27 @@ TEST_F(MediaSourcePlayerTest, StartVideoCodecWithValidSurface) {
// Test video decoder job will be created when surface is valid.
StartVideoDecoderJob();
// Video decoder job will not be created until surface is available.
- EXPECT_EQ(NULL, GetMediaDecoderJob(false));
+ EXPECT_FALSE(GetMediaDecoderJob(false));
EXPECT_EQ(0, demuxer_->num_data_requests());
- CreateAndSetVideoSurface();
+ // Set both an initial and a later video surface without receiving any
+ // demuxed data yet.
+ CreateNextTextureAndSetVideoSurface();
+ MediaDecoderJob* first_job = GetMediaDecoderJob(false);
+ EXPECT_TRUE(first_job);
+ CreateNextTextureAndSetVideoSurface();
- // Player should not seek the demuxer on setting initial surface.
+ // Setting another surface will not create a new job until any pending
+ // read is satisfied (and job is no longer decoding).
+ EXPECT_EQ(first_job, GetMediaDecoderJob(false));
+
+ // No seeks, even on setting surface, should have occurred. (Browser seeks can
+ // occur on setting surface, but only after previously receiving video data.)
EXPECT_EQ(0, demuxer_->num_seek_requests());
- // The decoder job should be ready now.
- EXPECT_TRUE(GetMediaDecoderJob(false));
- EXPECT_EQ(1, demuxer_->num_data_requests());
+ // Note, the decoder job for the second surface set, above, will be created
+ // only after the pending read is satisfied and decoded, and the resulting
+ // browser seek is done. See BrowserSeek_* tests for this coverage.
}
TEST_F(MediaSourcePlayerTest, StartVideoCodecWithInvalidSurface) {
@@ -297,7 +553,7 @@ TEST_F(MediaSourcePlayerTest, StartVideoCodecWithInvalidSurface) {
gfx::ScopedJavaSurface surface(surface_texture.get());
StartVideoDecoderJob();
// Video decoder job will not be created until surface is available.
- EXPECT_EQ(NULL, GetMediaDecoderJob(false));
+ EXPECT_FALSE(GetMediaDecoderJob(false));
EXPECT_EQ(0, demuxer_->num_data_requests());
// Release the surface texture.
@@ -307,7 +563,7 @@ TEST_F(MediaSourcePlayerTest, StartVideoCodecWithInvalidSurface) {
// Player should not seek the demuxer on setting initial surface.
EXPECT_EQ(0, demuxer_->num_seek_requests());
- EXPECT_EQ(NULL, GetMediaDecoderJob(false));
+ EXPECT_FALSE(GetMediaDecoderJob(false));
EXPECT_EQ(0, demuxer_->num_data_requests());
}
@@ -318,34 +574,8 @@ TEST_F(MediaSourcePlayerTest, ReadFromDemuxerAfterSeek) {
StartAudioDecoderJob();
EXPECT_TRUE(GetMediaDecoderJob(true));
EXPECT_EQ(1, demuxer_->num_data_requests());
-
- // Initiate a seek. Skip the round-trip of requesting seek from renderer.
- // Instead behave as if the renderer has asked us to seek.
- player_.SeekTo(base::TimeDelta());
-
- // Verify that the seek does not occur until the initial prefetch
- // completes.
- EXPECT_EQ(0, demuxer_->num_seek_requests());
-
- // Simulate aborted read caused by the seek. This aborts the initial
- // prefetch.
- DemuxerData data;
- data.type = DemuxerStream::AUDIO;
- data.access_units.resize(1);
- data.access_units[0].status = DemuxerStream::kAborted;
- player_.OnDemuxerDataAvailable(data);
-
- // Verify that the seek is requested now that the initial prefetch
- // has completed.
- EXPECT_EQ(1, demuxer_->num_seek_requests());
-
- // Sending back the seek done notification. This should trigger the player to
- // call OnReadFromDemuxer() again.
- player_.OnDemuxerSeekDone();
+ SeekPlayer(true, base::TimeDelta());
EXPECT_EQ(2, demuxer_->num_data_requests());
-
- // Reconfirm exactly 1 seek request has been made of demuxer.
- EXPECT_EQ(1, demuxer_->num_seek_requests());
}
TEST_F(MediaSourcePlayerTest, SetSurfaceWhileSeeking) {
@@ -357,35 +587,39 @@ TEST_F(MediaSourcePlayerTest, SetSurfaceWhileSeeking) {
// Player is still waiting for SetVideoSurface(), so no request is sent.
EXPECT_EQ(0, demuxer_->num_data_requests());
- // Initiate a seek. Skip the round-trip of requesting seek from renderer.
+ // Initiate a seek. Skip requesting element seek of renderer.
// Instead behave as if the renderer has asked us to seek.
EXPECT_EQ(0, demuxer_->num_seek_requests());
player_.SeekTo(base::TimeDelta());
EXPECT_EQ(1, demuxer_->num_seek_requests());
- CreateAndSetVideoSurface();
- EXPECT_TRUE(NULL == GetMediaDecoderJob(false));
+ CreateNextTextureAndSetVideoSurface();
+ EXPECT_FALSE(GetMediaDecoderJob(false));
EXPECT_EQ(1, demuxer_->num_seek_requests());
// Reconfirm player has not yet requested data.
EXPECT_EQ(0, demuxer_->num_data_requests());
// Send the seek done notification. The player should start requesting data.
- player_.OnDemuxerSeekDone();
+ player_.OnDemuxerSeekDone(kNoTimestamp());
EXPECT_TRUE(GetMediaDecoderJob(false));
EXPECT_EQ(1, demuxer_->num_data_requests());
- // Reconfirm exactly 1 seek request has been made of demuxer.
+ // Reconfirm exactly 1 seek request has been made of demuxer, and that it
+ // was not a browser seek request.
EXPECT_EQ(1, demuxer_->num_seek_requests());
+ EXPECT_EQ(0, demuxer_->num_browser_seek_requests());
}
TEST_F(MediaSourcePlayerTest, ChangeMultipleSurfaceWhileDecoding) {
SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE();
// Test MediaSourcePlayer can switch multiple surfaces during decoding.
- CreateAndSetVideoSurface();
+ CreateNextTextureAndSetVideoSurface();
StartVideoDecoderJob();
EXPECT_EQ(1, demuxer_->num_data_requests());
+ EXPECT_EQ(0, demuxer_->num_seek_requests());
+ EXPECT_TRUE(GetMediaDecoderJob(false));
// Send the first input chunk.
player_.OnDemuxerDataAvailable(CreateReadFromDemuxerAckForVideo());
@@ -394,47 +628,49 @@ TEST_F(MediaSourcePlayerTest, ChangeMultipleSurfaceWhileDecoding) {
// surface first.
gfx::ScopedJavaSurface empty_surface;
player_.SetVideoSurface(empty_surface.Pass());
- // Pass a new non-empty surface, don't call CreateAndSetVideoSurface() as
- // it will release the old surface_texture_ and might cause unexpected
- // behaviors on some devices.
- scoped_refptr<gfx::SurfaceTexture> surface_texture(
- new gfx::SurfaceTexture(1));
- gfx::ScopedJavaSurface surface(surface_texture.get());
- player_.SetVideoSurface(surface.Pass());
+ // Next, pass a new non-empty surface.
+ CreateNextTextureAndSetVideoSurface();
- // Wait for the decoder job to finish decoding.
- while(GetMediaDecoderJob(false)->is_decoding())
+ // Wait for the decoder job to finish decoding and be reset pending a browser
+ // seek.
+ while (GetMediaDecoderJob(false) && GetMediaDecoderJob(false)->is_decoding())
message_loop_.RunUntilIdle();
- // A seek should be initiated to request Iframe.
- EXPECT_EQ(1, demuxer_->num_seek_requests());
+ EXPECT_FALSE(GetMediaDecoderJob(false));
+
+ // Only one browser seek should have been initiated. No further data request
+ // should have been processed on |message_loop_| before surface change event
+ // became pending, above.
+ EXPECT_EQ(1, demuxer_->num_browser_seek_requests());
EXPECT_EQ(1, demuxer_->num_data_requests());
+
+ // Simulate browser seek is done and confirm player requests more data for new
+ // video decoder job.
+ player_.OnDemuxerSeekDone(player_.GetCurrentTime());
+ EXPECT_TRUE(GetMediaDecoderJob(false));
+ EXPECT_EQ(2, demuxer_->num_data_requests());
+ EXPECT_EQ(1, demuxer_->num_seek_requests());
}
-TEST_F(MediaSourcePlayerTest, StartAfterSeekFinish) {
+TEST_F(MediaSourcePlayerTest, AudioOnlyStartAfterSeekFinish) {
SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE();
- // Test decoder job will not start until all pending seek event is handled.
- DemuxerConfigs configs;
- configs.audio_codec = kCodecVorbis;
- configs.audio_channels = 2;
- configs.audio_sampling_rate = 44100;
- configs.is_audio_encrypted = false;
- configs.duration_ms = kDefaultDurationInMs;
+ // Test audio decoder job will not start until pending seek event is handled.
+ DemuxerConfigs configs = CreateAudioDemuxerConfigs();
player_.OnDemuxerConfigsAvailable(configs);
- EXPECT_EQ(NULL, GetMediaDecoderJob(true));
+ EXPECT_FALSE(GetMediaDecoderJob(true));
EXPECT_EQ(0, demuxer_->num_data_requests());
- // Initiate a seek. Skip the round-trip of requesting seek from renderer.
+ // Initiate a seek. Skip requesting element seek of renderer.
// Instead behave as if the renderer has asked us to seek.
player_.SeekTo(base::TimeDelta());
EXPECT_EQ(1, demuxer_->num_seek_requests());
player_.Start();
- EXPECT_EQ(NULL, GetMediaDecoderJob(true));
+ EXPECT_FALSE(GetMediaDecoderJob(true));
EXPECT_EQ(0, demuxer_->num_data_requests());
// Sending back the seek done notification.
- player_.OnDemuxerSeekDone();
+ player_.OnDemuxerSeekDone(kNoTimestamp());
EXPECT_TRUE(GetMediaDecoderJob(true));
EXPECT_EQ(1, demuxer_->num_data_requests());
@@ -442,6 +678,33 @@ TEST_F(MediaSourcePlayerTest, StartAfterSeekFinish) {
EXPECT_EQ(1, demuxer_->num_seek_requests());
}
+TEST_F(MediaSourcePlayerTest, VideoOnlyStartAfterSeekFinish) {
+ SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE();
+
+ // Test video decoder job will not start until pending seek event is handled.
+ CreateNextTextureAndSetVideoSurface();
+ DemuxerConfigs configs = CreateVideoDemuxerConfigs();
+ player_.OnDemuxerConfigsAvailable(configs);
+ EXPECT_FALSE(GetMediaDecoderJob(false));
+
+ // Initiate a seek. Skip requesting element seek of renderer.
+ // Instead behave as if the renderer has asked us to seek.
+ player_.SeekTo(base::TimeDelta());
+ EXPECT_EQ(1, demuxer_->num_seek_requests());
+
+ player_.Start();
+ EXPECT_FALSE(GetMediaDecoderJob(false));
+ EXPECT_EQ(0, demuxer_->num_data_requests());
+
+ // Sending back the seek done notification.
+ player_.OnDemuxerSeekDone(kNoTimestamp());
+ EXPECT_TRUE(GetMediaDecoderJob(false));
+ EXPECT_EQ(1, demuxer_->num_data_requests());
+
+ // Reconfirm exactly 1 seek request has been made of demuxer.
+ EXPECT_EQ(1, demuxer_->num_seek_requests());
+}
+
TEST_F(MediaSourcePlayerTest, StartImmediatelyAfterPause) {
SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE();
@@ -480,23 +743,11 @@ TEST_F(MediaSourcePlayerTest, DecoderJobsCannotStartWithoutAudio) {
// Test that when Start() is called, video decoder jobs will wait for audio
// decoder job before start decoding the data.
- DemuxerConfigs configs;
- configs.audio_codec = kCodecVorbis;
- configs.audio_channels = 2;
- configs.audio_sampling_rate = 44100;
- configs.is_audio_encrypted = false;
- scoped_refptr<DecoderBuffer> buffer = ReadTestDataFile("vorbis-extradata");
- configs.audio_extra_data = std::vector<uint8>(
- buffer->data(),
- buffer->data() + buffer->data_size());
- configs.video_codec = kCodecVP8;
- configs.video_size = gfx::Size(320, 240);
- configs.is_video_encrypted = false;
- configs.duration_ms = kDefaultDurationInMs;
+ DemuxerConfigs configs = CreateAudioVideoDemuxerConfigs();
Start(configs);
EXPECT_EQ(0, demuxer_->num_data_requests());
- CreateAndSetVideoSurface();
+ CreateNextTextureAndSetVideoSurface();
// Player should not seek the demuxer on setting initial surface.
EXPECT_EQ(0, demuxer_->num_seek_requests());
@@ -515,6 +766,9 @@ TEST_F(MediaSourcePlayerTest, DecoderJobsCannotStartWithoutAudio) {
player_.OnDemuxerDataAvailable(CreateReadFromDemuxerAckForAudio(0));
EXPECT_TRUE(audio_decoder_job->is_decoding());
EXPECT_TRUE(video_decoder_job->is_decoding());
+
+ // Reconfirm no seek occurred.
+ EXPECT_EQ(0, demuxer_->num_seek_requests());
}
TEST_F(MediaSourcePlayerTest, StartTimeTicksResetAfterDecoderUnderruns) {
@@ -548,7 +802,7 @@ TEST_F(MediaSourcePlayerTest, StartTimeTicksResetAfterDecoderUnderruns) {
// Send new data to the decoder so it can finish the currently
// pending decode.
player_.OnDemuxerDataAvailable(CreateReadFromDemuxerAckForAudio(3));
- while(GetMediaDecoderJob(true)->is_decoding())
+ while (GetMediaDecoderJob(true)->is_decoding())
message_loop_.RunUntilIdle();
// Verify the start time ticks is cleared at this point because the
@@ -569,7 +823,7 @@ TEST_F(MediaSourcePlayerTest, NoRequestForDataAfterInputEOS) {
// Test MediaSourcePlayer will not request for new data after input EOS is
// reached.
- CreateAndSetVideoSurface();
+ CreateNextTextureAndSetVideoSurface();
StartVideoDecoderJob();
// Player should not seek the demuxer on setting initial surface.
EXPECT_EQ(0, demuxer_->num_seek_requests());
@@ -585,6 +839,9 @@ TEST_F(MediaSourcePlayerTest, NoRequestForDataAfterInputEOS) {
message_loop_.Run();
// No more request for data should be made.
EXPECT_EQ(2, demuxer_->num_data_requests());
+
+ // Reconfirm no seek request has occurred.
+ EXPECT_EQ(0, demuxer_->num_seek_requests());
}
TEST_F(MediaSourcePlayerTest, ReplayAfterInputEOS) {
@@ -592,7 +849,7 @@ TEST_F(MediaSourcePlayerTest, ReplayAfterInputEOS) {
// Test MediaSourcePlayer can replay after input EOS is
// reached.
- CreateAndSetVideoSurface();
+ CreateNextTextureAndSetVideoSurface();
StartVideoDecoderJob();
// Player should not seek the demuxer on setting initial surface.
@@ -610,37 +867,40 @@ TEST_F(MediaSourcePlayerTest, ReplayAfterInputEOS) {
// No more request for data should be made.
EXPECT_EQ(2, demuxer_->num_data_requests());
- // Initiate a seek. Skip the round-trip of requesting seek from renderer.
+ // Initiate a seek. Skip requesting element seek of renderer.
// Instead behave as if the renderer has asked us to seek.
player_.SeekTo(base::TimeDelta());
StartVideoDecoderJob();
EXPECT_EQ(1, demuxer_->num_seek_requests());
- player_.OnDemuxerSeekDone();
+ player_.OnDemuxerSeekDone(kNoTimestamp());
// Seek/Play after EOS should request more data.
EXPECT_EQ(3, demuxer_->num_data_requests());
+
+ // Reconfirm only 1 seek request has occurred.
+ EXPECT_EQ(1, demuxer_->num_seek_requests());
}
TEST_F(MediaSourcePlayerTest, NoRequestForDataAfterAbort) {
SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE();
- // Test that the decoder will request new data after receiving an aborted
+ // Test that the decoder will not request new data after receiving an aborted
// access unit.
StartAudioDecoderJob();
EXPECT_EQ(1, demuxer_->num_data_requests());
// Send an aborted access unit.
- DemuxerData data;
- data.type = DemuxerStream::AUDIO;
- data.access_units.resize(1);
- data.access_units[0].status = DemuxerStream::kAborted;
- player_.OnDemuxerDataAvailable(data);
+ player_.OnDemuxerDataAvailable(CreateAbortedAck(true));
+
EXPECT_TRUE(GetMediaDecoderJob(true)->is_decoding());
// Wait for the decoder job to finish decoding.
- while(GetMediaDecoderJob(true)->is_decoding())
+ while (GetMediaDecoderJob(true)->is_decoding())
message_loop_.RunUntilIdle();
// No request will be sent for new data.
EXPECT_EQ(1, demuxer_->num_data_requests());
+
+ // No seek requests should have occurred.
+ EXPECT_EQ(0, demuxer_->num_seek_requests());
}
TEST_F(MediaSourcePlayerTest, DemuxerDataArrivesAfterRelease) {
@@ -649,7 +909,6 @@ TEST_F(MediaSourcePlayerTest, DemuxerDataArrivesAfterRelease) {
// Test that the decoder should not crash if demuxer data arrives after
// Release().
StartAudioDecoderJob();
- EXPECT_TRUE(player_.IsPlaying());
EXPECT_EQ(1, demuxer_->num_data_requests());
EXPECT_TRUE(GetMediaDecoderJob(true));
@@ -659,37 +918,285 @@ TEST_F(MediaSourcePlayerTest, DemuxerDataArrivesAfterRelease) {
// The decoder job should have been released.
EXPECT_FALSE(player_.IsPlaying());
EXPECT_EQ(1, demuxer_->num_data_requests());
+
+ // No seek requests should have occurred.
+ EXPECT_EQ(0, demuxer_->num_seek_requests());
}
-TEST_F(MediaSourcePlayerTest, PrerollAfterSeek) {
- if (!MediaCodecBridge::IsAvailable()) {
- LOG(INFO) << "Could not run test - not supported on device.";
- return;
+TEST_F(MediaSourcePlayerTest, BrowserSeek_RegularSeekPendsBrowserSeekDone) {
+ SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE();
+
+ // Test that a browser seek, once started, delays a newly arrived regular
+ // SeekTo() request's demuxer seek until the browser seek is done.
+ BrowserSeekPlayer(false);
+ EXPECT_EQ(1, demuxer_->num_data_requests());
+
+ // Simulate renderer requesting a regular seek while browser seek in progress.
+ player_.SeekTo(base::TimeDelta());
+ EXPECT_FALSE(GetMediaDecoderJob(false));
+
+ // Simulate browser seek is done. Confirm player requests the regular seek,
+ // still has no video decoder job configured, and has not requested any
+ // further data since the surface change event became pending in
+ // BrowserSeekPlayer().
+ EXPECT_EQ(1, demuxer_->num_seek_requests());
+ player_.OnDemuxerSeekDone(base::TimeDelta());
+ EXPECT_FALSE(GetMediaDecoderJob(false));
+ EXPECT_EQ(2, demuxer_->num_seek_requests());
+ EXPECT_EQ(1, demuxer_->num_browser_seek_requests());
+ EXPECT_EQ(1, demuxer_->num_data_requests());
+
+ // Simulate regular seek is done and confirm player requests more data for
+ // new video decoder job.
+ player_.OnDemuxerSeekDone(kNoTimestamp());
+ EXPECT_TRUE(GetMediaDecoderJob(false));
+ EXPECT_EQ(2, demuxer_->num_data_requests());
+ EXPECT_EQ(2, demuxer_->num_seek_requests());
+}
+
+TEST_F(MediaSourcePlayerTest, NoSeekForInitialReleaseAndStart) {
+ SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE();
+
+ // Test that no seek is requested if player Release() + Start() occurs prior
+ // to receiving any data.
+ CreateNextTextureAndSetVideoSurface();
+ StartVideoDecoderJob();
+ EXPECT_EQ(1, demuxer_->num_data_requests());
+ EXPECT_TRUE(GetMediaDecoderJob(false));
+
+ player_.Release();
+ EXPECT_FALSE(player_.IsPlaying());
+
+ // Pass a new non-empty surface.
+ CreateNextTextureAndSetVideoSurface();
+
+ player_.Start();
+
+ // TODO(wolenetz/qinmin): Multiple in-flight data requests for same stream
+ // should be prevented. See http://crbug.com/306314.
+ EXPECT_EQ(2, demuxer_->num_data_requests());
+
+ EXPECT_EQ(0, demuxer_->num_seek_requests());
+ EXPECT_TRUE(GetMediaDecoderJob(false));
+}
+
+TEST_F(MediaSourcePlayerTest, BrowserSeek_MidStreamReleaseAndStart) {
+ SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE();
+
+ // Test that one browser seek is requested if player Release() + Start(), with
+ // video data received between Release() and Start().
+ BrowserSeekPlayer(true);
+ EXPECT_FALSE(GetMediaDecoderJob(false));
+ EXPECT_EQ(1, demuxer_->num_data_requests());
+
+ // Simulate browser seek is done and confirm player requests more data.
+ player_.OnDemuxerSeekDone(base::TimeDelta());
+ EXPECT_TRUE(GetMediaDecoderJob(false));
+ EXPECT_EQ(2, demuxer_->num_data_requests());
+ EXPECT_EQ(1, demuxer_->num_seek_requests());
+}
+
+TEST_F(MediaSourcePlayerTest, PrerollAudioAfterSeek) {
+ SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE();
+
+ // Test decoder job will preroll the media to the seek position.
+ StartAudioDecoderJob();
+ EXPECT_TRUE(GetMediaDecoderJob(true));
+ EXPECT_EQ(1, demuxer_->num_data_requests());
+
+ SeekPlayer(true, base::TimeDelta::FromMilliseconds(100));
+ EXPECT_TRUE(IsPrerolling(true));
+ EXPECT_EQ(100.0, GetPrerollTimestamp().InMillisecondsF());
+
+ // Send some data before the seek position.
+ for (int i = 1; i < 4; ++i) {
+ player_.OnDemuxerDataAvailable(CreateReadFromDemuxerAckForAudio(i));
+ EXPECT_TRUE(GetMediaDecoderJob(true)->is_decoding());
+ message_loop_.Run();
}
+ EXPECT_EQ(100.0, player_.GetCurrentTime().InMillisecondsF());
+ EXPECT_TRUE(IsPrerolling(true));
+
+ // Send data after the seek position.
+ DemuxerData data = CreateReadFromDemuxerAckForAudio(3);
+ data.access_units[0].timestamp = base::TimeDelta::FromMilliseconds(100);
+ player_.OnDemuxerDataAvailable(data);
+ message_loop_.Run();
+ EXPECT_LT(100.0, player_.GetCurrentTime().InMillisecondsF());
+ EXPECT_FALSE(IsPrerolling(true));
+}
+
+TEST_F(MediaSourcePlayerTest, PrerollVideoAfterSeek) {
+ SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE();
// Test decoder job will preroll the media to the seek position.
+ CreateNextTextureAndSetVideoSurface();
+ StartVideoDecoderJob();
+ EXPECT_TRUE(GetMediaDecoderJob(false));
+ EXPECT_EQ(1, demuxer_->num_data_requests());
+
+ SeekPlayer(false, base::TimeDelta::FromMilliseconds(100));
+ EXPECT_TRUE(IsPrerolling(false));
+ EXPECT_EQ(100.0, GetPrerollTimestamp().InMillisecondsF());
+
+ // Send some data before the seek position.
+ DemuxerData data;
+ for (int i = 1; i < 4; ++i) {
+ data = CreateReadFromDemuxerAckForVideo();
+ data.access_units[0].timestamp = base::TimeDelta::FromMilliseconds(i * 30);
+ player_.OnDemuxerDataAvailable(data);
+ EXPECT_TRUE(GetMediaDecoderJob(false)->is_decoding());
+ message_loop_.Run();
+ }
+ EXPECT_EQ(100.0, player_.GetCurrentTime().InMillisecondsF());
+ EXPECT_TRUE(IsPrerolling(false));
+
+ // Send data at the seek position.
+ data = CreateReadFromDemuxerAckForVideo();
+ data.access_units[0].timestamp = base::TimeDelta::FromMilliseconds(100);
+ player_.OnDemuxerDataAvailable(data);
+ message_loop_.Run();
+
+ // TODO(wolenetz/qinmin): Player's maintenance of current time for video-only
+ // streams depends on decoder output, which may be initially inaccurate, and
+ // encoded video test data may also need updating. Verify at least that AU
+ // timestamp-based preroll logic has determined video preroll has completed.
+ EXPECT_FALSE(IsPrerolling(false));
+}
+
+TEST_F(MediaSourcePlayerTest, SeekingAfterCompletingPrerollRestartsPreroll) {
+ SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE();
+
+ // Test decoder job will begin prerolling upon seek, when it was not
+ // prerolling prior to the seek.
+ StartAudioDecoderJob();
+ MediaDecoderJob* decoder_job = GetMediaDecoderJob(true);
+ EXPECT_TRUE(decoder_job);
+ EXPECT_EQ(1, demuxer_->num_data_requests());
+ EXPECT_TRUE(IsPrerolling(true));
+
+ // Complete the initial preroll by feeding data to the decoder.
+ for (int i = 0; i < 4; ++i) {
+ player_.OnDemuxerDataAvailable(CreateReadFromDemuxerAckForAudio(i));
+ EXPECT_TRUE(GetMediaDecoderJob(true)->is_decoding());
+ message_loop_.Run();
+ }
+ EXPECT_LT(0.0, player_.GetCurrentTime().InMillisecondsF());
+ EXPECT_FALSE(IsPrerolling(true));
+
+ SeekPlayer(true, base::TimeDelta::FromMilliseconds(500));
+
+ // Prerolling should have begun again.
+ EXPECT_TRUE(IsPrerolling(true));
+ EXPECT_EQ(500.0, GetPrerollTimestamp().InMillisecondsF());
+
+ // Send data at and after the seek position. Prerolling should complete.
+ for (int i = 0; i < 4; ++i) {
+ DemuxerData data = CreateReadFromDemuxerAckForAudio(i);
+ data.access_units[0].timestamp = base::TimeDelta::FromMilliseconds(
+ 500 + 30 * (i - 1));
+ player_.OnDemuxerDataAvailable(data);
+ EXPECT_TRUE(GetMediaDecoderJob(true)->is_decoding());
+ message_loop_.Run();
+ }
+ EXPECT_LT(500.0, player_.GetCurrentTime().InMillisecondsF());
+ EXPECT_FALSE(IsPrerolling(true));
+
+ // Throughout this test, we should have not re-created the decoder job, so
+ // IsPrerolling() transition from false to true was not due to constructor
+ // initialization. It was due to BeginPrerolling().
+ EXPECT_EQ(decoder_job, GetMediaDecoderJob(true));
+}
+
+TEST_F(MediaSourcePlayerTest, PrerollContinuesAcrossReleaseAndStart) {
+ SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE();
+
+ // Test decoder job will resume media prerolling if interrupted by Release()
+ // and Start().
StartAudioDecoderJob();
EXPECT_TRUE(GetMediaDecoderJob(true));
EXPECT_EQ(1, demuxer_->num_data_requests());
- // Initiate a seek to 100 ms. This will abort the intial prefetch.
- player_.SeekTo(base::TimeDelta::FromMilliseconds(100));
+ SeekPlayer(true, base::TimeDelta::FromMilliseconds(100));
+ EXPECT_TRUE(IsPrerolling(true));
+ EXPECT_EQ(100.0, GetPrerollTimestamp().InMillisecondsF());
+
+ // Send some data before the seek position.
+ // Test uses 'large' number of iterations because decoder job may not get
+ // MEDIA_CODEC_OK output status until after a few dequeue output attempts.
+ // This allows decoder status to stabilize prior to AU timestamp reaching
+ // the preroll target.
DemuxerData data;
- data.type = DemuxerStream::AUDIO;
- data.access_units.resize(1);
- data.access_units[0].status = DemuxerStream::kAborted;
+ for (int i = 0; i < 10; ++i) {
+ data = CreateReadFromDemuxerAckForAudio(3);
+ data.access_units[0].timestamp = base::TimeDelta::FromMilliseconds(i * 10);
+ if (i == 1) {
+ // While still prerolling, Release() and Start() the player.
+ // TODO(qinmin): Simulation of multiple in-flight data requests (one from
+ // before Release(), one from after Start()) is not included here, and
+ // neither is any data enqueued for later decode if it arrives after
+ // Release() and before Start(). See http://crbug.com/306314. Assumption
+ // for this test, to prevent flakiness until the bug is fixed, is the
+ // first request's data arrives before Start(). Though that data is not
+ // seen by decoder, this assumption allows preroll continuation
+ // verification and prevents multiple in-flight data requests.
+ player_.Release();
+ player_.OnDemuxerDataAvailable(data);
+ message_loop_.RunUntilIdle();
+ EXPECT_FALSE(GetMediaDecoderJob(true));
+ StartAudioDecoderJob();
+ EXPECT_TRUE(GetMediaDecoderJob(true));
+ } else {
+ player_.OnDemuxerDataAvailable(data);
+ EXPECT_TRUE(GetMediaDecoderJob(true)->is_decoding());
+ message_loop_.Run();
+ }
+ EXPECT_TRUE(IsPrerolling(true));
+ }
+ EXPECT_EQ(100.0, player_.GetCurrentTime().InMillisecondsF());
+ EXPECT_TRUE(IsPrerolling(true));
+
+ // Send data after the seek position.
+ data = CreateReadFromDemuxerAckForAudio(3);
+ data.access_units[0].timestamp = base::TimeDelta::FromMilliseconds(100);
player_.OnDemuxerDataAvailable(data);
- EXPECT_EQ(1, demuxer_->num_seek_requests());
- player_.OnDemuxerSeekDone();
- EXPECT_EQ(2, demuxer_->num_data_requests());
+ message_loop_.Run();
+ EXPECT_LT(100.0, player_.GetCurrentTime().InMillisecondsF());
+ EXPECT_FALSE(IsPrerolling(true));
+}
+
+TEST_F(MediaSourcePlayerTest, PrerollContinuesAcrossConfigChange) {
+ SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE();
+
+ // Test decoder job will resume media prerolling if interrupted by
+ // |kConfigChanged| and OnDemuxerConfigsAvailable().
+ StartAudioDecoderJob();
+ EXPECT_TRUE(GetMediaDecoderJob(true));
+ EXPECT_EQ(1, demuxer_->num_data_requests());
+
+ SeekPlayer(true, base::TimeDelta::FromMilliseconds(100));
+ EXPECT_TRUE(IsPrerolling(true));
+ EXPECT_EQ(100.0, GetPrerollTimestamp().InMillisecondsF());
+
+ // In response to data request, simulate that demuxer signals config change by
+ // sending an AU with |kConfigChanged|. Player should prepare to reconfigure
+ // the audio decoder job, and should request new demuxer configs.
+ DemuxerData data = CreateReadFromDemuxerAckWithConfigChanged(true, 0);
+ EXPECT_EQ(0, demuxer_->num_config_requests());
+ player_.OnDemuxerDataAvailable(data);
+ EXPECT_EQ(1, demuxer_->num_config_requests());
+
+ // Simulate arrival of new configs.
+ player_.OnDemuxerConfigsAvailable(CreateAudioDemuxerConfigs());
- // Send some data with before the seek position.
+ // Send some data before the seek position.
for (int i = 1; i < 4; ++i) {
player_.OnDemuxerDataAvailable(CreateReadFromDemuxerAckForAudio(i));
EXPECT_TRUE(GetMediaDecoderJob(true)->is_decoding());
message_loop_.Run();
}
EXPECT_EQ(100.0, player_.GetCurrentTime().InMillisecondsF());
+ EXPECT_TRUE(IsPrerolling(true));
// Send data after the seek position.
data = CreateReadFromDemuxerAckForAudio(3);
@@ -697,6 +1204,179 @@ TEST_F(MediaSourcePlayerTest, PrerollAfterSeek) {
player_.OnDemuxerDataAvailable(data);
message_loop_.Run();
EXPECT_LT(100.0, player_.GetCurrentTime().InMillisecondsF());
+ EXPECT_FALSE(IsPrerolling(true));
+}
+
+TEST_F(MediaSourcePlayerTest, DemuxerConfigRequestedIfInPrefetchUnit0) {
+ SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE();
+
+ // Test that the player detects need for and requests demuxer configs if
+ // the |kConfigChanged| unit is the very first unit in the set of units
+ // received in OnDemuxerDataAvailable() ostensibly while
+ // |PREFETCH_DONE_EVENT_PENDING|.
+ StartConfigChange(true, true, 0);
+}
+
+TEST_F(MediaSourcePlayerTest, DemuxerConfigRequestedIfInPrefetchUnit1) {
+ SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE();
+
+ // Test that the player detects need for and requests demuxer configs if
+ // the |kConfigChanged| unit is not the first unit in the set of units
+ // received in OnDemuxerDataAvailable() ostensibly while
+ // |PREFETCH_DONE_EVENT_PENDING|.
+ StartConfigChange(true, true, 1);
+}
+
+TEST_F(MediaSourcePlayerTest, DemuxerConfigRequestedIfInUnit0AfterPrefetch) {
+ SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE();
+
+ // Test that the player detects need for and requests demuxer configs if
+ // the |kConfigChanged| unit is the very first unit in the set of units
+ // received in OnDemuxerDataAvailable() from data requested ostensibly while
+ // not prefetching.
+ StartConfigChange(true, false, 0);
+}
+
+TEST_F(MediaSourcePlayerTest, DemuxerConfigRequestedIfInUnit1AfterPrefetch) {
+ SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE();
+
+ // Test that the player detects need for and requests demuxer configs if
+ // the |kConfigChanged| unit is not the first unit in the set of units
+ // received in OnDemuxerDataAvailable() from data requested ostensibly while
+ // not prefetching.
+ StartConfigChange(true, false, 1);
+}
+
+TEST_F(MediaSourcePlayerTest, BrowserSeek_PrerollAfterBrowserSeek) {
+ SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE();
+
+ // Test decoder job will preroll the media to the actual seek position
+ // resulting from a browser seek.
+ BrowserSeekPlayer(false);
+
+ // Simulate browser seek is done, but to a later time than was requested.
+ EXPECT_LT(player_.GetCurrentTime().InMillisecondsF(), 100);
+ player_.OnDemuxerSeekDone(base::TimeDelta::FromMilliseconds(100));
+ EXPECT_TRUE(GetMediaDecoderJob(false));
+ EXPECT_EQ(100.0, player_.GetCurrentTime().InMillisecondsF());
+ EXPECT_EQ(100.0, GetPrerollTimestamp().InMillisecondsF());
+ EXPECT_EQ(2, demuxer_->num_data_requests());
+
+ // Send some data with access unit timestamps before the actual browser seek
+ // position. This is a bit unrealistic in this case where the browser seek
+ // jumped forward and next data from demuxer would normally begin at this
+ // browser seek position, immediately completing preroll. For simplicity and
+ // coverage, this test simulates the more common condition that AUs received
+ // after browser seek begin with timestamps before the seek target, and don't
+ // immediately complete preroll.
+ DemuxerData data;
+ for (int i = 1; i < 4; ++i) {
+ data = CreateReadFromDemuxerAckForVideo();
+ data.access_units[0].timestamp = base::TimeDelta::FromMilliseconds(i * 30);
+ player_.OnDemuxerDataAvailable(data);
+ EXPECT_TRUE(GetMediaDecoderJob(false)->is_decoding());
+ message_loop_.Run();
+ EXPECT_TRUE(IsPrerolling(false));
+ }
+
+ EXPECT_EQ(100.0, player_.GetCurrentTime().InMillisecondsF());
+
+ // Send data after the browser seek position.
+ data = CreateReadFromDemuxerAckForVideo();
+ data.access_units[0].timestamp = base::TimeDelta::FromMilliseconds(120);
+ player_.OnDemuxerDataAvailable(data);
+ message_loop_.Run();
+ EXPECT_FALSE(IsPrerolling(false));
+}
+
+TEST_F(MediaSourcePlayerTest, VideoDemuxerConfigChange) {
+ SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE();
+
+ // Test that video config change notification results in request for demuxer
+ // configuration, and that a video decoder job results without any browser
+ // seek necessary once the new demuxer config arrives.
+ StartConfigChange(false, true, 1);
+ MediaDecoderJob* first_job = GetMediaDecoderJob(false);
+ EXPECT_TRUE(first_job);
+ EXPECT_EQ(1, demuxer_->num_data_requests());
+ EXPECT_EQ(1, demuxer_->num_config_requests());
+
+ // Simulate arrival of new configs.
+ player_.OnDemuxerConfigsAvailable(CreateVideoDemuxerConfigs());
+
+ // New video decoder job should have been created and configured, without any
+ // browser seek.
+ MediaDecoderJob* second_job = GetMediaDecoderJob(false);
+ EXPECT_TRUE(second_job);
+ EXPECT_NE(first_job, second_job);
+ EXPECT_EQ(2, demuxer_->num_data_requests());
+ EXPECT_EQ(1, demuxer_->num_config_requests());
+ EXPECT_EQ(0, demuxer_->num_seek_requests());
+}
+
+TEST_F(MediaSourcePlayerTest, VideoConfigChangeContinuesAcrossSeek) {
+ SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE();
+
+ // Test if a demuxer config request is pending (due to previously receiving
+ // |kConfigChanged|), and a seek request arrives prior to demuxer configs,
+ // then seek is processed first, followed by the decoder config change.
+ // This assumes the demuxer sends |kConfigChanged| read response prior to
+ // canceling any reads pending seek; no |kAborted| is involved in this test.
+ StartConfigChange(false, false, 1);
+ MediaDecoderJob* first_job = GetMediaDecoderJob(false);
+ EXPECT_TRUE(first_job);
+ EXPECT_EQ(1, demuxer_->num_config_requests());
+ EXPECT_EQ(2, demuxer_->num_data_requests());
+ EXPECT_EQ(0, demuxer_->num_seek_requests());
+
+ player_.SeekTo(base::TimeDelta::FromMilliseconds(100));
+
+ // Verify that the seek is requested immediately.
+ EXPECT_EQ(1, demuxer_->num_seek_requests());
+
+ // Simulate unlikely delayed arrival of the demuxer configs, completing the
+ // config change.
+ // TODO(wolenetz): Is it even possible for requested demuxer configs to be
+ // delayed until after a SeekTo request arrives?
+ player_.OnDemuxerConfigsAvailable(CreateVideoDemuxerConfigs());
+
+ MediaDecoderJob* second_job = GetMediaDecoderJob(false);
+ EXPECT_NE(first_job, second_job);
+ EXPECT_TRUE(second_job);
+
+ // Send back the seek done notification. This should finish the seek and
+ // trigger the player to request more data.
+ EXPECT_EQ(2, demuxer_->num_data_requests());
+ player_.OnDemuxerSeekDone(kNoTimestamp());
+ EXPECT_EQ(3, demuxer_->num_data_requests());
+}
+
+TEST_F(MediaSourcePlayerTest, NewSurfaceWhileChangingConfigs) {
+ SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE();
+
+ // Test that no seek or duplicated demuxer config request results from a
+ // SetVideoSurface() that occurs while the player is expecting new demuxer
+ // configs. This test may be good to keep beyond browser seek hack.
+ StartConfigChange(false, false, 1);
+ MediaDecoderJob* first_job = GetMediaDecoderJob(false);
+ EXPECT_TRUE(first_job);
+ EXPECT_EQ(1, demuxer_->num_config_requests());
+ EXPECT_EQ(2, demuxer_->num_data_requests());
+
+ CreateNextTextureAndSetVideoSurface();
+
+ // Surface change processing (including decoder job re-creation) should
+ // not occur until the pending video config change is completed.
+ EXPECT_EQ(first_job, GetMediaDecoderJob(false));
+
+ player_.OnDemuxerConfigsAvailable(CreateVideoDemuxerConfigs());
+ MediaDecoderJob* second_job = GetMediaDecoderJob(false);
+ EXPECT_NE(first_job, second_job);
+ EXPECT_TRUE(second_job);
+
+ EXPECT_EQ(3, demuxer_->num_data_requests());
+ EXPECT_EQ(1, demuxer_->num_config_requests());
+ EXPECT_EQ(0, demuxer_->num_seek_requests());
}
// TODO(xhwang): Enable this test when the test devices are updated.
@@ -771,7 +1451,5 @@ TEST_F(MediaSourcePlayerTest, IsTypeSupported_InvalidUUID) {
// TODO(xhwang): Are these IsTypeSupported tests device specific?
// TODO(xhwang): Add more IsTypeSupported tests.
-// TODO(wolenetz): Add tests around second SetVideoSurface, once fix to reach
-// next I-frame is correct.
} // namespace media
diff --git a/media/base/audio_bus_perftest.cc b/media/base/audio_bus_perftest.cc
new file mode 100644
index 0000000000..e4152fdfc6
--- /dev/null
+++ b/media/base/audio_bus_perftest.cc
@@ -0,0 +1,52 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/time/time.h"
+#include "media/base/audio_bus.h"
+#include "media/base/fake_audio_render_callback.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/perf/perf_test.h"
+
+namespace media {
+
+static const int kBenchmarkIterations = 100;
+
+template <typename T>
+void RunInterleaveBench(AudioBus* bus, const std::string& trace_name) {
+ const int frame_size = bus->frames() * bus->channels();
+ scoped_ptr<T> interleaved(new T[frame_size]);
+ const int byte_size = sizeof(*interleaved);
+
+ base::TimeTicks start = base::TimeTicks::HighResNow();
+ for (int i = 0; i < kBenchmarkIterations; ++i) {
+ bus->ToInterleaved(bus->frames(), byte_size, interleaved.get());
+ }
+ double total_time_seconds =
+ (base::TimeTicks::HighResNow() - start).InSecondsF();
+ perf_test::PrintResult(
+ "audio_bus_to_interleaved", "", trace_name,
+ kBenchmarkIterations / total_time_seconds, "runs/s", true);
+
+ start = base::TimeTicks::HighResNow();
+ for (int i = 0; i < kBenchmarkIterations; ++i) {
+ bus->FromInterleaved(interleaved.get(), bus->frames(), byte_size);
+ }
+ total_time_seconds = (base::TimeTicks::HighResNow() - start).InSecondsF();
+ perf_test::PrintResult(
+ "audio_bus_from_interleaved", "", trace_name,
+ kBenchmarkIterations / total_time_seconds, "runs/s", true);
+}
+
+// Benchmark the FromInterleaved() and ToInterleaved() methods.
+TEST(AudioBusPerfTest, Interleave) {
+ scoped_ptr<AudioBus> bus = AudioBus::Create(2, 48000 * 120);
+ FakeAudioRenderCallback callback(0.2);
+ callback.Render(bus.get(), 0);
+
+ RunInterleaveBench<int8>(bus.get(), "int8");
+ RunInterleaveBench<int16>(bus.get(), "int16");
+ RunInterleaveBench<int32>(bus.get(), "int32");
+}
+
+} // namespace media
diff --git a/media/base/audio_bus_unittest.cc b/media/base/audio_bus_unittest.cc
index a82090bd8e..e8c78a36b4 100644
--- a/media/base/audio_bus_unittest.cc
+++ b/media/base/audio_bus_unittest.cc
@@ -413,60 +413,4 @@ TEST_F(AudioBusTest, Scale) {
}
}
-// Benchmark the FromInterleaved() and ToInterleaved() methods.
-TEST_F(AudioBusTest, DISABLED_InterleaveBench) {
- scoped_ptr<AudioBus> bus = AudioBus::Create(2, 48000 * 120);
- const int frame_size = bus->frames() * bus->channels();
- FakeAudioRenderCallback callback(0.2);
- callback.Render(bus.get(), 0);
- {
- SCOPED_TRACE("uint8");
- scoped_ptr<uint8> interleaved(new uint8[frame_size]);
- const int byte_size = sizeof(*interleaved);
-
- base::TimeTicks start = base::TimeTicks::HighResNow();
- bus->ToInterleaved(bus->frames(), byte_size, interleaved.get());
- double total_time_ms =
- (base::TimeTicks::HighResNow() - start).InMillisecondsF();
- printf("ToInterleaved uint8 took %.2fms.\n", total_time_ms);
-
- start = base::TimeTicks::HighResNow();
- bus->FromInterleaved(interleaved.get(), bus->frames(), byte_size);
- total_time_ms = (base::TimeTicks::HighResNow() - start).InMillisecondsF();
- printf("FromInterleaved uint8 took %.2fms.\n", total_time_ms);
- }
- {
- SCOPED_TRACE("int16");
- scoped_ptr<int16> interleaved(new int16[frame_size]);
- const int byte_size = sizeof(*interleaved);
-
- base::TimeTicks start = base::TimeTicks::HighResNow();
- bus->ToInterleaved(bus->frames(), byte_size, interleaved.get());
- double total_time_ms =
- (base::TimeTicks::HighResNow() - start).InMillisecondsF();
- printf("ToInterleaved int16 took %.2fms.\n", total_time_ms);
-
- start = base::TimeTicks::HighResNow();
- bus->FromInterleaved(interleaved.get(), bus->frames(), byte_size);
- total_time_ms = (base::TimeTicks::HighResNow() - start).InMillisecondsF();
- printf("FromInterleaved int16 took %.2fms.\n", total_time_ms);
- }
- {
- SCOPED_TRACE("int32");
- scoped_ptr<int32> interleaved(new int32[frame_size]);
- const int byte_size = sizeof(*interleaved);
-
- base::TimeTicks start = base::TimeTicks::HighResNow();
- bus->ToInterleaved(bus->frames(), byte_size, interleaved.get());
- double total_time_ms =
- (base::TimeTicks::HighResNow() - start).InMillisecondsF();
- printf("ToInterleaved int32 took %.2fms.\n", total_time_ms);
-
- start = base::TimeTicks::HighResNow();
- bus->FromInterleaved(interleaved.get(), bus->frames(), byte_size);
- total_time_ms = (base::TimeTicks::HighResNow() - start).InMillisecondsF();
- printf("FromInterleaved int32 took %.2fms.\n", total_time_ms);
- }
-}
-
} // namespace media
diff --git a/media/base/audio_converter_perftest.cc b/media/base/audio_converter_perftest.cc
new file mode 100644
index 0000000000..f8570e1ec3
--- /dev/null
+++ b/media/base/audio_converter_perftest.cc
@@ -0,0 +1,79 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/time/time.h"
+#include "media/base/audio_converter.h"
+#include "media/base/fake_audio_render_callback.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/perf/perf_test.h"
+
+namespace media {
+
+static const int kBenchmarkIterations = 500000;
+
+// InputCallback that zero's out the provided AudioBus.
+class NullInputProvider : public AudioConverter::InputCallback {
+ public:
+ NullInputProvider() {}
+ virtual ~NullInputProvider() {}
+
+ virtual double ProvideInput(AudioBus* audio_bus,
+ base::TimeDelta buffer_delay) OVERRIDE {
+ audio_bus->Zero();
+ return 1;
+ }
+};
+
+void RunConvertBenchmark(const AudioParameters& in_params,
+ const AudioParameters& out_params,
+ bool fifo,
+ const std::string& trace_name) {
+ NullInputProvider fake_input1;
+ NullInputProvider fake_input2;
+ NullInputProvider fake_input3;
+ scoped_ptr<AudioBus> output_bus = AudioBus::Create(out_params);
+
+ AudioConverter converter(in_params, out_params, !fifo);
+ converter.AddInput(&fake_input1);
+ converter.AddInput(&fake_input2);
+ converter.AddInput(&fake_input3);
+
+ base::TimeTicks start = base::TimeTicks::HighResNow();
+ for (int i = 0; i < kBenchmarkIterations; ++i) {
+ converter.Convert(output_bus.get());
+ }
+ double runs_per_second = kBenchmarkIterations /
+ (base::TimeTicks::HighResNow() - start).InSecondsF();
+ perf_test::PrintResult(
+ "audio_converter", "", trace_name, runs_per_second, "runs/s", true);
+}
+
+TEST(AudioConverterPerfTest, ConvertBenchmark) {
+ // Create input and output parameters to convert between the two most common
+ // sets of parameters (as indicated via UMA data).
+ AudioParameters input_params(
+ AudioParameters::AUDIO_PCM_LINEAR, CHANNEL_LAYOUT_MONO, 48000, 16, 2048);
+ AudioParameters output_params(
+ AudioParameters::AUDIO_PCM_LINEAR, CHANNEL_LAYOUT_STEREO, 44100, 16, 440);
+
+ RunConvertBenchmark(input_params, output_params, false, "convert");
+}
+
+TEST(AudioConverterPerfTest, ConvertBenchmarkFIFO) {
+ // Create input and output parameters to convert between common buffer sizes
+ // without any resampling for the FIFO vs no FIFO benchmarks.
+ AudioParameters input_params(AudioParameters::AUDIO_PCM_LINEAR,
+ CHANNEL_LAYOUT_STEREO,
+ 44100,
+ 16,
+ 2048);
+ AudioParameters output_params(
+ AudioParameters::AUDIO_PCM_LINEAR, CHANNEL_LAYOUT_STEREO, 44100, 16, 440);
+
+ RunConvertBenchmark(input_params, output_params, true, "convert_fifo_only");
+ RunConvertBenchmark(input_params, output_params, false,
+ "convert_pass_through");
+}
+
+} // namespace media
diff --git a/media/base/audio_converter_unittest.cc b/media/base/audio_converter_unittest.cc
index d218ac882f..aeb021c311 100644
--- a/media/base/audio_converter_unittest.cc
+++ b/media/base/audio_converter_unittest.cc
@@ -7,12 +7,9 @@
#include <cmath>
-#include "base/command_line.h"
-#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "base/memory/scoped_vector.h"
#include "base/strings/string_number_conversions.h"
-#include "base/time/time.h"
#include "media/base/audio_converter.h"
#include "media/base/fake_audio_render_callback.h"
#include "testing/gmock/include/gmock/gmock.h"
@@ -20,10 +17,6 @@
namespace media {
-// Command line switch for runtime adjustment of benchmark iterations.
-static const char kBenchmarkIterations[] = "audio-converter-iterations";
-static const int kDefaultIterations = 10;
-
// Parameters which control the many input case tests.
static const int kConvertInputs = 8;
static const int kConvertCycles = 3;
@@ -234,107 +227,6 @@ TEST(AudioConverterTest, AudioDelay) {
callback.last_audio_delay_milliseconds());
}
-// InputCallback that zero's out the provided AudioBus. Used for benchmarking.
-class NullInputProvider : public AudioConverter::InputCallback {
- public:
- NullInputProvider() {}
- virtual ~NullInputProvider() {}
-
- virtual double ProvideInput(AudioBus* audio_bus,
- base::TimeDelta buffer_delay) OVERRIDE {
- audio_bus->Zero();
- return 1;
- }
-};
-
-// Benchmark for audio conversion. Original benchmarks were run with
-// --audio-converter-iterations=50000.
-TEST(AudioConverterTest, ConvertBenchmark) {
- int benchmark_iterations = kDefaultIterations;
- std::string iterations(CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
- kBenchmarkIterations));
- base::StringToInt(iterations, &benchmark_iterations);
- if (benchmark_iterations < kDefaultIterations)
- benchmark_iterations = kDefaultIterations;
-
- NullInputProvider fake_input1;
- NullInputProvider fake_input2;
- NullInputProvider fake_input3;
-
- printf("Benchmarking %d iterations:\n", benchmark_iterations);
-
- {
- // Create input and output parameters to convert between the two most common
- // sets of parameters (as indicated via UMA data).
- AudioParameters input_params(
- AudioParameters::AUDIO_PCM_LINEAR, CHANNEL_LAYOUT_MONO,
- 48000, 16, 2048);
- AudioParameters output_params(
- AudioParameters::AUDIO_PCM_LINEAR, CHANNEL_LAYOUT_STEREO,
- 44100, 16, 440);
- scoped_ptr<AudioBus> output_bus = AudioBus::Create(output_params);
-
- scoped_ptr<AudioConverter> converter(
- new AudioConverter(input_params, output_params, true));
- converter->AddInput(&fake_input1);
- converter->AddInput(&fake_input2);
- converter->AddInput(&fake_input3);
-
- // Benchmark Convert() w/ FIFO.
- base::TimeTicks start = base::TimeTicks::HighResNow();
- for (int i = 0; i < benchmark_iterations; ++i) {
- converter->Convert(output_bus.get());
- }
- double total_time_ms =
- (base::TimeTicks::HighResNow() - start).InMillisecondsF();
- printf("Convert() w/ Resampling took %.2fms.\n", total_time_ms);
- }
-
- // Create input and output parameters to convert between common buffer sizes
- // without any resampling for the FIFO vs no FIFO benchmarks.
- AudioParameters input_params(
- AudioParameters::AUDIO_PCM_LINEAR, CHANNEL_LAYOUT_STEREO,
- 44100, 16, 2048);
- AudioParameters output_params(
- AudioParameters::AUDIO_PCM_LINEAR, CHANNEL_LAYOUT_STEREO,
- 44100, 16, 440);
- scoped_ptr<AudioBus> output_bus = AudioBus::Create(output_params);
-
- {
- scoped_ptr<AudioConverter> converter(
- new AudioConverter(input_params, output_params, false));
- converter->AddInput(&fake_input1);
- converter->AddInput(&fake_input2);
- converter->AddInput(&fake_input3);
-
- // Benchmark Convert() w/ FIFO.
- base::TimeTicks start = base::TimeTicks::HighResNow();
- for (int i = 0; i < benchmark_iterations; ++i) {
- converter->Convert(output_bus.get());
- }
- double total_time_ms =
- (base::TimeTicks::HighResNow() - start).InMillisecondsF();
- printf("Convert() w/ FIFO took %.2fms.\n", total_time_ms);
- }
-
- {
- scoped_ptr<AudioConverter> converter(
- new AudioConverter(input_params, output_params, true));
- converter->AddInput(&fake_input1);
- converter->AddInput(&fake_input2);
- converter->AddInput(&fake_input3);
-
- // Benchmark Convert() w/o FIFO.
- base::TimeTicks start = base::TimeTicks::HighResNow();
- for (int i = 0; i < benchmark_iterations; ++i) {
- converter->Convert(output_bus.get());
- }
- double total_time_ms =
- (base::TimeTicks::HighResNow() - start).InMillisecondsF();
- printf("Convert() w/o FIFO took %.2fms.\n", total_time_ms);
- }
-}
-
TEST_P(AudioConverterTest, NoInputs) {
FillAudioData(1.0f);
EXPECT_TRUE(RenderAndValidateAudioData(0.0f));
diff --git a/media/base/media_switches.cc b/media/base/media_switches.cc
index c295a0d991..4e75216e0c 100644
--- a/media/base/media_switches.cc
+++ b/media/base/media_switches.cc
@@ -21,10 +21,6 @@ const char kDisableVp8AlphaPlayback[] = "disable-vp8-alpha-playback";
// Set number of threads to use for video decoding.
const char kVideoThreads[] = "video-threads";
-// Override suppressed responses to canPlayType().
-const char kOverrideEncryptedMediaCanPlayType[] =
- "override-encrypted-media-canplaytype";
-
// Enables MP3 stream parser for Media Source Extensions.
const char kEnableMP3StreamParser[] = "enable-mp3-stream-parser";
@@ -33,9 +29,6 @@ const char kEnableMP3StreamParser[] = "enable-mp3-stream-parser";
const char kDisableInfobarForProtectedMediaIdentifier[] =
"disable-infobar-for-protected-media-identifier";
-// Enables use of MediaDrm for Encrypted Media Extensions implementation.
-const char kEnableMediaDrm[] = "enable-mediadrm";
-
// Enables use of non-compositing MediaDrm decoding by default for Encrypted
// Media Extensions implementation.
const char kMediaDrmEnableNonCompositing[] = "mediadrm-enable-non-compositing";
@@ -63,6 +56,13 @@ const char kAlsaOutputDevice[] = "alsa-output-device";
// tested. See http://crbug.com/158170.
// TODO(dalecurtis): Remove this once we're sure nothing has exploded.
const char kDisableMainThreadAudio[] = "disable-main-thread-audio";
+// AVFoundation is available in versions 10.7 and onwards, and is to be used
+// http://crbug.com/288562 for both audio and video device monitoring and for
+// video capture. Being a dynamically loaded NSBundle and library, it hits the
+// Chrome startup time (http://crbug.com/311325 and http://crbug.com/311437);
+// until development is finished and the library load time issue is solved, the
+// usage of this library is hidden behind this flag.
+const char kEnableAVFoundation[] = "enable-avfoundation";
#endif
#if defined(OS_WIN)
diff --git a/media/base/media_switches.h b/media/base/media_switches.h
index 963a351cba..69fb10e260 100644
--- a/media/base/media_switches.h
+++ b/media/base/media_switches.h
@@ -22,13 +22,10 @@ MEDIA_EXPORT extern const char kDisableVp8AlphaPlayback[];
MEDIA_EXPORT extern const char kVideoThreads[];
-MEDIA_EXPORT extern const char kOverrideEncryptedMediaCanPlayType[];
-
MEDIA_EXPORT extern const char kEnableMP3StreamParser[];
#if defined(OS_ANDROID)
MEDIA_EXPORT extern const char kDisableInfobarForProtectedMediaIdentifier[];
-MEDIA_EXPORT extern const char kEnableMediaDrm[];
MEDIA_EXPORT extern const char kMediaDrmEnableNonCompositing[];
#endif
@@ -43,6 +40,7 @@ MEDIA_EXPORT extern const char kAlsaOutputDevice[];
#if defined(OS_MACOSX)
MEDIA_EXPORT extern const char kDisableMainThreadAudio[];
+MEDIA_EXPORT extern const char kEnableAVFoundation[];
#endif
#if defined(OS_WIN)
diff --git a/media/base/sinc_resampler.h b/media/base/sinc_resampler.h
index 77f331bf38..217077830c 100644
--- a/media/base/sinc_resampler.h
+++ b/media/base/sinc_resampler.h
@@ -75,7 +75,7 @@ class MEDIA_EXPORT SincResampler {
private:
FRIEND_TEST_ALL_PREFIXES(SincResamplerTest, Convolve);
- FRIEND_TEST_ALL_PREFIXES(SincResamplerTest, ConvolveBenchmark);
+ FRIEND_TEST_ALL_PREFIXES(SincResamplerPerfTest, Convolve);
void InitializeKernel();
void UpdateRegions(bool second_load);
diff --git a/media/base/sinc_resampler_perftest.cc b/media/base/sinc_resampler_perftest.cc
new file mode 100644
index 0000000000..c7e75170e6
--- /dev/null
+++ b/media/base/sinc_resampler_perftest.cc
@@ -0,0 +1,76 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/cpu.h"
+#include "base/time/time.h"
+#include "media/base/sinc_resampler.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/perf/perf_test.h"
+
+namespace media {
+
+static const int kBenchmarkIterations = 50000000;
+
+static const double kSampleRateRatio = 192000.0 / 44100.0;
+static const double kKernelInterpolationFactor = 0.5;
+
+// Helper function to provide no input to SincResampler's Convolve benchmark.
+static void DoNothing(int frames, float* destination) {}
+
+// Define platform independent function name for Convolve* tests.
+#if defined(ARCH_CPU_X86_FAMILY)
+#define CONVOLVE_FUNC Convolve_SSE
+#elif defined(ARCH_CPU_ARM_FAMILY) && defined(USE_NEON)
+#define CONVOLVE_FUNC Convolve_NEON
+#endif
+
+static void RunConvolveBenchmark(
+ SincResampler* resampler,
+ float (*convolve_fn)(const float*, const float*, const float*, double),
+ bool aligned,
+ const std::string& trace_name) {
+ base::TimeTicks start = base::TimeTicks::HighResNow();
+ for (int i = 0; i < kBenchmarkIterations; ++i) {
+ convolve_fn(resampler->get_kernel_for_testing() + (aligned ? 0 : 1),
+ resampler->get_kernel_for_testing(),
+ resampler->get_kernel_for_testing(),
+ kKernelInterpolationFactor);
+ }
+ double total_time_seconds =
+ (base::TimeTicks::HighResNow() - start).InSecondsF();
+ perf_test::PrintResult("sinc_resampler_convolve",
+ "",
+ trace_name,
+ kBenchmarkIterations / total_time_seconds,
+ "runs/s",
+ true);
+}
+
+// Benchmark for the various Convolve() methods. Make sure to build with
+// branding=Chrome so that DCHECKs are compiled out when benchmarking.
+TEST(SincResamplerPerfTest, Convolve) {
+ SincResampler resampler(kSampleRateRatio,
+ SincResampler::kDefaultRequestSize,
+ base::Bind(&DoNothing));
+
+ RunConvolveBenchmark(
+ &resampler, SincResampler::Convolve_C, true, "unoptimized_aligned");
+
+#if defined(CONVOLVE_FUNC)
+#if defined(ARCH_CPU_X86_FAMILY)
+ ASSERT_TRUE(base::CPU().has_sse());
+#endif
+ RunConvolveBenchmark(
+ &resampler, SincResampler::CONVOLVE_FUNC, true, "optimized_aligned");
+ RunConvolveBenchmark(
+ &resampler, SincResampler::CONVOLVE_FUNC, false, "optimized_unaligned");
+#endif
+}
+
+#undef CONVOLVE_FUNC
+
+} // namespace media
diff --git a/media/base/sinc_resampler_unittest.cc b/media/base/sinc_resampler_unittest.cc
index 8b89a5d380..3b460a39c3 100644
--- a/media/base/sinc_resampler_unittest.cc
+++ b/media/base/sinc_resampler_unittest.cc
@@ -9,11 +9,8 @@
#include "base/bind.h"
#include "base/bind_helpers.h"
-#include "base/command_line.h"
#include "base/cpu.h"
-#include "base/logging.h"
#include "base/strings/string_number_conversions.h"
-#include "base/strings/stringize_macros.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "media/base/sinc_resampler.h"
@@ -25,10 +22,6 @@ using testing::_;
namespace media {
static const double kSampleRateRatio = 192000.0 / 44100.0;
-static const double kKernelInterpolationFactor = 0.5;
-
-// Command line switch for runtime adjustment of ConvolveBenchmark iterations.
-static const char kConvolveIterations[] = "convolve-iterations";
// Helper class to ensure ChunkedResample() functions properly.
class MockSource {
@@ -125,6 +118,8 @@ TEST(SincResamplerTest, DISABLED_SetRatioBench) {
// this test if other optimized methods exist, otherwise the default Convolve()
// will be tested by the parameterized SincResampler tests below.
#if defined(CONVOLVE_FUNC)
+static const double kKernelInterpolationFactor = 0.5;
+
TEST(SincResamplerTest, Convolve) {
#if defined(ARCH_CPU_X86_FAMILY)
ASSERT_TRUE(base::CPU().has_sse());
@@ -161,74 +156,6 @@ TEST(SincResamplerTest, Convolve) {
}
#endif
-// Benchmark for the various Convolve() methods. Make sure to build with
-// branding=Chrome so that DCHECKs are compiled out when benchmarking. Original
-// benchmarks were run with --convolve-iterations=50000000.
-TEST(SincResamplerTest, ConvolveBenchmark) {
- // Initialize a dummy resampler.
- MockSource mock_source;
- SincResampler resampler(
- kSampleRateRatio, SincResampler::kDefaultRequestSize,
- base::Bind(&MockSource::ProvideInput, base::Unretained(&mock_source)));
-
- // Retrieve benchmark iterations from command line.
- int convolve_iterations = 10;
- std::string iterations(CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
- kConvolveIterations));
- if (!iterations.empty())
- base::StringToInt(iterations, &convolve_iterations);
-
- printf("Benchmarking %d iterations:\n", convolve_iterations);
-
- // Benchmark Convolve_C().
- base::TimeTicks start = base::TimeTicks::HighResNow();
- for (int i = 0; i < convolve_iterations; ++i) {
- resampler.Convolve_C(
- resampler.kernel_storage_.get(), resampler.kernel_storage_.get(),
- resampler.kernel_storage_.get(), kKernelInterpolationFactor);
- }
- double total_time_c_ms =
- (base::TimeTicks::HighResNow() - start).InMillisecondsF();
- printf("Convolve_C took %.2fms.\n", total_time_c_ms);
-
-#if defined(CONVOLVE_FUNC)
-#if defined(ARCH_CPU_X86_FAMILY)
- ASSERT_TRUE(base::CPU().has_sse());
-#endif
-
- // Benchmark with unaligned input pointer.
- start = base::TimeTicks::HighResNow();
- for (int j = 0; j < convolve_iterations; ++j) {
- resampler.CONVOLVE_FUNC(
- resampler.kernel_storage_.get() + 1, resampler.kernel_storage_.get(),
- resampler.kernel_storage_.get(), kKernelInterpolationFactor);
- }
- double total_time_optimized_unaligned_ms =
- (base::TimeTicks::HighResNow() - start).InMillisecondsF();
- printf(STRINGIZE(CONVOLVE_FUNC) " (unaligned) took %.2fms; which is %.2fx "
- "faster than Convolve_C.\n", total_time_optimized_unaligned_ms,
- total_time_c_ms / total_time_optimized_unaligned_ms);
-
- // Benchmark with aligned input pointer.
- start = base::TimeTicks::HighResNow();
- for (int j = 0; j < convolve_iterations; ++j) {
- resampler.CONVOLVE_FUNC(
- resampler.kernel_storage_.get(), resampler.kernel_storage_.get(),
- resampler.kernel_storage_.get(), kKernelInterpolationFactor);
- }
- double total_time_optimized_aligned_ms =
- (base::TimeTicks::HighResNow() - start).InMillisecondsF();
- printf(STRINGIZE(CONVOLVE_FUNC) " (aligned) took %.2fms; which is %.2fx "
- "faster than Convolve_C and %.2fx faster than "
- STRINGIZE(CONVOLVE_FUNC) " (unaligned).\n",
- total_time_optimized_aligned_ms,
- total_time_c_ms / total_time_optimized_aligned_ms,
- total_time_optimized_unaligned_ms / total_time_optimized_aligned_ms);
-#endif
-}
-
-#undef CONVOLVE_FUNC
-
// Fake audio source for testing the resampler. Generates a sinusoidal linear
// chirp (http://en.wikipedia.org/wiki/Chirp) which can be tuned to stress the
// resampler for the specific sample rate conversion being used.
diff --git a/media/base/vector_math_perftest.cc b/media/base/vector_math_perftest.cc
new file mode 100644
index 0000000000..78699c3d70
--- /dev/null
+++ b/media/base/vector_math_perftest.cc
@@ -0,0 +1,124 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/cpu.h"
+#include "base/memory/aligned_memory.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/time/time.h"
+#include "media/base/vector_math.h"
+#include "media/base/vector_math_testing.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/perf/perf_test.h"
+
+using base::TimeTicks;
+using std::fill;
+
+namespace media {
+
+static const int kBenchmarkIterations = 200000;
+static const float kScale = 0.5;
+static const int kVectorSize = 8192;
+
+class VectorMathPerfTest : public testing::Test {
+ public:
+ VectorMathPerfTest() {
+ // Initialize input and output vectors.
+ input_vector_.reset(static_cast<float*>(base::AlignedAlloc(
+ sizeof(float) * kVectorSize, vector_math::kRequiredAlignment)));
+ output_vector_.reset(static_cast<float*>(base::AlignedAlloc(
+ sizeof(float) * kVectorSize, vector_math::kRequiredAlignment)));
+ fill(input_vector_.get(), input_vector_.get() + kVectorSize, 1.0f);
+ fill(output_vector_.get(), output_vector_.get() + kVectorSize, 0.0f);
+ }
+
+ void RunBenchmark(void (*fn)(const float[], float, int, float[]),
+ bool aligned,
+ const std::string& test_name,
+ const std::string& trace_name) {
+ TimeTicks start = TimeTicks::HighResNow();
+ for (int i = 0; i < kBenchmarkIterations; ++i) {
+ fn(input_vector_.get(),
+ kScale,
+ kVectorSize - (aligned ? 0 : 1),
+ output_vector_.get());
+ }
+ double total_time_seconds = (TimeTicks::HighResNow() - start).InSecondsF();
+ perf_test::PrintResult(test_name,
+ "",
+ trace_name,
+ kBenchmarkIterations / total_time_seconds,
+ "runs/s",
+ true);
+ }
+
+ protected:
+ scoped_ptr_malloc<float, base::ScopedPtrAlignedFree> input_vector_;
+ scoped_ptr_malloc<float, base::ScopedPtrAlignedFree> output_vector_;
+
+ DISALLOW_COPY_AND_ASSIGN(VectorMathPerfTest);
+};
+
+// Define platform independent function name for FMAC* perf tests.
+#if defined(ARCH_CPU_X86_FAMILY)
+#define FMAC_FUNC FMAC_SSE
+#elif defined(ARCH_CPU_ARM_FAMILY) && defined(USE_NEON)
+#define FMAC_FUNC FMAC_NEON
+#endif
+
+// Benchmark for each optimized vector_math::FMAC() method.
+TEST_F(VectorMathPerfTest, FMAC) {
+ // Benchmark FMAC_C().
+ RunBenchmark(
+ vector_math::FMAC_C, true, "vector_math_fmac", "unoptimized");
+#if defined(FMAC_FUNC)
+#if defined(ARCH_CPU_X86_FAMILY)
+ ASSERT_TRUE(base::CPU().has_sse());
+#endif
+ // Benchmark FMAC_FUNC() with unaligned size.
+ ASSERT_NE((kVectorSize - 1) % (vector_math::kRequiredAlignment /
+ sizeof(float)), 0U);
+ RunBenchmark(
+ vector_math::FMAC_FUNC, false, "vector_math_fmac", "optimized_unaligned");
+ // Benchmark FMAC_FUNC() with aligned size.
+ ASSERT_EQ(kVectorSize % (vector_math::kRequiredAlignment / sizeof(float)),
+ 0U);
+ RunBenchmark(
+ vector_math::FMAC_FUNC, true, "vector_math_fmac", "optimized_aligned");
+#endif
+}
+
+#undef FMAC_FUNC
+
+// Define platform independent function name for FMULBenchmark* tests.
+#if defined(ARCH_CPU_X86_FAMILY)
+#define FMUL_FUNC FMUL_SSE
+#elif defined(ARCH_CPU_ARM_FAMILY) && defined(USE_NEON)
+#define FMUL_FUNC FMUL_NEON
+#endif
+
+// Benchmark for each optimized vector_math::FMUL() method.
+TEST_F(VectorMathPerfTest, FMUL) {
+ // Benchmark FMUL_C().
+ RunBenchmark(
+ vector_math::FMUL_C, true, "vector_math_fmul", "unoptimized");
+#if defined(FMUL_FUNC)
+#if defined(ARCH_CPU_X86_FAMILY)
+ ASSERT_TRUE(base::CPU().has_sse());
+#endif
+ // Benchmark FMUL_FUNC() with unaligned size.
+ ASSERT_NE((kVectorSize - 1) % (vector_math::kRequiredAlignment /
+ sizeof(float)), 0U);
+ RunBenchmark(
+ vector_math::FMUL_FUNC, false, "vector_math_fmac", "optimized_unaligned");
+ // Benchmark FMUL_FUNC() with aligned size.
+ ASSERT_EQ(kVectorSize % (vector_math::kRequiredAlignment / sizeof(float)),
+ 0U);
+ RunBenchmark(
+ vector_math::FMUL_FUNC, true, "vector_math_fmac", "optimized_aligned");
+#endif
+}
+
+#undef FMUL_FUNC
+
+} // namespace media
diff --git a/media/base/vector_math_unittest.cc b/media/base/vector_math_unittest.cc
index 2c7740142c..32e5ea468c 100644
--- a/media/base/vector_math_unittest.cc
+++ b/media/base/vector_math_unittest.cc
@@ -6,68 +6,50 @@
#define _USE_MATH_DEFINES
#include <cmath>
-#include "base/command_line.h"
#include "base/cpu.h"
#include "base/memory/aligned_memory.h"
#include "base/memory/scoped_ptr.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringize_macros.h"
-#include "base/time/time.h"
#include "media/base/vector_math.h"
#include "media/base/vector_math_testing.h"
#include "testing/gtest/include/gtest/gtest.h"
-using base::TimeTicks;
using std::fill;
-// Command line switch for runtime adjustment of benchmark iterations.
-static const char kBenchmarkIterations[] = "vector-math-iterations";
-static const int kDefaultIterations = 10;
+namespace media {
// Default test values.
static const float kScale = 0.5;
static const float kInputFillValue = 1.0;
static const float kOutputFillValue = 3.0;
-
-namespace media {
+static const int kVectorSize = 8192;
class VectorMathTest : public testing::Test {
public:
- static const int kVectorSize = 8192;
VectorMathTest() {
// Initialize input and output vectors.
- input_vector.reset(static_cast<float*>(base::AlignedAlloc(
+ input_vector_.reset(static_cast<float*>(base::AlignedAlloc(
sizeof(float) * kVectorSize, vector_math::kRequiredAlignment)));
- output_vector.reset(static_cast<float*>(base::AlignedAlloc(
+ output_vector_.reset(static_cast<float*>(base::AlignedAlloc(
sizeof(float) * kVectorSize, vector_math::kRequiredAlignment)));
}
void FillTestVectors(float input, float output) {
// Setup input and output vectors.
- fill(input_vector.get(), input_vector.get() + kVectorSize, input);
- fill(output_vector.get(), output_vector.get() + kVectorSize, output);
+ fill(input_vector_.get(), input_vector_.get() + kVectorSize, input);
+ fill(output_vector_.get(), output_vector_.get() + kVectorSize, output);
}
void VerifyOutput(float value) {
for (int i = 0; i < kVectorSize; ++i)
- ASSERT_FLOAT_EQ(output_vector.get()[i], value);
- }
-
- int BenchmarkIterations() {
- int vector_math_iterations = kDefaultIterations;
- std::string iterations(
- CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
- kBenchmarkIterations));
- if (!iterations.empty())
- base::StringToInt(iterations, &vector_math_iterations);
- return vector_math_iterations;
+ ASSERT_FLOAT_EQ(output_vector_.get()[i], value);
}
protected:
- int benchmark_iterations;
- scoped_ptr_malloc<float, base::ScopedPtrAlignedFree> input_vector;
- scoped_ptr_malloc<float, base::ScopedPtrAlignedFree> output_vector;
+ scoped_ptr_malloc<float, base::ScopedPtrAlignedFree> input_vector_;
+ scoped_ptr_malloc<float, base::ScopedPtrAlignedFree> output_vector_;
DISALLOW_COPY_AND_ASSIGN(VectorMathTest);
};
@@ -80,7 +62,7 @@ TEST_F(VectorMathTest, FMAC) {
SCOPED_TRACE("FMAC");
FillTestVectors(kInputFillValue, kOutputFillValue);
vector_math::FMAC(
- input_vector.get(), kScale, kVectorSize, output_vector.get());
+ input_vector_.get(), kScale, kVectorSize, output_vector_.get());
VerifyOutput(kResult);
}
@@ -88,7 +70,7 @@ TEST_F(VectorMathTest, FMAC) {
SCOPED_TRACE("FMAC_C");
FillTestVectors(kInputFillValue, kOutputFillValue);
vector_math::FMAC_C(
- input_vector.get(), kScale, kVectorSize, output_vector.get());
+ input_vector_.get(), kScale, kVectorSize, output_vector_.get());
VerifyOutput(kResult);
}
@@ -98,7 +80,7 @@ TEST_F(VectorMathTest, FMAC) {
SCOPED_TRACE("FMAC_SSE");
FillTestVectors(kInputFillValue, kOutputFillValue);
vector_math::FMAC_SSE(
- input_vector.get(), kScale, kVectorSize, output_vector.get());
+ input_vector_.get(), kScale, kVectorSize, output_vector_.get());
VerifyOutput(kResult);
}
#endif
@@ -108,7 +90,7 @@ TEST_F(VectorMathTest, FMAC) {
SCOPED_TRACE("FMAC_NEON");
FillTestVectors(kInputFillValue, kOutputFillValue);
vector_math::FMAC_NEON(
- input_vector.get(), kScale, kVectorSize, output_vector.get());
+ input_vector_.get(), kScale, kVectorSize, output_vector_.get());
VerifyOutput(kResult);
}
#endif
@@ -122,7 +104,7 @@ TEST_F(VectorMathTest, FMUL) {
SCOPED_TRACE("FMUL");
FillTestVectors(kInputFillValue, kOutputFillValue);
vector_math::FMUL(
- input_vector.get(), kScale, kVectorSize, output_vector.get());
+ input_vector_.get(), kScale, kVectorSize, output_vector_.get());
VerifyOutput(kResult);
}
@@ -130,7 +112,7 @@ TEST_F(VectorMathTest, FMUL) {
SCOPED_TRACE("FMUL_C");
FillTestVectors(kInputFillValue, kOutputFillValue);
vector_math::FMUL_C(
- input_vector.get(), kScale, kVectorSize, output_vector.get());
+ input_vector_.get(), kScale, kVectorSize, output_vector_.get());
VerifyOutput(kResult);
}
@@ -140,7 +122,7 @@ TEST_F(VectorMathTest, FMUL) {
SCOPED_TRACE("FMUL_SSE");
FillTestVectors(kInputFillValue, kOutputFillValue);
vector_math::FMUL_SSE(
- input_vector.get(), kScale, kVectorSize, output_vector.get());
+ input_vector_.get(), kScale, kVectorSize, output_vector_.get());
VerifyOutput(kResult);
}
#endif
@@ -150,142 +132,10 @@ TEST_F(VectorMathTest, FMUL) {
SCOPED_TRACE("FMUL_NEON");
FillTestVectors(kInputFillValue, kOutputFillValue);
vector_math::FMUL_NEON(
- input_vector.get(), kScale, kVectorSize, output_vector.get());
+ input_vector_.get(), kScale, kVectorSize, output_vector_.get());
VerifyOutput(kResult);
}
#endif
}
-// Define platform independent function name for FMACBenchmark* tests.
-#if defined(ARCH_CPU_X86_FAMILY)
-#define FMAC_FUNC FMAC_SSE
-#elif defined(ARCH_CPU_ARM_FAMILY) && defined(USE_NEON)
-#define FMAC_FUNC FMAC_NEON
-#endif
-
-// Benchmark for each optimized vector_math::FMAC() method. Original benchmarks
-// were run with --vector-fmac-iterations=200000.
-TEST_F(VectorMathTest, FMACBenchmark) {
- static const int kBenchmarkIterations = BenchmarkIterations();
-
- printf("Benchmarking %d iterations:\n", kBenchmarkIterations);
-
- // Benchmark FMAC_C().
- FillTestVectors(kInputFillValue, kOutputFillValue);
- TimeTicks start = TimeTicks::HighResNow();
- for (int i = 0; i < kBenchmarkIterations; ++i) {
- vector_math::FMAC_C(
- input_vector.get(), kScale, kVectorSize, output_vector.get());
- }
- double total_time_c_ms = (TimeTicks::HighResNow() - start).InMillisecondsF();
- printf("FMAC_C took %.2fms.\n", total_time_c_ms);
-
-#if defined(FMAC_FUNC)
-#if defined(ARCH_CPU_X86_FAMILY)
- ASSERT_TRUE(base::CPU().has_sse());
-#endif
-
- // Benchmark FMAC_FUNC() with unaligned size.
- ASSERT_NE((kVectorSize - 1) % (vector_math::kRequiredAlignment /
- sizeof(float)), 0U);
- FillTestVectors(kInputFillValue, kOutputFillValue);
- start = TimeTicks::HighResNow();
- for (int j = 0; j < kBenchmarkIterations; ++j) {
- vector_math::FMAC_FUNC(
- input_vector.get(), kScale, kVectorSize - 1, output_vector.get());
- }
- double total_time_optimized_unaligned_ms =
- (TimeTicks::HighResNow() - start).InMillisecondsF();
- printf(STRINGIZE(FMAC_FUNC) " (unaligned size) took %.2fms; which is %.2fx "
- "faster than FMAC_C.\n", total_time_optimized_unaligned_ms,
- total_time_c_ms / total_time_optimized_unaligned_ms);
-
- // Benchmark FMAC_FUNC() with aligned size.
- ASSERT_EQ(kVectorSize % (vector_math::kRequiredAlignment / sizeof(float)),
- 0U);
- FillTestVectors(kInputFillValue, kOutputFillValue);
- start = TimeTicks::HighResNow();
- for (int j = 0; j < kBenchmarkIterations; ++j) {
- vector_math::FMAC_FUNC(
- input_vector.get(), kScale, kVectorSize, output_vector.get());
- }
- double total_time_optimized_aligned_ms =
- (TimeTicks::HighResNow() - start).InMillisecondsF();
- printf(STRINGIZE(FMAC_FUNC) " (aligned) took %.2fms; which is %.2fx "
- "faster than FMAC_C and %.2fx faster than "
- STRINGIZE(FMAC_FUNC) " (unaligned).\n",
- total_time_optimized_aligned_ms,
- total_time_c_ms / total_time_optimized_aligned_ms,
- total_time_optimized_unaligned_ms / total_time_optimized_aligned_ms);
-#endif
-}
-
-#undef FMAC_FUNC
-
-// Define platform independent function name for FMULBenchmark* tests.
-#if defined(ARCH_CPU_X86_FAMILY)
-#define FMUL_FUNC FMUL_SSE
-#elif defined(ARCH_CPU_ARM_FAMILY) && defined(USE_NEON)
-#define FMUL_FUNC FMUL_NEON
-#endif
-
-// Benchmark for each optimized vector_math::FMUL() method. Original benchmarks
-// were run with --vector-math-iterations=200000.
-TEST_F(VectorMathTest, FMULBenchmark) {
- static const int kBenchmarkIterations = BenchmarkIterations();
-
- printf("Benchmarking %d iterations:\n", kBenchmarkIterations);
-
- // Benchmark FMUL_C().
- FillTestVectors(kInputFillValue, kOutputFillValue);
- TimeTicks start = TimeTicks::HighResNow();
- for (int i = 0; i < kBenchmarkIterations; ++i) {
- vector_math::FMUL_C(
- input_vector.get(), kScale, kVectorSize, output_vector.get());
- }
- double total_time_c_ms = (TimeTicks::HighResNow() - start).InMillisecondsF();
- printf("FMUL_C took %.2fms.\n", total_time_c_ms);
-
-#if defined(FMUL_FUNC)
-#if defined(ARCH_CPU_X86_FAMILY)
- ASSERT_TRUE(base::CPU().has_sse());
-#endif
-
- // Benchmark FMUL_SSE() with unaligned size.
- ASSERT_NE((kVectorSize - 1) % (vector_math::kRequiredAlignment /
- sizeof(float)), 0U);
- FillTestVectors(kInputFillValue, kOutputFillValue);
- start = TimeTicks::HighResNow();
- for (int j = 0; j < kBenchmarkIterations; ++j) {
- vector_math::FMUL_FUNC(
- input_vector.get(), kScale, kVectorSize - 1, output_vector.get());
- }
- double total_time_optimized_unaligned_ms =
- (TimeTicks::HighResNow() - start).InMillisecondsF();
- printf(STRINGIZE(FMUL_FUNC) " (unaligned size) took %.2fms; which is %.2fx "
- "faster than FMUL_C.\n", total_time_optimized_unaligned_ms,
- total_time_c_ms / total_time_optimized_unaligned_ms);
-
- // Benchmark FMUL_SSE() with aligned size.
- ASSERT_EQ(kVectorSize % (vector_math::kRequiredAlignment / sizeof(float)),
- 0U);
- FillTestVectors(kInputFillValue, kOutputFillValue);
- start = TimeTicks::HighResNow();
- for (int j = 0; j < kBenchmarkIterations; ++j) {
- vector_math::FMUL_FUNC(
- input_vector.get(), kScale, kVectorSize, output_vector.get());
- }
- double total_time_optimized_aligned_ms =
- (TimeTicks::HighResNow() - start).InMillisecondsF();
- printf(STRINGIZE(FMUL_FUNC) " (aligned) took %.2fms; which is %.2fx "
- "faster than FMUL_C and %.2fx faster than "
- STRINGIZE(FMUL_FUNC) " (unaligned).\n",
- total_time_optimized_aligned_ms,
- total_time_c_ms / total_time_optimized_aligned_ms,
- total_time_optimized_unaligned_ms / total_time_optimized_aligned_ms);
-#endif
-}
-
-#undef FMUL_FUNC
-
} // namespace media