| // Copyright (c) 2012 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 <algorithm> |
| |
| #include "base/bind.h" |
| #include "base/logging.h" |
| #include "media/base/decrypt_config.h" |
| #include "media/webm/cluster_builder.h" |
| #include "media/webm/webm_cluster_parser.h" |
| #include "media/webm/webm_constants.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| using ::testing::InSequence; |
| using ::testing::Return; |
| using ::testing::_; |
| |
| namespace media { |
| |
| enum { |
| kTimecodeScale = 1000000, // Timecode scale for millisecond timestamps. |
| kAudioTrackNum = 1, |
| kVideoTrackNum = 2, |
| kTextTrackNum = 3, |
| }; |
| |
| struct BlockInfo { |
| int track_num; |
| int timestamp; |
| int duration; |
| bool use_simple_block; |
| }; |
| |
| static const BlockInfo kDefaultBlockInfo[] = { |
| { kAudioTrackNum, 0, 23, true }, |
| { kAudioTrackNum, 23, 23, true }, |
| { kVideoTrackNum, 33, 34, true }, |
| { kAudioTrackNum, 46, 23, true }, |
| { kVideoTrackNum, 67, 33, false }, |
| { kAudioTrackNum, 69, 23, false }, |
| { kVideoTrackNum, 100, 33, false }, |
| }; |
| |
| static const uint8 kEncryptedFrame[] = { |
| 0x01, // Block is encrypted |
| 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 // IV |
| }; |
| |
| static scoped_ptr<Cluster> CreateCluster(int timecode, |
| const BlockInfo* block_info, |
| int block_count) { |
| ClusterBuilder cb; |
| cb.SetClusterTimecode(0); |
| |
| for (int i = 0; i < block_count; i++) { |
| uint8 data[] = { 0x00 }; |
| if (block_info[i].use_simple_block) { |
| cb.AddSimpleBlock(block_info[i].track_num, |
| block_info[i].timestamp, |
| 0, data, sizeof(data)); |
| continue; |
| } |
| |
| CHECK_GE(block_info[i].duration, 0); |
| cb.AddBlockGroup(block_info[i].track_num, |
| block_info[i].timestamp, |
| block_info[i].duration, |
| 0, data, sizeof(data)); |
| } |
| |
| return cb.Finish(); |
| } |
| |
| // Creates a Cluster with one encrypted Block. |bytes_to_write| is number of |
| // bytes of the encrypted frame to write. |
| static scoped_ptr<Cluster> CreateEncryptedCluster(int bytes_to_write) { |
| CHECK_GT(bytes_to_write, 0); |
| CHECK_LE(bytes_to_write, static_cast<int>(sizeof(kEncryptedFrame))); |
| |
| ClusterBuilder cb; |
| cb.SetClusterTimecode(0); |
| cb.AddSimpleBlock(kVideoTrackNum, 0, 0, kEncryptedFrame, bytes_to_write); |
| return cb.Finish(); |
| } |
| |
| static bool VerifyBuffers(const WebMClusterParser::BufferQueue& audio_buffers, |
| const WebMClusterParser::BufferQueue& video_buffers, |
| const WebMClusterParser::BufferQueue& text_buffers, |
| const BlockInfo* block_info, |
| int block_count) { |
| size_t audio_offset = 0; |
| size_t video_offset = 0; |
| size_t text_offset = 0; |
| for (int i = 0; i < block_count; i++) { |
| const WebMClusterParser::BufferQueue* buffers = NULL; |
| size_t* offset; |
| |
| if (block_info[i].track_num == kAudioTrackNum) { |
| buffers = &audio_buffers; |
| offset = &audio_offset; |
| } else if (block_info[i].track_num == kVideoTrackNum) { |
| buffers = &video_buffers; |
| offset = &video_offset; |
| } else if (block_info[i].track_num == kTextTrackNum) { |
| buffers = &text_buffers; |
| offset = &text_offset; |
| } else { |
| LOG(ERROR) << "Unexpected track number " << block_info[i].track_num; |
| return false; |
| } |
| |
| if (*offset >= buffers->size()) |
| return false; |
| |
| scoped_refptr<StreamParserBuffer> buffer = (*buffers)[(*offset)++]; |
| |
| |
| EXPECT_EQ(buffer->timestamp().InMilliseconds(), block_info[i].timestamp); |
| |
| if (!block_info[i].use_simple_block) |
| EXPECT_NE(buffer->duration(), kNoTimestamp()); |
| |
| if (buffer->duration() != kNoTimestamp()) |
| EXPECT_EQ(buffer->duration().InMilliseconds(), block_info[i].duration); |
| } |
| |
| return true; |
| } |
| |
| static bool VerifyBuffers(const scoped_ptr<WebMClusterParser>& parser, |
| const BlockInfo* block_info, |
| int block_count) { |
| typedef WebMClusterParser::TextTrackIterator TextTrackIterator; |
| TextTrackIterator text_it = parser->CreateTextTrackIterator(); |
| |
| int text_track_num; |
| const WebMClusterParser::BufferQueue* text_buffers; |
| |
| while (text_it(&text_track_num, &text_buffers)) |
| break; |
| |
| const WebMClusterParser::BufferQueue no_text_buffers; |
| |
| if (text_buffers == NULL) |
| text_buffers = &no_text_buffers; |
| |
| return VerifyBuffers(parser->audio_buffers(), |
| parser->video_buffers(), |
| *text_buffers, |
| block_info, |
| block_count); |
| } |
| |
| static bool VerifyTextBuffers( |
| const scoped_ptr<WebMClusterParser>& parser, |
| const BlockInfo* block_info_ptr, |
| int block_count, |
| int text_track_num, |
| const WebMClusterParser::BufferQueue& text_buffers) { |
| const BlockInfo* const block_info_end = block_info_ptr + block_count; |
| |
| typedef WebMClusterParser::BufferQueue::const_iterator TextBufferIter; |
| TextBufferIter buffer_iter = text_buffers.begin(); |
| const TextBufferIter buffer_end = text_buffers.end(); |
| |
| while (block_info_ptr != block_info_end) { |
| const BlockInfo& block_info = *block_info_ptr++; |
| |
| if (block_info.track_num != text_track_num) |
| continue; |
| |
| EXPECT_FALSE(block_info.use_simple_block); |
| EXPECT_FALSE(buffer_iter == buffer_end); |
| |
| const scoped_refptr<StreamParserBuffer> buffer = *buffer_iter++; |
| EXPECT_EQ(buffer->timestamp().InMilliseconds(), block_info.timestamp); |
| EXPECT_EQ(buffer->duration().InMilliseconds(), block_info.duration); |
| } |
| |
| EXPECT_TRUE(buffer_iter == buffer_end); |
| return true; |
| } |
| |
| static bool VerifyEncryptedBuffer( |
| scoped_refptr<StreamParserBuffer> buffer) { |
| EXPECT_TRUE(buffer->decrypt_config()); |
| EXPECT_EQ(static_cast<unsigned long>(DecryptConfig::kDecryptionKeySize), |
| buffer->decrypt_config()->iv().length()); |
| const uint8* data = buffer->data(); |
| return data[0] & kWebMFlagEncryptedFrame; |
| } |
| |
| static void AppendToEnd(const WebMClusterParser::BufferQueue& src, |
| WebMClusterParser::BufferQueue* dest) { |
| for (WebMClusterParser::BufferQueue::const_iterator itr = src.begin(); |
| itr != src.end(); ++itr) { |
| dest->push_back(*itr); |
| } |
| } |
| |
| class WebMClusterParserTest : public testing::Test { |
| public: |
| WebMClusterParserTest() |
| : parser_(new WebMClusterParser(kTimecodeScale, |
| kAudioTrackNum, |
| kVideoTrackNum, |
| WebMTracksParser::TextTracks(), |
| std::set<int64>(), |
| std::string(), |
| std::string(), |
| LogCB())) {} |
| |
| protected: |
| scoped_ptr<WebMClusterParser> parser_; |
| }; |
| |
| TEST_F(WebMClusterParserTest, Reset) { |
| InSequence s; |
| |
| int block_count = arraysize(kDefaultBlockInfo); |
| scoped_ptr<Cluster> cluster(CreateCluster(0, kDefaultBlockInfo, block_count)); |
| |
| // Send slightly less than the full cluster so all but the last block is |
| // parsed. |
| int result = parser_->Parse(cluster->data(), cluster->size() - 1); |
| EXPECT_GT(result, 0); |
| EXPECT_LT(result, cluster->size()); |
| |
| ASSERT_TRUE(VerifyBuffers(parser_, kDefaultBlockInfo, block_count - 1)); |
| parser_->Reset(); |
| |
| // Now parse a whole cluster to verify that all the blocks will get parsed. |
| result = parser_->Parse(cluster->data(), cluster->size()); |
| EXPECT_EQ(result, cluster->size()); |
| ASSERT_TRUE(VerifyBuffers(parser_, kDefaultBlockInfo, block_count)); |
| } |
| |
| TEST_F(WebMClusterParserTest, ParseClusterWithSingleCall) { |
| int block_count = arraysize(kDefaultBlockInfo); |
| scoped_ptr<Cluster> cluster(CreateCluster(0, kDefaultBlockInfo, block_count)); |
| |
| int result = parser_->Parse(cluster->data(), cluster->size()); |
| EXPECT_EQ(cluster->size(), result); |
| ASSERT_TRUE(VerifyBuffers(parser_, kDefaultBlockInfo, block_count)); |
| } |
| |
| TEST_F(WebMClusterParserTest, ParseClusterWithMultipleCalls) { |
| int block_count = arraysize(kDefaultBlockInfo); |
| scoped_ptr<Cluster> cluster(CreateCluster(0, kDefaultBlockInfo, block_count)); |
| |
| WebMClusterParser::BufferQueue audio_buffers; |
| WebMClusterParser::BufferQueue video_buffers; |
| const WebMClusterParser::BufferQueue no_text_buffers; |
| |
| const uint8* data = cluster->data(); |
| int size = cluster->size(); |
| int default_parse_size = 3; |
| int parse_size = std::min(default_parse_size, size); |
| |
| while (size > 0) { |
| int result = parser_->Parse(data, parse_size); |
| ASSERT_GE(result, 0); |
| ASSERT_LE(result, parse_size); |
| |
| if (result == 0) { |
| // The parser needs more data so increase the parse_size a little. |
| parse_size += default_parse_size; |
| parse_size = std::min(parse_size, size); |
| continue; |
| } |
| |
| AppendToEnd(parser_->audio_buffers(), &audio_buffers); |
| AppendToEnd(parser_->video_buffers(), &video_buffers); |
| |
| parse_size = default_parse_size; |
| |
| data += result; |
| size -= result; |
| } |
| ASSERT_TRUE(VerifyBuffers(audio_buffers, video_buffers, |
| no_text_buffers, kDefaultBlockInfo, |
| block_count)); |
| } |
| |
| // Verify that both BlockGroups with the BlockDuration before the Block |
| // and BlockGroups with the BlockDuration after the Block are supported |
| // correctly. |
| // Note: Raw bytes are use here because ClusterBuilder only generates |
| // one of these scenarios. |
| TEST_F(WebMClusterParserTest, ParseBlockGroup) { |
| const BlockInfo kBlockInfo[] = { |
| { kAudioTrackNum, 0, 23, false }, |
| { kVideoTrackNum, 33, 34, false }, |
| }; |
| int block_count = arraysize(kBlockInfo); |
| |
| const uint8 kClusterData[] = { |
| 0x1F, 0x43, 0xB6, 0x75, 0x9B, // Cluster(size=27) |
| 0xE7, 0x81, 0x00, // Timecode(size=1, value=0) |
| // BlockGroup with BlockDuration before Block. |
| 0xA0, 0x8A, // BlockGroup(size=10) |
| 0x9B, 0x81, 0x17, // BlockDuration(size=1, value=23) |
| 0xA1, 0x85, 0x81, 0x00, 0x00, 0x00, 0xaa, // Block(size=5, track=1, ts=0) |
| // BlockGroup with BlockDuration after Block. |
| 0xA0, 0x8A, // BlockGroup(size=10) |
| 0xA1, 0x85, 0x82, 0x00, 0x21, 0x00, 0x55, // Block(size=5, track=2, ts=33) |
| 0x9B, 0x81, 0x22, // BlockDuration(size=1, value=34) |
| }; |
| const int kClusterSize = sizeof(kClusterData); |
| |
| int result = parser_->Parse(kClusterData, kClusterSize); |
| EXPECT_EQ(result, kClusterSize); |
| ASSERT_TRUE(VerifyBuffers(parser_, kBlockInfo, block_count)); |
| } |
| |
| TEST_F(WebMClusterParserTest, ParseSimpleBlockAndBlockGroupMixture) { |
| const BlockInfo kBlockInfo[] = { |
| { kAudioTrackNum, 0, 23, true }, |
| { kAudioTrackNum, 23, 23, false }, |
| { kVideoTrackNum, 33, 34, true }, |
| { kAudioTrackNum, 46, 23, false }, |
| { kVideoTrackNum, 67, 33, false }, |
| }; |
| int block_count = arraysize(kBlockInfo); |
| scoped_ptr<Cluster> cluster(CreateCluster(0, kBlockInfo, block_count)); |
| |
| int result = parser_->Parse(cluster->data(), cluster->size()); |
| EXPECT_EQ(cluster->size(), result); |
| ASSERT_TRUE(VerifyBuffers(parser_, kBlockInfo, block_count)); |
| } |
| |
| TEST_F(WebMClusterParserTest, IgnoredTracks) { |
| std::set<int64> ignored_tracks; |
| ignored_tracks.insert(kTextTrackNum); |
| |
| parser_.reset(new WebMClusterParser(kTimecodeScale, |
| kAudioTrackNum, |
| kVideoTrackNum, |
| WebMTracksParser::TextTracks(), |
| ignored_tracks, |
| std::string(), |
| std::string(), |
| LogCB())); |
| |
| const BlockInfo kInputBlockInfo[] = { |
| { kAudioTrackNum, 0, 23, true }, |
| { kAudioTrackNum, 23, 23, true }, |
| { kVideoTrackNum, 33, 33, true }, |
| { kTextTrackNum, 33, 99, true }, |
| { kAudioTrackNum, 46, 23, true }, |
| { kVideoTrackNum, 67, 33, true }, |
| }; |
| int input_block_count = arraysize(kInputBlockInfo); |
| |
| const BlockInfo kOutputBlockInfo[] = { |
| { kAudioTrackNum, 0, 23, true }, |
| { kAudioTrackNum, 23, 23, true }, |
| { kVideoTrackNum, 33, 33, true }, |
| { kAudioTrackNum, 46, 23, true }, |
| { kVideoTrackNum, 67, 33, true }, |
| }; |
| int output_block_count = arraysize(kOutputBlockInfo); |
| |
| scoped_ptr<Cluster> cluster( |
| CreateCluster(0, kInputBlockInfo, input_block_count)); |
| |
| int result = parser_->Parse(cluster->data(), cluster->size()); |
| EXPECT_EQ(cluster->size(), result); |
| ASSERT_TRUE(VerifyBuffers(parser_, kOutputBlockInfo, output_block_count)); |
| } |
| |
| TEST_F(WebMClusterParserTest, ParseTextTracks) { |
| typedef WebMTracksParser::TextTracks TextTracks; |
| TextTracks text_tracks; |
| |
| text_tracks.insert(std::make_pair(TextTracks::key_type(kTextTrackNum), |
| TextTrackConfig(kTextSubtitles, "", "", |
| ""))); |
| |
| parser_.reset(new WebMClusterParser(kTimecodeScale, |
| kAudioTrackNum, |
| kVideoTrackNum, |
| text_tracks, |
| std::set<int64>(), |
| std::string(), |
| std::string(), |
| LogCB())); |
| |
| const BlockInfo kInputBlockInfo[] = { |
| { kAudioTrackNum, 0, 23, true }, |
| { kAudioTrackNum, 23, 23, true }, |
| { kVideoTrackNum, 33, 33, true }, |
| { kTextTrackNum, 33, 42, false }, |
| { kAudioTrackNum, 46, 23, true }, |
| { kTextTrackNum, 55, 44, false }, |
| { kVideoTrackNum, 67, 33, true }, |
| }; |
| int input_block_count = arraysize(kInputBlockInfo); |
| |
| scoped_ptr<Cluster> cluster( |
| CreateCluster(0, kInputBlockInfo, input_block_count)); |
| |
| int result = parser_->Parse(cluster->data(), cluster->size()); |
| EXPECT_EQ(cluster->size(), result); |
| ASSERT_TRUE(VerifyBuffers(parser_, kInputBlockInfo, input_block_count)); |
| } |
| |
| TEST_F(WebMClusterParserTest, TextTracksSimpleBlock) { |
| typedef WebMTracksParser::TextTracks TextTracks; |
| WebMTracksParser::TextTracks text_tracks; |
| |
| text_tracks.insert(std::make_pair(TextTracks::key_type(kTextTrackNum), |
| TextTrackConfig(kTextSubtitles, "", "", |
| ""))); |
| |
| parser_.reset(new WebMClusterParser(kTimecodeScale, |
| kAudioTrackNum, |
| kVideoTrackNum, |
| text_tracks, |
| std::set<int64>(), |
| std::string(), |
| std::string(), |
| LogCB())); |
| |
| const BlockInfo kInputBlockInfo[] = { |
| { kTextTrackNum, 33, 42, true }, |
| }; |
| int input_block_count = arraysize(kInputBlockInfo); |
| |
| scoped_ptr<Cluster> cluster( |
| CreateCluster(0, kInputBlockInfo, input_block_count)); |
| |
| int result = parser_->Parse(cluster->data(), cluster->size()); |
| EXPECT_LT(result, 0); |
| } |
| |
| TEST_F(WebMClusterParserTest, ParseMultipleTextTracks) { |
| typedef WebMTracksParser::TextTracks TextTracks; |
| TextTracks text_tracks; |
| |
| const int kSubtitleTextTrackNum = kTextTrackNum; |
| const int kCaptionTextTrackNum = kTextTrackNum + 1; |
| |
| text_tracks.insert(std::make_pair(TextTracks::key_type(kSubtitleTextTrackNum), |
| TextTrackConfig(kTextSubtitles, "", "", |
| ""))); |
| |
| text_tracks.insert(std::make_pair(TextTracks::key_type(kCaptionTextTrackNum), |
| TextTrackConfig(kTextCaptions, "", "", |
| ""))); |
| |
| parser_.reset(new WebMClusterParser(kTimecodeScale, |
| kAudioTrackNum, |
| kVideoTrackNum, |
| text_tracks, |
| std::set<int64>(), |
| std::string(), |
| std::string(), |
| LogCB())); |
| |
| const BlockInfo kInputBlockInfo[] = { |
| { kAudioTrackNum, 0, 23, true }, |
| { kAudioTrackNum, 23, 23, true }, |
| { kVideoTrackNum, 33, 33, true }, |
| { kSubtitleTextTrackNum, 33, 42, false }, |
| { kAudioTrackNum, 46, 23, true }, |
| { kCaptionTextTrackNum, 55, 44, false }, |
| { kVideoTrackNum, 67, 33, true }, |
| { kSubtitleTextTrackNum, 67, 33, false }, |
| }; |
| int input_block_count = arraysize(kInputBlockInfo); |
| |
| scoped_ptr<Cluster> cluster( |
| CreateCluster(0, kInputBlockInfo, input_block_count)); |
| |
| int result = parser_->Parse(cluster->data(), cluster->size()); |
| EXPECT_EQ(cluster->size(), result); |
| |
| WebMClusterParser::TextTrackIterator text_it = |
| parser_->CreateTextTrackIterator(); |
| |
| int text_track_num; |
| const WebMClusterParser::BufferQueue* text_buffers; |
| |
| while (text_it(&text_track_num, &text_buffers)) { |
| const WebMTracksParser::TextTracks::const_iterator find_result = |
| text_tracks.find(text_track_num); |
| ASSERT_TRUE(find_result != text_tracks.end()); |
| ASSERT_TRUE(VerifyTextBuffers(parser_, kInputBlockInfo, input_block_count, |
| text_track_num, *text_buffers)); |
| } |
| } |
| |
| TEST_F(WebMClusterParserTest, ParseEncryptedBlock) { |
| scoped_ptr<Cluster> cluster(CreateEncryptedCluster(sizeof(kEncryptedFrame))); |
| |
| parser_.reset(new WebMClusterParser(kTimecodeScale, |
| kAudioTrackNum, |
| kVideoTrackNum, |
| WebMTracksParser::TextTracks(), |
| std::set<int64>(), |
| std::string(), |
| "video_key_id", |
| LogCB())); |
| int result = parser_->Parse(cluster->data(), cluster->size()); |
| EXPECT_EQ(cluster->size(), result); |
| ASSERT_EQ(1UL, parser_->video_buffers().size()); |
| scoped_refptr<StreamParserBuffer> buffer = parser_->video_buffers()[0]; |
| EXPECT_TRUE(VerifyEncryptedBuffer(buffer)); |
| } |
| |
| TEST_F(WebMClusterParserTest, ParseBadEncryptedBlock) { |
| scoped_ptr<Cluster> cluster( |
| CreateEncryptedCluster(sizeof(kEncryptedFrame) - 1)); |
| |
| parser_.reset(new WebMClusterParser(kTimecodeScale, |
| kAudioTrackNum, |
| kVideoTrackNum, |
| WebMTracksParser::TextTracks(), |
| std::set<int64>(), |
| std::string(), |
| "video_key_id", |
| LogCB())); |
| int result = parser_->Parse(cluster->data(), cluster->size()); |
| EXPECT_EQ(-1, result); |
| } |
| |
| } // namespace media |