|  | // 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/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, | 
|  | }; | 
|  |  | 
|  | struct BlockInfo { | 
|  | int track_num; | 
|  | int timestamp; | 
|  | int duration; | 
|  | bool use_simple_block; | 
|  | }; | 
|  |  | 
|  | 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 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(); | 
|  | } | 
|  |  | 
|  | static bool VerifyBuffers(const WebMClusterParser::BufferQueue& audio_buffers, | 
|  | const WebMClusterParser::BufferQueue& video_buffers, | 
|  | const BlockInfo* block_info, | 
|  | int block_count) { | 
|  | size_t audio_offset = 0; | 
|  | size_t video_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 { | 
|  | 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->GetTimestamp().InMilliseconds(), block_info[i].timestamp); | 
|  |  | 
|  | if (!block_info[i].use_simple_block) | 
|  | EXPECT_NE(buffer->GetDuration(), kNoTimestamp()); | 
|  |  | 
|  | if (buffer->GetDuration() != kNoTimestamp()) | 
|  | EXPECT_EQ(buffer->GetDuration().InMilliseconds(), block_info[i].duration); | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | static bool VerifyBuffers(const scoped_ptr<WebMClusterParser>& parser, | 
|  | const BlockInfo* block_info, | 
|  | int block_count) { | 
|  | return VerifyBuffers(parser->audio_buffers(), | 
|  | parser->video_buffers(), | 
|  | block_info, | 
|  | block_count); | 
|  | } | 
|  |  | 
|  | 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, "", "", LogCB())) { | 
|  | } | 
|  |  | 
|  | protected: | 
|  | scoped_ptr<WebMClusterParser> parser_; | 
|  | }; | 
|  |  | 
|  | TEST_F(WebMClusterParserTest, TestReset) { | 
|  | 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 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, 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)); | 
|  | } | 
|  |  | 
|  | }  // namespace media |