root/media/formats/webm/webm_cluster_parser_unittest.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. CreateCluster
  2. CreateEncryptedCluster
  3. VerifyBuffers
  4. VerifyBuffers
  5. VerifyTextBuffers
  6. VerifyEncryptedBuffer
  7. AppendToEnd
  8. TEST_F
  9. TEST_F
  10. TEST_F
  11. TEST_F
  12. TEST_F
  13. TEST_F
  14. TEST_F
  15. TEST_F
  16. TEST_F
  17. TEST_F
  18. TEST_F
  19. TEST_F
  20. TEST_F

// Copyright 2014 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/formats/webm/cluster_builder.h"
#include "media/formats/webm/webm_cluster_parser.h"
#include "media/formats/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 },  // Assumes not using DefaultDuration
  { 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;
    StreamParserBuffer::Type expected_type = DemuxerStream::UNKNOWN;

    if (block_info[i].track_num == kAudioTrackNum) {
      buffers = &audio_buffers;
      offset = &audio_offset;
      expected_type = DemuxerStream::AUDIO;
    } else if (block_info[i].track_num == kVideoTrackNum) {
      buffers = &video_buffers;
      offset = &video_offset;
      expected_type = DemuxerStream::VIDEO;
    } else if (block_info[i].track_num == kTextTrackNum) {
      buffers = &text_buffers;
      offset = &text_offset;
      expected_type = DemuxerStream::TEXT;
    } 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(block_info[i].timestamp, buffer->timestamp().InMilliseconds());
    EXPECT_EQ(block_info[i].duration, buffer->duration().InMilliseconds());
    EXPECT_EQ(expected_type, buffer->type());
    EXPECT_EQ(block_info[i].track_num, buffer->track_id());
  }

  return true;
}

static bool VerifyBuffers(const scoped_ptr<WebMClusterParser>& parser,
                          const BlockInfo* block_info,
                          int block_count) {
  const WebMClusterParser::TextBufferQueueMap& text_map =
      parser->GetTextBuffers();
  const WebMClusterParser::BufferQueue* text_buffers;
  const WebMClusterParser::BufferQueue no_text_buffers;
  if (!text_map.empty())
    text_buffers = &(text_map.rbegin()->second);
  else
    text_buffers = &no_text_buffers;

  return VerifyBuffers(parser->GetAudioBuffers(),
                       parser->GetVideoBuffers(),
                       *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(block_info.timestamp, buffer->timestamp().InMilliseconds());
    EXPECT_EQ(block_info.duration, buffer->duration().InMilliseconds());
    EXPECT_EQ(DemuxerStream::TEXT, buffer->type());
    EXPECT_EQ(text_track_num, buffer->track_id());
  }

  EXPECT_TRUE(buffer_iter == buffer_end);
  return true;
}

