| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 // ffmpeg_unittests verify that the parts of the FFmpeg API that Chromium uses | |
| 6 // function as advertised for each media format that Chromium supports. This | |
| 7 // mostly includes stuff like reporting proper timestamps, seeking to | |
| 8 // keyframes, and supporting certain features like reordered_opaque. | |
| 9 // | |
| 10 | |
| 11 #include <limits> | |
| 12 #include <queue> | |
| 13 | |
| 14 #include "base/base_paths.h" | |
| 15 #include "base/files/file_path.h" | |
| 16 #include "base/files/file_util.h" | |
| 17 #include "base/files/memory_mapped_file.h" | |
| 18 #include "base/memory/scoped_ptr.h" | |
| 19 #include "base/path_service.h" | |
| 20 #include "base/strings/string_util.h" | |
| 21 #include "base/test/perf_test_suite.h" | |
| 22 #include "base/test/perf_time_logger.h" | |
| 23 #include "media/base/media.h" | |
| 24 #include "media/ffmpeg/ffmpeg_common.h" | |
| 25 #include "media/filters/ffmpeg_glue.h" | |
| 26 #include "media/filters/in_memory_url_protocol.h" | |
| 27 #include "testing/gtest/include/gtest/gtest.h" | |
| 28 | |
| 29 int main(int argc, char** argv) { | |
| 30 return base::PerfTestSuite(argc, argv).Run(); | |
| 31 } | |
| 32 | |
| 33 namespace media { | |
| 34 | |
| 35 // Mirror setting in ffmpeg_video_decoder. | |
| 36 static const int kDecodeThreads = 2; | |
| 37 | |
| 38 class AVPacketQueue { | |
| 39 public: | |
| 40 AVPacketQueue() { | |
| 41 } | |
| 42 | |
| 43 ~AVPacketQueue() { | |
| 44 flush(); | |
| 45 } | |
| 46 | |
| 47 bool empty() { | |
| 48 return packets_.empty(); | |
| 49 } | |
| 50 | |
| 51 AVPacket* peek() { | |
| 52 return packets_.front(); | |
| 53 } | |
| 54 | |
| 55 void pop() { | |
| 56 AVPacket* packet = packets_.front(); | |
| 57 packets_.pop(); | |
| 58 av_free_packet(packet); | |
| 59 delete packet; | |
| 60 } | |
| 61 | |
| 62 void push(AVPacket* packet) { | |
| 63 av_dup_packet(packet); | |
| 64 packets_.push(packet); | |
| 65 } | |
| 66 | |
| 67 void flush() { | |
| 68 while (!empty()) { | |
| 69 pop(); | |
| 70 } | |
| 71 } | |
| 72 | |
| 73 private: | |
| 74 std::queue<AVPacket*> packets_; | |
| 75 | |
| 76 DISALLOW_COPY_AND_ASSIGN(AVPacketQueue); | |
| 77 }; | |
| 78 | |
| 79 // TODO(dalecurtis): We should really just use PipelineIntegrationTests instead | |
| 80 // of a one-off step decoder so we're exercising the real pipeline. | |
| 81 class FFmpegTest : public testing::TestWithParam<const char*> { | |
| 82 protected: | |
| 83 FFmpegTest() | |
| 84 : av_format_context_(NULL), | |
| 85 audio_stream_index_(-1), | |
| 86 video_stream_index_(-1), | |
| 87 decoded_audio_time_(AV_NOPTS_VALUE), | |
| 88 decoded_audio_duration_(AV_NOPTS_VALUE), | |
| 89 decoded_video_time_(AV_NOPTS_VALUE), | |
| 90 decoded_video_duration_(AV_NOPTS_VALUE), | |
| 91 duration_(AV_NOPTS_VALUE) { | |
| 92 InitializeFFmpeg(); | |
| 93 | |
| 94 audio_buffer_.reset(av_frame_alloc()); | |
| 95 video_buffer_.reset(av_frame_alloc()); | |
| 96 } | |
| 97 | |
| 98 virtual ~FFmpegTest() { | |
| 99 } | |
| 100 | |
| 101 void OpenAndReadFile(const std::string& name) { | |
| 102 OpenFile(name); | |
| 103 OpenCodecs(); | |
| 104 ReadRemainingFile(); | |
| 105 } | |
| 106 | |
| 107 void OpenFile(const std::string& name) { | |
| 108 base::FilePath path; | |
| 109 PathService::Get(base::DIR_SOURCE_ROOT, &path); | |
| 110 path = path.AppendASCII("media") | |
| 111 .AppendASCII("test") | |
| 112 .AppendASCII("data") | |
| 113 .AppendASCII("content") | |
| 114 .AppendASCII(name.c_str()); | |
| 115 EXPECT_TRUE(base::PathExists(path)); | |
| 116 | |
| 117 CHECK(file_data_.Initialize(path)); | |
| 118 protocol_.reset(new InMemoryUrlProtocol( | |
| 119 file_data_.data(), file_data_.length(), false)); | |
| 120 glue_.reset(new FFmpegGlue(protocol_.get())); | |
| 121 | |
| 122 ASSERT_TRUE(glue_->OpenContext()) << "Could not open " << path.value(); | |
| 123 av_format_context_ = glue_->format_context(); | |
| 124 ASSERT_LE(0, avformat_find_stream_info(av_format_context_, NULL)) | |
| 125 << "Could not find stream information for " << path.value(); | |
| 126 | |
| 127 // Determine duration by picking max stream duration. | |
| 128 for (unsigned int i = 0; i < av_format_context_->nb_streams; ++i) { | |
| 129 AVStream* av_stream = av_format_context_->streams[i]; | |
| 130 int64 duration = ConvertFromTimeBase( | |
| 131 av_stream->time_base, av_stream->duration).InMicroseconds(); | |
| 132 duration_ = std::max(duration_, duration); | |
| 133 } | |
| 134 | |
| 135 // Final check to see if the container itself specifies a duration. | |
| 136 AVRational av_time_base = {1, AV_TIME_BASE}; | |
| 137 int64 duration = | |
| 138 ConvertFromTimeBase(av_time_base, | |
| 139 av_format_context_->duration).InMicroseconds(); | |
| 140 duration_ = std::max(duration_, duration); | |
| 141 } | |
| 142 | |
| 143 void OpenCodecs() { | |
| 144 for (unsigned int i = 0; i < av_format_context_->nb_streams; ++i) { | |
| 145 AVStream* av_stream = av_format_context_->streams[i]; | |
| 146 AVCodecContext* av_codec_context = av_stream->codec; | |
| 147 AVCodec* av_codec = avcodec_find_decoder(av_codec_context->codec_id); | |
| 148 | |
| 149 EXPECT_TRUE(av_codec) | |
| 150 << "Could not find AVCodec with CodecID " | |
| 151 << av_codec_context->codec_id; | |
| 152 | |
| 153 av_codec_context->error_concealment = FF_EC_GUESS_MVS | FF_EC_DEBLOCK; | |
| 154 av_codec_context->thread_count = kDecodeThreads; | |
| 155 | |
| 156 EXPECT_EQ(0, avcodec_open2(av_codec_context, av_codec, NULL)) | |
| 157 << "Could not open AVCodecContext with CodecID " | |
| 158 << av_codec_context->codec_id; | |
| 159 | |
| 160 if (av_codec->type == AVMEDIA_TYPE_AUDIO) { | |
| 161 EXPECT_EQ(-1, audio_stream_index_) << "Found multiple audio streams."; | |
| 162 audio_stream_index_ = static_cast<int>(i); | |
| 163 } else if (av_codec->type == AVMEDIA_TYPE_VIDEO) { | |
| 164 EXPECT_EQ(-1, video_stream_index_) << "Found multiple video streams."; | |
| 165 video_stream_index_ = static_cast<int>(i); | |
| 166 } else { | |
| 167 ADD_FAILURE() << "Found unknown stream type."; | |
| 168 } | |
| 169 } | |
| 170 } | |
| 171 | |
| 172 void Flush() { | |
| 173 if (has_audio()) { | |
| 174 audio_packets_.flush(); | |
| 175 avcodec_flush_buffers(av_audio_context()); | |
| 176 } | |
| 177 if (has_video()) { | |
| 178 video_packets_.flush(); | |
| 179 avcodec_flush_buffers(av_video_context()); | |
| 180 } | |
| 181 } | |
| 182 | |
| 183 void ReadUntil(int64 time) { | |
| 184 while (true) { | |
| 185 scoped_ptr<AVPacket> packet(new AVPacket()); | |
| 186 if (av_read_frame(av_format_context_, packet.get()) < 0) { | |
| 187 break; | |
| 188 } | |
| 189 | |
| 190 int stream_index = static_cast<int>(packet->stream_index); | |
| 191 int64 packet_time = AV_NOPTS_VALUE; | |
| 192 if (stream_index == audio_stream_index_) { | |
| 193 packet_time = | |
| 194 ConvertFromTimeBase(av_audio_stream()->time_base, packet->pts) | |
| 195 .InMicroseconds(); | |
| 196 audio_packets_.push(packet.release()); | |
| 197 } else if (stream_index == video_stream_index_) { | |
| 198 packet_time = | |
| 199 ConvertFromTimeBase(av_video_stream()->time_base, packet->pts) | |
| 200 .InMicroseconds(); | |
| 201 video_packets_.push(packet.release()); | |
| 202 } else { | |
| 203 ADD_FAILURE() << "Found packet that belongs to unknown stream."; | |
| 204 } | |
| 205 | |
| 206 if (packet_time > time) { | |
| 207 break; | |
| 208 } | |
| 209 } | |
| 210 } | |
| 211 | |
| 212 void ReadRemainingFile() { | |
| 213 ReadUntil(std::numeric_limits<int64>::max()); | |
| 214 } | |
| 215 | |
| 216 bool StepDecodeAudio() { | |
| 217 EXPECT_TRUE(has_audio()); | |
| 218 if (!has_audio() || audio_packets_.empty()) { | |
| 219 return false; | |
| 220 } | |
| 221 | |
| 222 // Decode until output is produced, end of stream, or error. | |
| 223 while (true) { | |
| 224 int result = 0; | |
| 225 int got_audio = 0; | |
| 226 bool end_of_stream = false; | |
| 227 | |
| 228 AVPacket packet; | |
| 229 if (audio_packets_.empty()) { | |
| 230 av_init_packet(&packet); | |
| 231 end_of_stream = true; | |
| 232 } else { | |
| 233 memcpy(&packet, audio_packets_.peek(), sizeof(packet)); | |
| 234 } | |
| 235 | |
| 236 av_frame_unref(audio_buffer_.get()); | |
| 237 result = avcodec_decode_audio4(av_audio_context(), audio_buffer_.get(), | |
| 238 &got_audio, &packet); | |
| 239 if (!audio_packets_.empty()) { | |
| 240 audio_packets_.pop(); | |
| 241 } | |
| 242 | |
| 243 EXPECT_GE(result, 0) << "Audio decode error."; | |
| 244 if (result < 0 || (got_audio == 0 && end_of_stream)) { | |
| 245 return false; | |
| 246 } | |
| 247 | |
| 248 if (result > 0) { | |
| 249 double microseconds = 1.0L * audio_buffer_->nb_samples / | |
| 250 av_audio_context()->sample_rate * | |
| 251 base::Time::kMicrosecondsPerSecond; | |
| 252 decoded_audio_duration_ = static_cast<int64>(microseconds); | |
| 253 | |
| 254 if (packet.pts == static_cast<int64>(AV_NOPTS_VALUE)) { | |
| 255 EXPECT_NE(decoded_audio_time_, static_cast<int64>(AV_NOPTS_VALUE)) | |
| 256 << "We never received an initial timestamped audio packet! " | |
| 257 << "Looks like there's a seeking/parsing bug in FFmpeg."; | |
| 258 decoded_audio_time_ += decoded_audio_duration_; | |
| 259 } else { | |
| 260 decoded_audio_time_ = | |
| 261 ConvertFromTimeBase(av_audio_stream()->time_base, packet.pts) | |
| 262 .InMicroseconds(); | |
| 263 } | |
| 264 return true; | |
| 265 } | |
| 266 } | |
| 267 return true; | |
| 268 } | |
| 269 | |
| 270 bool StepDecodeVideo() { | |
| 271 EXPECT_TRUE(has_video()); | |
| 272 if (!has_video() || video_packets_.empty()) { | |
| 273 return false; | |
| 274 } | |
| 275 | |
| 276 // Decode until output is produced, end of stream, or error. | |
| 277 while (true) { | |
| 278 int result = 0; | |
| 279 int got_picture = 0; | |
| 280 bool end_of_stream = false; | |
| 281 | |
| 282 AVPacket packet; | |
| 283 if (video_packets_.empty()) { | |
| 284 av_init_packet(&packet); | |
| 285 end_of_stream = true; | |
| 286 } else { | |
| 287 memcpy(&packet, video_packets_.peek(), sizeof(packet)); | |
| 288 } | |
| 289 | |
| 290 av_frame_unref(video_buffer_.get()); | |
| 291 av_video_context()->reordered_opaque = packet.pts; | |
| 292 result = avcodec_decode_video2(av_video_context(), video_buffer_.get(), | |
| 293 &got_picture, &packet); | |
| 294 if (!video_packets_.empty()) { | |
| 295 video_packets_.pop(); | |
| 296 } | |
| 297 | |
| 298 EXPECT_GE(result, 0) << "Video decode error."; | |
| 299 if (result < 0 || (got_picture == 0 && end_of_stream)) { | |
| 300 return false; | |
| 301 } | |
| 302 | |
| 303 if (got_picture) { | |
| 304 AVRational doubled_time_base; | |
| 305 doubled_time_base.den = av_video_stream()->r_frame_rate.num; | |
| 306 doubled_time_base.num = av_video_stream()->r_frame_rate.den; | |
| 307 doubled_time_base.den *= 2; | |
| 308 | |
| 309 decoded_video_time_ = | |
| 310 ConvertFromTimeBase(av_video_stream()->time_base, | |
| 311 video_buffer_->reordered_opaque) | |
| 312 .InMicroseconds(); | |
| 313 decoded_video_duration_ = | |
| 314 ConvertFromTimeBase(doubled_time_base, | |
| 315 2 + video_buffer_->repeat_pict) | |
| 316 .InMicroseconds(); | |
| 317 return true; | |
| 318 } | |
| 319 } | |
| 320 } | |
| 321 | |
| 322 void DecodeRemainingAudio() { | |
| 323 while (StepDecodeAudio()) {} | |
| 324 } | |
| 325 | |
| 326 void DecodeRemainingVideo() { | |
| 327 while (StepDecodeVideo()) {} | |
| 328 } | |
| 329 | |
| 330 void SeekTo(double position) { | |
| 331 int64 seek_time = | |
| 332 static_cast<int64>(position * base::Time::kMicrosecondsPerSecond); | |
| 333 int flags = AVSEEK_FLAG_BACKWARD; | |
| 334 | |
| 335 // Passing -1 as our stream index lets FFmpeg pick a default stream. | |
| 336 // FFmpeg will attempt to use the lowest-index video stream, if present, | |
| 337 // followed by the lowest-index audio stream. | |
| 338 EXPECT_GE(0, av_seek_frame(av_format_context_, -1, seek_time, flags)) | |
| 339 << "Failed to seek to position " << position; | |
| 340 Flush(); | |
| 341 } | |
| 342 | |
| 343 bool has_audio() { return audio_stream_index_ >= 0; } | |
| 344 bool has_video() { return video_stream_index_ >= 0; } | |
| 345 int64 decoded_audio_time() { return decoded_audio_time_; } | |
| 346 int64 decoded_audio_duration() { return decoded_audio_duration_; } | |
| 347 int64 decoded_video_time() { return decoded_video_time_; } | |
| 348 int64 decoded_video_duration() { return decoded_video_duration_; } | |
| 349 int64 duration() { return duration_; } | |
| 350 | |
| 351 AVStream* av_audio_stream() { | |
| 352 return av_format_context_->streams[audio_stream_index_]; | |
| 353 } | |
| 354 AVStream* av_video_stream() { | |
| 355 return av_format_context_->streams[video_stream_index_]; | |
| 356 } | |
| 357 AVCodecContext* av_audio_context() { | |
| 358 return av_audio_stream()->codec; | |
| 359 } | |
| 360 AVCodecContext* av_video_context() { | |
| 361 return av_video_stream()->codec; | |
| 362 } | |
| 363 | |
| 364 private: | |
| 365 void InitializeFFmpeg() { | |
| 366 static bool initialized = false; | |
| 367 if (initialized) { | |
| 368 return; | |
| 369 } | |
| 370 | |
| 371 base::FilePath path; | |
| 372 PathService::Get(base::DIR_MODULE, &path); | |
| 373 EXPECT_TRUE(InitializeMediaLibrary(path)) | |
| 374 << "Could not initialize media library."; | |
| 375 | |
| 376 initialized = true; | |
| 377 } | |
| 378 | |
| 379 AVFormatContext* av_format_context_; | |
| 380 int audio_stream_index_; | |
| 381 int video_stream_index_; | |
| 382 AVPacketQueue audio_packets_; | |
| 383 AVPacketQueue video_packets_; | |
| 384 | |
| 385 scoped_ptr<AVFrame, media::ScopedPtrAVFreeFrame> audio_buffer_; | |
| 386 scoped_ptr<AVFrame, media::ScopedPtrAVFreeFrame> video_buffer_; | |
| 387 | |
| 388 int64 decoded_audio_time_; | |
| 389 int64 decoded_audio_duration_; | |
| 390 int64 decoded_video_time_; | |
| 391 int64 decoded_video_duration_; | |
| 392 int64 duration_; | |
| 393 | |
| 394 base::MemoryMappedFile file_data_; | |
| 395 scoped_ptr<InMemoryUrlProtocol> protocol_; | |
| 396 scoped_ptr<FFmpegGlue> glue_; | |
| 397 | |
| 398 DISALLOW_COPY_AND_ASSIGN(FFmpegTest); | |
| 399 }; | |
| 400 | |
| 401 #define FFMPEG_TEST_CASE(name, extension) \ | |
| 402 INSTANTIATE_TEST_CASE_P(name##_##extension, FFmpegTest, \ | |
| 403 testing::Values(#name "." #extension)); | |
| 404 | |
| 405 // Covers all our basic formats. | |
| 406 FFMPEG_TEST_CASE(sync0, mp4); | |
| 407 FFMPEG_TEST_CASE(sync0, ogv); | |
| 408 FFMPEG_TEST_CASE(sync0, webm); | |
| 409 FFMPEG_TEST_CASE(sync1, m4a); | |
| 410 FFMPEG_TEST_CASE(sync1, mp3); | |
| 411 FFMPEG_TEST_CASE(sync1, mp4); | |
| 412 FFMPEG_TEST_CASE(sync1, ogg); | |
| 413 FFMPEG_TEST_CASE(sync1, ogv); | |
| 414 FFMPEG_TEST_CASE(sync1, webm); | |
| 415 FFMPEG_TEST_CASE(sync2, m4a); | |
| 416 FFMPEG_TEST_CASE(sync2, mp3); | |
| 417 FFMPEG_TEST_CASE(sync2, mp4); | |
| 418 FFMPEG_TEST_CASE(sync2, ogg); | |
| 419 FFMPEG_TEST_CASE(sync2, ogv); | |
| 420 FFMPEG_TEST_CASE(sync2, webm); | |
| 421 | |
| 422 // Covers our LayoutTest file. | |
| 423 FFMPEG_TEST_CASE(counting, ogv); | |
| 424 | |
| 425 TEST_P(FFmpegTest, Perf) { | |
| 426 { | |
| 427 base::PerfTimeLogger timer("Opening file"); | |
| 428 OpenFile(GetParam()); | |
| 429 } | |
| 430 { | |
| 431 base::PerfTimeLogger timer("Opening codecs"); | |
| 432 OpenCodecs(); | |
| 433 } | |
| 434 { | |
| 435 base::PerfTimeLogger timer("Reading file"); | |
| 436 ReadRemainingFile(); | |
| 437 } | |
| 438 if (has_audio()) { | |
| 439 base::PerfTimeLogger timer("Decoding audio"); | |
| 440 DecodeRemainingAudio(); | |
| 441 } | |
| 442 if (has_video()) { | |
| 443 base::PerfTimeLogger timer("Decoding video"); | |
| 444 DecodeRemainingVideo(); | |
| 445 } | |
| 446 { | |
| 447 base::PerfTimeLogger timer("Seeking to zero"); | |
| 448 SeekTo(0); | |
| 449 } | |
| 450 } | |
| 451 | |
| 452 TEST_P(FFmpegTest, Loop_Audio) { | |
| 453 OpenAndReadFile(GetParam()); | |
| 454 if (!has_audio()) { | |
| 455 return; | |
| 456 } | |
| 457 | |
| 458 const int kSteps = 4; | |
| 459 std::vector<int64> expected_timestamps_; | |
| 460 for (int i = 0; i < kSteps; ++i) { | |
| 461 EXPECT_TRUE(StepDecodeAudio()); | |
| 462 expected_timestamps_.push_back(decoded_audio_time()); | |
| 463 } | |
| 464 | |
| 465 SeekTo(0); | |
| 466 ReadRemainingFile(); | |
| 467 | |
| 468 for (int i = 0; i < kSteps; ++i) { | |
| 469 EXPECT_TRUE(StepDecodeAudio()); | |
| 470 EXPECT_EQ(expected_timestamps_[i], decoded_audio_time()) | |
| 471 << "Frame " << i << " had a mismatched timestamp."; | |
| 472 } | |
| 473 } | |
| 474 | |
| 475 TEST_P(FFmpegTest, Loop_Video) { | |
| 476 OpenAndReadFile(GetParam()); | |
| 477 if (!has_video()) { | |
| 478 return; | |
| 479 } | |
| 480 | |
| 481 const int kSteps = 4; | |
| 482 std::vector<int64> expected_timestamps_; | |
| 483 for (int i = 0; i < kSteps; ++i) { | |
| 484 EXPECT_TRUE(StepDecodeVideo()); | |
| 485 expected_timestamps_.push_back(decoded_video_time()); | |
| 486 } | |
| 487 | |
| 488 SeekTo(0); | |
| 489 ReadRemainingFile(); | |
| 490 | |
| 491 for (int i = 0; i < kSteps; ++i) { | |
| 492 EXPECT_TRUE(StepDecodeVideo()); | |
| 493 EXPECT_EQ(expected_timestamps_[i], decoded_video_time()) | |
| 494 << "Frame " << i << " had a mismatched timestamp."; | |
| 495 } | |
| 496 } | |
| 497 | |
| 498 TEST_P(FFmpegTest, Seek_Audio) { | |
| 499 OpenAndReadFile(GetParam()); | |
| 500 if (!has_audio() && duration() >= 0.5) { | |
| 501 return; | |
| 502 } | |
| 503 | |
| 504 SeekTo(duration() - 0.5); | |
| 505 ReadRemainingFile(); | |
| 506 | |
| 507 EXPECT_TRUE(StepDecodeAudio()); | |
| 508 EXPECT_NE(static_cast<int64>(AV_NOPTS_VALUE), decoded_audio_time()); | |
| 509 } | |
| 510 | |
| 511 TEST_P(FFmpegTest, Seek_Video) { | |
| 512 OpenAndReadFile(GetParam()); | |
| 513 if (!has_video() && duration() >= 0.5) { | |
| 514 return; | |
| 515 } | |
| 516 | |
| 517 SeekTo(duration() - 0.5); | |
| 518 ReadRemainingFile(); | |
| 519 | |
| 520 EXPECT_TRUE(StepDecodeVideo()); | |
| 521 EXPECT_NE(static_cast<int64>(AV_NOPTS_VALUE), decoded_video_time()); | |
| 522 } | |
| 523 | |
| 524 TEST_P(FFmpegTest, Decode_Audio) { | |
| 525 OpenAndReadFile(GetParam()); | |
| 526 if (!has_audio()) { | |
| 527 return; | |
| 528 } | |
| 529 | |
| 530 int64 last_audio_time = AV_NOPTS_VALUE; | |
| 531 while (StepDecodeAudio()) { | |
| 532 ASSERT_GT(decoded_audio_time(), last_audio_time); | |
| 533 last_audio_time = decoded_audio_time(); | |
| 534 } | |
| 535 } | |
| 536 | |
| 537 TEST_P(FFmpegTest, Decode_Video) { | |
| 538 OpenAndReadFile(GetParam()); | |
| 539 if (!has_video()) { | |
| 540 return; | |
| 541 } | |
| 542 | |
| 543 int64 last_video_time = AV_NOPTS_VALUE; | |
| 544 while (StepDecodeVideo()) { | |
| 545 ASSERT_GT(decoded_video_time(), last_video_time); | |
| 546 last_video_time = decoded_video_time(); | |
| 547 } | |
| 548 } | |
| 549 | |
| 550 TEST_P(FFmpegTest, Duration) { | |
| 551 OpenAndReadFile(GetParam()); | |
| 552 | |
| 553 if (has_audio()) { | |
| 554 DecodeRemainingAudio(); | |
| 555 } | |
| 556 | |
| 557 if (has_video()) { | |
| 558 DecodeRemainingVideo(); | |
| 559 } | |
| 560 | |
| 561 double expected = static_cast<double>(duration()); | |
| 562 double actual = static_cast<double>( | |
| 563 std::max(decoded_audio_time() + decoded_audio_duration(), | |
| 564 decoded_video_time() + decoded_video_duration())); | |
| 565 EXPECT_NEAR(expected, actual, 500000) | |
| 566 << "Duration is off by more than 0.5 seconds."; | |
| 567 } | |
| 568 | |
| 569 TEST_F(FFmpegTest, VideoPlayedCollapse) { | |
| 570 OpenFile("test.ogv"); | |
| 571 OpenCodecs(); | |
| 572 | |
| 573 SeekTo(0.5); | |
| 574 ReadRemainingFile(); | |
| 575 EXPECT_TRUE(StepDecodeVideo()); | |
| 576 VLOG(1) << decoded_video_time(); | |
| 577 | |
| 578 SeekTo(2.83); | |
| 579 ReadRemainingFile(); | |
| 580 EXPECT_TRUE(StepDecodeVideo()); | |
| 581 VLOG(1) << decoded_video_time(); | |
| 582 | |
| 583 SeekTo(0.4); | |
| 584 ReadRemainingFile(); | |
| 585 EXPECT_TRUE(StepDecodeVideo()); | |
| 586 VLOG(1) << decoded_video_time(); | |
| 587 } | |
| 588 | |
| 589 } // namespace media | |
| OLD | NEW |