diff options
author | acolwell@chromium.org <acolwell@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-04-01 14:18:55 +0000 |
---|---|---|
committer | acolwell@chromium.org <acolwell@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-04-01 14:18:55 +0000 |
commit | b8b9d30cc9a9fb93ffa6a301bcec79ed85239c4a (patch) | |
tree | 7feb05a189dc3c5781563ecc7798cf7ee3ad6126 | |
parent | ddbd222c8dd1e08fb993b42adfc5159841755266 (diff) | |
download | chromium_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.cc | 4 | ||||
-rw-r--r-- | media/ffmpeg/ffmpeg_common.cc | 82 | ||||
-rw-r--r-- | media/ffmpeg/ffmpeg_common.h | 41 | ||||
-rw-r--r-- | media/ffmpeg/ffmpeg_common_unittest.cc | 176 | ||||
-rw-r--r-- | media/ffmpeg/ffmpeg_unittest.cc | 14 | ||||
-rw-r--r-- | media/filters/audio_file_reader.cc | 2 | ||||
-rw-r--r-- | media/filters/ffmpeg_demuxer.cc | 4 | ||||
-rw-r--r-- | media/filters/ffmpeg_video_decoder.cc | 2 | ||||
-rw-r--r-- | media/media.gyp | 1 | ||||
-rw-r--r-- | media/video/ffmpeg_video_decode_engine.cc | 2 |
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 |