summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoracolwell@chromium.org <acolwell@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-04-01 14:18:55 +0000
committeracolwell@chromium.org <acolwell@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-04-01 14:18:55 +0000
commitb8b9d30cc9a9fb93ffa6a301bcec79ed85239c4a (patch)
tree7feb05a189dc3c5781563ecc7798cf7ee3ad6126
parentddbd222c8dd1e08fb993b42adfc5159841755266 (diff)
downloadchromium_src-b8b9d30cc9a9fb93ffa6a301bcec79ed85239c4a.zip
chromium_src-b8b9d30cc9a9fb93ffa6a301bcec79ed85239c4a.tar.gz
chromium_src-b8b9d30cc9a9fb93ffa6a301bcec79ed85239c4a.tar.bz2
Create helper methods to process FFmpeg index data.
BUG=none TEST=FFmpegCommonTest Review URL: http://codereview.chromium.org/6708130 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@80153 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--media/base/mock_ffmpeg.cc4
-rw-r--r--media/ffmpeg/ffmpeg_common.cc82
-rw-r--r--media/ffmpeg/ffmpeg_common.h41
-rw-r--r--media/ffmpeg/ffmpeg_common_unittest.cc176
-rw-r--r--media/ffmpeg/ffmpeg_unittest.cc14
-rw-r--r--media/filters/audio_file_reader.cc2
-rw-r--r--media/filters/ffmpeg_demuxer.cc4
-rw-r--r--media/filters/ffmpeg_video_decoder.cc2
-rw-r--r--media/media.gyp1
-rw-r--r--media/video/ffmpeg_video_decode_engine.cc2
10 files changed, 313 insertions, 15 deletions
diff --git a/media/base/mock_ffmpeg.cc b/media/base/mock_ffmpeg.cc
index f1e28f9..c91c081 100644
--- a/media/base/mock_ffmpeg.cc
+++ b/media/base/mock_ffmpeg.cc
@@ -176,7 +176,9 @@ int64 av_rescale_q(int64 a, AVRational bq, AVRational cq) {
// implement a cheap version that's capable of overflowing.
int64 num = bq.num * cq.den;
int64 den = cq.num * bq.den;
- return a * num / den;
+
+ // Rescale a by num/den. The den / 2 is for rounding.
+ return (a * num + den / 2) / den;
}
int av_read_frame(AVFormatContext* format, AVPacket* packet) {
diff --git a/media/ffmpeg/ffmpeg_common.cc b/media/ffmpeg/ffmpeg_common.cc
index fd82936..fc59709 100644
--- a/media/ffmpeg/ffmpeg_common.cc
+++ b/media/ffmpeg/ffmpeg_common.cc
@@ -10,11 +10,17 @@ namespace media {
static const AVRational kMicrosBase = { 1, base::Time::kMicrosecondsPerSecond };
-base::TimeDelta ConvertTimestamp(const AVRational& time_base, int64 timestamp) {
+base::TimeDelta ConvertFromTimeBase(const AVRational& time_base,
+ int64 timestamp) {
int64 microseconds = av_rescale_q(timestamp, time_base, kMicrosBase);
return base::TimeDelta::FromMicroseconds(microseconds);
}
+int64 ConvertToTimeBase(const AVRational& time_base,
+ const base::TimeDelta& timestamp) {
+ return av_rescale_q(timestamp.InMicroseconds(), kMicrosBase, time_base);
+}
+
VideoCodec CodecIDToVideoCodec(CodecID codec_id) {
switch (codec_id) {
case CODEC_ID_VC1:
@@ -55,4 +61,78 @@ CodecID VideoCodecToCodecID(VideoCodec video_codec) {
return CODEC_ID_NONE;
}
+bool GetSeekTimeAfter(AVStream* stream, const base::TimeDelta& timestamp,
+ base::TimeDelta* seek_time) {
+ DCHECK(stream);
+ DCHECK(timestamp >= base::TimeDelta::FromSeconds(0));
+ DCHECK(seek_time);
+
+ // Make sure we have index data.
+ if (!stream->index_entries || stream->nb_index_entries <= 0)
+ return false;
+
+ // Search for the index entry >= the specified timestamp.
+ int64 stream_ts = ConvertToTimeBase(stream->time_base, timestamp);
+ int i = av_index_search_timestamp(stream, stream_ts, 0);
+
+ if (i < 0)
+ return false;
+
+ if (stream->index_entries[i].timestamp == static_cast<int64>(AV_NOPTS_VALUE))
+ return false;
+
+ *seek_time = ConvertFromTimeBase(stream->time_base,
+ stream->index_entries[i].timestamp);
+ return true;
+}
+
+
+bool GetStreamByteCountOverRange(AVStream* stream,
+ const base::TimeDelta& start_time,
+ const base::TimeDelta& end_time,
+ int64* bytes,
+ base::TimeDelta* range_start,
+ base::TimeDelta* range_end) {
+ DCHECK(stream);
+ DCHECK(start_time < end_time);
+ DCHECK(start_time >= base::TimeDelta::FromSeconds(0));
+ DCHECK(bytes);
+ DCHECK(range_start);
+ DCHECK(range_end);
+
+ // Make sure the stream has index data.
+ if (!stream->index_entries || stream->nb_index_entries <= 1)
+ return false;
+
+ // Search for the index entries associated with the timestamps.
+ int64 start_ts = ConvertToTimeBase(stream->time_base, start_time);
+ int64 end_ts = ConvertToTimeBase(stream->time_base, end_time);
+ int i = av_index_search_timestamp(stream, start_ts, AVSEEK_FLAG_BACKWARD);
+ int j = av_index_search_timestamp(stream, end_ts, 0);
+
+ // Make sure start & end indexes are valid.
+ if (i < 0 || j < 0)
+ return false;
+
+ // Shouldn't happen because start & end use different seek flags, but we want
+ // to know about it if they end up pointing to the same index entry.
+ DCHECK_NE(i, j);
+
+ AVIndexEntry* start_ie = &stream->index_entries[i];
+ AVIndexEntry* end_ie = &stream->index_entries[j];
+
+ // Make sure index entries have valid timestamps & position data.
+ if (start_ie->timestamp == static_cast<int64>(AV_NOPTS_VALUE) ||
+ end_ie->timestamp == static_cast<int64>(AV_NOPTS_VALUE) ||
+ start_ie->timestamp >= end_ie->timestamp ||
+ start_ie->pos >= end_ie->pos) {
+ return false;
+ }
+
+ *bytes = end_ie->pos - start_ie->pos;
+ *range_start = ConvertFromTimeBase(stream->time_base, start_ie->timestamp);
+ *range_end = ConvertFromTimeBase(stream->time_base, end_ie->timestamp);
+ return true;
+}
+
} // namespace media
diff --git a/media/ffmpeg/ffmpeg_common.h b/media/ffmpeg/ffmpeg_common.h
index ac1774a5b..dcdc2d8 100644
--- a/media/ffmpeg/ffmpeg_common.h
+++ b/media/ffmpeg/ffmpeg_common.h
@@ -50,11 +50,50 @@ class ScopedPtrAVFreePacket {
}
};
-base::TimeDelta ConvertTimestamp(const AVRational& time_base, int64 timestamp);
+// Converts an int64 timestamp in |time_base| units to a base::TimeDelta.
+// For example if |timestamp| equals 11025 and |time_base| equals {1, 44100}
+// then the return value will be a base::TimeDelta for 0.25 seconds since that
+// is how much time 11025/44100ths of a second represents.
+base::TimeDelta ConvertFromTimeBase(const AVRational& time_base,
+ int64 timestamp);
+
+// Converts a base::TimeDelta into an int64 timestamp in |time_base| units.
+// For example if |timestamp| is 0.5 seconds and |time_base| is {1, 44100}, then
+// the return value will be 22050 since that is how many 1/44100ths of a second
+// represent 0.5 seconds.
+int64 ConvertToTimeBase(const AVRational& time_base,
+ const base::TimeDelta& timestamp);
VideoCodec CodecIDToVideoCodec(CodecID codec_id);
CodecID VideoCodecToCodecID(VideoCodec video_codec);
+// Get the timestamp of the next seek point after |timestamp|.
+// Returns true if a valid seek point was found after |timestamp| and
+// |seek_time| was set. Returns false if a seek point could not be
+// found or the parameters are invalid.
+bool GetSeekTimeAfter(AVStream* stream,
+ const base::TimeDelta& timestamp,
+ base::TimeDelta* seek_time);
+
+// Get the number of bytes required to play the stream over a specified
+// time range. This is an estimate based on the available index data.
+// Returns true if input time range was valid and |bytes|, |range_start|,
+// and |range_end|, were set. Returns false if the range was invalid or we don't
+// have enough index data to make an estimate.
+//
+// |bytes| - The number of bytes in the stream for the specified range.
+// |range_start| - The start time for the range covered by |bytes|. This
+// may be different than |start_time| if the index doesn't
+// have data for that exact time. |range_start| <= |start_time|
+// |range_end| - The end time for the range covered by |bytes|. This may be
+// different than |end_time| if the index doesn't have data for
+// that exact time. |range_end| >= |end_time|
+bool GetStreamByteCountOverRange(AVStream* stream,
+ const base::TimeDelta& start_time,
+ const base::TimeDelta& end_time,
+ int64* bytes,
+ base::TimeDelta* range_start,
+ base::TimeDelta* range_end);
} // namespace media
#endif // MEDIA_FFMPEG_FFMPEG_COMMON_H_
diff --git a/media/ffmpeg/ffmpeg_common_unittest.cc b/media/ffmpeg/ffmpeg_common_unittest.cc
new file mode 100644
index 0000000..f045f76
--- /dev/null
+++ b/media/ffmpeg/ffmpeg_common_unittest.cc
@@ -0,0 +1,176 @@
+// Copyright (c) 2011 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/file_path.h"
+#include "base/logging.h"
+#include "base/path_service.h"
+#include "media/base/media.h"
+#include "media/ffmpeg/ffmpeg_common.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::TimeDelta;
+
+namespace media {
+
+static AVIndexEntry kIndexEntries[] = {
+ // pos, timestamp, flags, size, min_distance
+ { 0, 0, AVINDEX_KEYFRAME, 0, 0 },
+ { 2000, 1000, AVINDEX_KEYFRAME, 0, 0 },
+ { 3000, 2000, 0, 0, 0 },
+ { 5000, 3000, AVINDEX_KEYFRAME, 0, 0 },
+ { 6000, 4000, 0, 0, 0 },
+ { 8000, 5000, AVINDEX_KEYFRAME, 0, 0 },
+ { 9000, 6000, AVINDEX_KEYFRAME, 0, 0 },
+ { 11500, 7000, AVINDEX_KEYFRAME, 0, 0 },
+};
+
+static const AVRational kTimeBase = { 1, 1000 };
+
+class FFmpegCommonTest : public testing::Test {
+ public:
+ FFmpegCommonTest();
+ virtual ~FFmpegCommonTest();
+
+ protected:
+ AVStream stream_;
+
+ DISALLOW_COPY_AND_ASSIGN(FFmpegCommonTest);
+};
+
+static bool InitFFmpeg() {
+ static bool initialized = false;
+ if (initialized) {
+ return true;
+ }
+ FilePath path;
+ PathService::Get(base::DIR_MODULE, &path);
+ return media::InitializeMediaLibrary(path);
+}
+
+FFmpegCommonTest::FFmpegCommonTest() {
+ CHECK(InitFFmpeg());
+ stream_.time_base = kTimeBase;
+ stream_.index_entries = kIndexEntries;
+ stream_.index_entries_allocated_size = sizeof(kIndexEntries);
+ stream_.nb_index_entries = arraysize(kIndexEntries);
+}
+
+FFmpegCommonTest::~FFmpegCommonTest() {}
+
+TEST_F(FFmpegCommonTest, TestTimeBaseConversions) {
+ int64 test_data[][5] = {
+ {1, 2, 1, 500000, 1 },
+ {1, 3, 1, 333333, 1 },
+ {1, 3, 2, 666667, 2 },
+ };
+
+ for (size_t i = 0; i < arraysize(test_data); ++i) {
+ SCOPED_TRACE(i);
+
+ AVRational time_base;
+ time_base.num = static_cast<int>(test_data[i][0]);
+ time_base.den = static_cast<int>(test_data[i][1]);
+
+ TimeDelta time_delta = ConvertFromTimeBase(time_base, test_data[i][2]);
+
+ EXPECT_EQ(time_delta.InMicroseconds(), test_data[i][3]);
+ EXPECT_EQ(ConvertToTimeBase(time_base, time_delta), test_data[i][4]);
+ }
+}
+
+TEST_F(FFmpegCommonTest, GetSeekTimeAfterSuccess) {
+ int64 test_data[][2] = {
+ {5000, 5000}, // Timestamp is a keyframe seek point.
+ {2400, 3000}, // Timestamp is just before a keyframe seek point.
+ {2000, 3000}, // Timestamp is a non-keyframe entry.
+ {1800, 3000}, // Timestamp is just before a non-keyframe entry.
+ };
+
+ for (size_t i = 0; i < arraysize(test_data); ++i) {
+ SCOPED_TRACE(i);
+
+ TimeDelta seek_point;
+ TimeDelta timestamp = TimeDelta::FromMilliseconds(test_data[i][0]);
+ ASSERT_TRUE(GetSeekTimeAfter(&stream_, timestamp, &seek_point));
+ EXPECT_EQ(seek_point.InMilliseconds(), test_data[i][1]);
+ }
+}
+
+TEST_F(FFmpegCommonTest, GetSeekTimeAfterFailures) {
+ TimeDelta seek_point;
+
+ // Timestamp after last index entry.
+ ASSERT_FALSE(GetSeekTimeAfter(&stream_, TimeDelta::FromSeconds(8),
+ &seek_point));
+
+ AVStream stream;
+ memcpy(&stream, &stream_, sizeof(stream));
+ stream.index_entries = NULL;
+ // Stream w/o index data.
+ ASSERT_FALSE(GetSeekTimeAfter(&stream, TimeDelta::FromSeconds(1),
+ &seek_point));
+}
+
+TEST_F(FFmpegCommonTest, GetStreamByteCountOverRangeSuccess) {
+ int64 test_data[][5] = {
+ { 0, 1000, 2000, 0, 1000}, // Bytes between two adjacent keyframe
+ // entries.
+ {1000, 2000, 3000, 1000, 3000}, // Bytes between two keyframe entries with a
+ // non-keyframe entry between them.
+ {5500, 5600, 1000, 5000, 6000}, // Bytes for a range that starts and ends
+ // between two index entries.
+ {4500, 7000, 6500, 3000, 7000}, // Bytes for a range that ends on the last
+ // seek point.
+ };
+
+
+ for (size_t i = 0; i < arraysize(test_data); ++i) {
+ SCOPED_TRACE(i);
+
+ TimeDelta start_time = TimeDelta::FromMilliseconds(test_data[i][0]);
+ TimeDelta end_time = TimeDelta::FromMilliseconds(test_data[i][1]);
+ int64 bytes;
+ TimeDelta range_start;
+ TimeDelta range_end;
+
+ ASSERT_TRUE(GetStreamByteCountOverRange(&stream_, start_time, end_time,
+ &bytes, &range_start, &range_end));
+ EXPECT_EQ(bytes, test_data[i][2]);
+ EXPECT_EQ(range_start.InMilliseconds(), test_data[i][3]);
+ EXPECT_EQ(range_end.InMilliseconds(), test_data[i][4]);
+ }
+}
+
+TEST_F(FFmpegCommonTest, GetStreamByteCountOverRangeFailures) {
+
+ int64 test_data[][2] = {
+ {7000, 7600}, // Test a range that starts at the last seek point.
+ {7500, 7600}, // Test a range after the last seek point
+ };
+
+ int64 bytes;
+ TimeDelta range_start;
+ TimeDelta range_end;
+
+ for (size_t i = 0; i < arraysize(test_data); ++i) {
+ SCOPED_TRACE(i);
+
+ TimeDelta start_time = TimeDelta::FromMilliseconds(test_data[i][0]);
+ TimeDelta end_time = TimeDelta::FromMilliseconds(test_data[i][1]);
+
+ EXPECT_FALSE(GetStreamByteCountOverRange(&stream_, start_time, end_time,
+ &bytes, &range_start, &range_end));
+ }
+
+ AVStream stream;
+ memcpy(&stream, &stream_, sizeof(stream));
+ stream.index_entries = NULL;
+ // Stream w/o index data.
+ ASSERT_FALSE(GetStreamByteCountOverRange(&stream,
+ TimeDelta::FromSeconds(1),
+ TimeDelta::FromSeconds(2),
+ &bytes, &range_start, &range_end));
+}
+
+} // namespace media
diff --git a/media/ffmpeg/ffmpeg_unittest.cc b/media/ffmpeg/ffmpeg_unittest.cc
index 140ea48..86f28bc 100644
--- a/media/ffmpeg/ffmpeg_unittest.cc
+++ b/media/ffmpeg/ffmpeg_unittest.cc
@@ -143,7 +143,7 @@ class FFmpegTest : public testing::TestWithParam<const char*> {
// Determine duration by picking max stream duration.
for (unsigned int i = 0; i < av_format_context_->nb_streams; ++i) {
AVStream* av_stream = av_format_context_->streams[i];
- int64 duration = ConvertTimestamp(av_stream->time_base,
+ int64 duration = ConvertFromTimeBase(av_stream->time_base,
av_stream->duration).InMicroseconds();
duration_ = std::max(duration_, duration);
}
@@ -151,7 +151,7 @@ class FFmpegTest : public testing::TestWithParam<const char*> {
// Final check to see if the container itself specifies a duration.
AVRational av_time_base = {1, AV_TIME_BASE};
int64 duration =
- ConvertTimestamp(av_time_base,
+ ConvertFromTimeBase(av_time_base,
av_format_context_->duration).InMicroseconds();
duration_ = std::max(duration_, duration);
}
@@ -215,12 +215,12 @@ class FFmpegTest : public testing::TestWithParam<const char*> {
int64 packet_time = AV_NOPTS_VALUE;
if (stream_index == audio_stream_index_) {
packet_time =
- ConvertTimestamp(av_audio_stream()->time_base, packet->pts)
+ ConvertFromTimeBase(av_audio_stream()->time_base, packet->pts)
.InMicroseconds();
audio_packets_.push(packet.release());
} else if (stream_index == video_stream_index_) {
packet_time =
- ConvertTimestamp(av_video_stream()->time_base, packet->pts)
+ ConvertFromTimeBase(av_video_stream()->time_base, packet->pts)
.InMicroseconds();
video_packets_.push(packet.release());
} else {
@@ -285,7 +285,7 @@ class FFmpegTest : public testing::TestWithParam<const char*> {
decoded_audio_time_ += decoded_audio_duration_;
} else {
decoded_audio_time_ =
- ConvertTimestamp(av_audio_stream()->time_base, packet.pts)
+ ConvertFromTimeBase(av_audio_stream()->time_base, packet.pts)
.InMicroseconds();
}
return true;
@@ -333,11 +333,11 @@ class FFmpegTest : public testing::TestWithParam<const char*> {
doubled_time_base.den *= 2;
decoded_video_time_ =
- ConvertTimestamp(av_video_stream()->time_base,
+ ConvertFromTimeBase(av_video_stream()->time_base,
video_buffer_->reordered_opaque)
.InMicroseconds();
decoded_video_duration_ =
- ConvertTimestamp(doubled_time_base,
+ ConvertFromTimeBase(doubled_time_base,
2 + video_buffer_->repeat_pict)
.InMicroseconds();
return true;
diff --git a/media/filters/audio_file_reader.cc b/media/filters/audio_file_reader.cc
index 5e01d27..a9304ca 100644
--- a/media/filters/audio_file_reader.cc
+++ b/media/filters/audio_file_reader.cc
@@ -36,7 +36,7 @@ int AudioFileReader::sample_rate() const {
base::TimeDelta AudioFileReader::duration() const {
const AVRational av_time_base = {1, AV_TIME_BASE};
- return ConvertTimestamp(av_time_base, format_context_->duration);
+ return ConvertFromTimeBase(av_time_base, format_context_->duration);
}
int64 AudioFileReader::number_of_frames() const {
diff --git a/media/filters/ffmpeg_demuxer.cc b/media/filters/ffmpeg_demuxer.cc
index ce7bbb2..15df542 100644
--- a/media/filters/ffmpeg_demuxer.cc
+++ b/media/filters/ffmpeg_demuxer.cc
@@ -229,7 +229,7 @@ base::TimeDelta FFmpegDemuxerStream::ConvertStreamTimestamp(
if (timestamp == static_cast<int64>(AV_NOPTS_VALUE))
return kNoTimestamp;
- return ConvertTimestamp(time_base, timestamp);
+ return ConvertFromTimeBase(time_base, timestamp);
}
//
@@ -482,7 +482,7 @@ void FFmpegDemuxer::InitializeTask(DataSource* data_source,
const AVRational av_time_base = {1, AV_TIME_BASE};
max_duration =
std::max(max_duration,
- ConvertTimestamp(av_time_base, format_context_->duration));
+ ConvertFromTimeBase(av_time_base, format_context_->duration));
} else {
// If the duration is not a valid value. Assume that this is a live stream
// and we set duration to the maximum int64 number to represent infinity.
diff --git a/media/filters/ffmpeg_video_decoder.cc b/media/filters/ffmpeg_video_decoder.cc
index 9354563..fd5f15f 100644
--- a/media/filters/ffmpeg_video_decoder.cc
+++ b/media/filters/ffmpeg_video_decoder.cc
@@ -399,7 +399,7 @@ FFmpegVideoDecoder::TimeTuple FFmpegVideoDecoder::FindPtsAndDuration(
pts.duration = duration;
} else {
// Otherwise assume a normal frame duration.
- pts.duration = ConvertTimestamp(time_base, 1);
+ pts.duration = ConvertFromTimeBase(time_base, 1);
}
return pts;
diff --git a/media/media.gyp b/media/media.gyp
index 0796fb8..4f36789 100644
--- a/media/media.gyp
+++ b/media/media.gyp
@@ -378,6 +378,7 @@
'base/state_matrix_unittest.cc',
'base/video_frame_unittest.cc',
'base/yuv_convert_unittest.cc',
+ 'ffmpeg/ffmpeg_common_unittest.cc',
'filters/adaptive_demuxer_unittest.cc',
'filters/audio_renderer_algorithm_ola_unittest.cc',
'filters/audio_renderer_base_unittest.cc',
diff --git a/media/video/ffmpeg_video_decode_engine.cc b/media/video/ffmpeg_video_decode_engine.cc
index 18397e5..f9a1996 100644
--- a/media/video/ffmpeg_video_decode_engine.cc
+++ b/media/video/ffmpeg_video_decode_engine.cc
@@ -295,7 +295,7 @@ void FFmpegVideoDecodeEngine::DecodeFrame(scoped_refptr<Buffer> buffer) {
base::TimeDelta timestamp =
base::TimeDelta::FromMicroseconds(av_frame_->reordered_opaque);
base::TimeDelta duration =
- ConvertTimestamp(doubled_time_base, 2 + av_frame_->repeat_pict);
+ ConvertFromTimeBase(doubled_time_base, 2 + av_frame_->repeat_pict);
if (!direct_rendering_) {
// Available frame is guaranteed, because we issue as much reads as