static void VerifyEncryptedBuffer(
    scoped_refptr<StreamParserBuffer> buffer) {
  EXPECT_TRUE(buffer->decrypt_config());
  EXPECT_EQ(static_cast<unsigned long>(DecryptConfig::kDecryptionKeySize),
            buffer->decrypt_config()->iv().length());
}

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,
                                      kNoTimestamp(),
                                      kVideoTrackNum,
                                      kNoTimestamp(),
                                      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(cluster->size(), result);
  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_->GetAudioBuffers(), &audio_buffers);
    AppendToEnd(parser_->GetVideoBuffers(), &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(kClusterSize, result);
  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,
                                      kNoTimestamp(),
                                      kVideoTrackNum,
                                      kNoTimestamp(),
                                      WebMTracksParser::TextTracks(),
                                      ignored_tracks,
                                      std::string(),
                                      std::string(),
                                      LogCB()));

  const BlockInfo kInputBlockInfo[] = {
    { kAudioTrackNum, 0,  23, true },
    { kAudioTrackNum, 23, 23, true },
    { kVideoTrackNum, 33, 34, true },
    { kTextTrackNum,  33, 99, true },
    { kAudioTrackNum, 46, 23, true },
    { kVideoTrackNum, 67, 34, true },
  };
  int input_block_count = arraysize(kInputBlockInfo);

  const BlockInfo kOutputBlockInfo[] = {
    { kAudioTrackNum, 0,  23, true },
    { kAudioTrackNum, 23, 23, true },
    { kVideoTrackNum, 33, 34, true },
    { kAudioTrackNum, 46, 23, true },
    { kVideoTrackNum, 67, 34, 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,
                                      kNoTimestamp(),
                                      kVideoTrackNum,
                                      kNoTimestamp(),
                                      text_tracks,
                                      std::set<int64>(),
                                      std::string(),
                                      std::string(),
                                      LogCB()));

  const BlockInfo kInputBlockInfo[] = {
    { kAudioTrackNum, 0,  23, true },
    { kAudioTrackNum, 23, 23, true },
    { kVideoTrackNum, 33, 34, true },
    { kTextTrackNum,  33, 42, false },
    { kAudioTrackNum, 46, 23, true },
    { kTextTrackNum, 55, 44, false },
    { kVideoTrackNum, 67, 34, 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,
                                      kNoTimestamp(),
                                      kVideoTrackNum,
                                      kNoTimestamp(),
                                      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,
                                      kNoTimestamp(),
                                      kVideoTrackNum,
                                      kNoTimestamp(),
                                      text_tracks,
                                      std::set<int64>(),
                                      std::string(),
                                      std::string(),
                                      LogCB()));

  const BlockInfo kInputBlockInfo[] = {
    { kAudioTrackNum, 0,  23, true },
    { kAudioTrackNum, 23, 23, true },
    { kVideoTrackNum, 33, 34, true },
    { kSubtitleTextTrackNum,  33, 42, false },
    { kAudioTrackNum, 46, 23, true },
    { kCaptionTextTrackNum, 55, 44, false },
    { kVideoTrackNum, 67, 34, 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);

  const WebMClusterParser::TextBufferQueueMap& text_map =
      parser_->GetTextBuffers();
  for (WebMClusterParser::TextBufferQueueMap::const_iterator itr =
           text_map.begin();
       itr != text_map.end();
       ++itr) {
    const WebMTracksParser::TextTracks::const_iterator find_result =
        text_tracks.find(itr->first);
    ASSERT_TRUE(find_result != text_tracks.end());
    ASSERT_TRUE(VerifyTextBuffers(parser_, kInputBlockInfo, input_block_count,
                                  itr->first, itr->second));
  }
}

TEST_F(WebMClusterParserTest, ParseEncryptedBlock) {
  scoped_ptr<Cluster> cluster(CreateEncryptedCluster(sizeof(kEncryptedFrame)));

  parser_.reset(new WebMClusterParser(kTimecodeScale,
                                      kAudioTrackNum,
                                      kNoTimestamp(),
                                      kVideoTrackNum,
                                      kNoTimestamp(),
                                      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_->GetVideoBuffers().size());
  scoped_refptr<StreamParserBuffer> buffer = parser_->GetVideoBuffers()[0];
  VerifyEncryptedBuffer(buffer);
}

TEST_F(WebMClusterParserTest, ParseBadEncryptedBlock) {
  scoped_ptr<Cluster> cluster(
      CreateEncryptedCluster(sizeof(kEncryptedFrame) - 1));

  parser_.reset(new WebMClusterParser(kTimecodeScale,
                                      kAudioTrackNum,
                                      kNoTimestamp(),
                                      kVideoTrackNum,
                                      kNoTimestamp(),
                                      WebMTracksParser::TextTracks(),
                                      std::set<int64>(),
                                      std::string(),
                                      "video_key_id",
                                      LogCB()));
  int result = parser_->Parse(cluster->data(), cluster->size());
  EXPECT_EQ(-1, result);
}

TEST_F(WebMClusterParserTest, ParseInvalidZeroSizedCluster) {
  const uint8 kBuffer[] = {
    0x1F, 0x43, 0xB6, 0x75, 0x80,  // CLUSTER (size = 0)
  };

  EXPECT_EQ(-1, parser_->Parse(kBuffer, sizeof(kBuffer)));
}

TEST_F(WebMClusterParserTest, ParseInvalidUnknownButActuallyZeroSizedCluster) {
  const uint8 kBuffer[] = {
    0x1F, 0x43, 0xB6, 0x75, 0xFF,  // CLUSTER (size = "unknown")
    0x1F, 0x43, 0xB6, 0x75, 0x85,  // CLUSTER (size = 5)
  };

  EXPECT_EQ(-1, parser_->Parse(kBuffer, sizeof(kBuffer)));
}

}  // namespace media

/* [<][>][^][v][top][bottom][index][help] */