summaryrefslogtreecommitdiffstats
path: root/media/formats/mp2t/es_parser_h264_unittest.cc
blob: 2c13df0d853fb72b12efe2640000ebd63ca95e97 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
// 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 <vector>

#include "base/bind.h"
#include "base/command_line.h"
#include "base/files/memory_mapped_file.h"
#include "base/logging.h"
#include "base/path_service.h"
#include "base/time/time.h"
#include "media/base/stream_parser_buffer.h"
#include "media/base/test_data_util.h"
#include "media/filters/h264_parser.h"
#include "media/formats/mp2t/es_parser_h264.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace media {
class VideoDecoderConfig;

namespace mp2t {

namespace {

struct Packet {
  // Offset in the stream.
  size_t offset;

  // Size of the packet.
  size_t size;

  // Timestamp of the packet.
  base::TimeDelta pts;
};

// Compute the size of each packet assuming packets are given in stream order
// and the last packet covers the end of the stream.
void ComputePacketSize(std::vector<Packet>& packets, size_t stream_size) {
  for (size_t k = 0; k < packets.size() - 1; k++) {
    DCHECK_GE(packets[k + 1].offset, packets[k].offset);
    packets[k].size = packets[k + 1].offset - packets[k].offset;
  }
  packets[packets.size() - 1].size =
      stream_size - packets[packets.size() - 1].offset;
}

// Get the offset of the start of each access unit.
// This function assumes there is only one slice per access unit.
// This is a very simplified access unit segmenter that is good
// enough for unit tests.
std::vector<Packet> GetAccessUnits(const uint8* stream, size_t stream_size) {
  std::vector<Packet> access_units;
  bool start_access_unit = true;

  // In a first pass, retrieve the offsets of all access units.
  size_t offset = 0;
  while (true) {
    // Find the next start code.
    off_t relative_offset = 0;
    off_t start_code_size = 0;
    bool success = H264Parser::FindStartCode(
        &stream[offset], stream_size - offset,
        &relative_offset, &start_code_size);
    if (!success)
      break;
    offset += relative_offset;

    if (start_access_unit) {
      Packet cur_access_unit;
      cur_access_unit.offset = offset;
      access_units.push_back(cur_access_unit);
      start_access_unit = false;
    }

    // Get the NALU type.
    offset += start_code_size;
    if (offset >= stream_size)
      break;
    int nal_unit_type = stream[offset] & 0x1f;

    // We assume there is only one slice per access unit.
    if (nal_unit_type == H264NALU::kIDRSlice ||
        nal_unit_type == H264NALU::kNonIDRSlice) {
      start_access_unit = true;
    }
  }

  ComputePacketSize(access_units, stream_size);
  return access_units;
}

// Append an AUD NALU at the beginning of each access unit
// needed for streams which do not already have AUD NALUs.
void AppendAUD(
    const uint8* stream, size_t stream_size,
    const std::vector<Packet>& access_units,
    std::vector<uint8>& stream_with_aud,
    std::vector<Packet>& access_units_with_aud) {
  uint8 aud[] = { 0x00, 0x00, 0x01, 0x09 };
  stream_with_aud.resize(stream_size + access_units.size() * sizeof(aud));
  access_units_with_aud.resize(access_units.size());

  size_t offset = 0;
  for (size_t k = 0; k < access_units.size(); k++) {
    access_units_with_aud[k].offset = offset;
    access_units_with_aud[k].size = access_units[k].size + sizeof(aud);

    memcpy(&stream_with_aud[offset], aud, sizeof(aud));
    offset += sizeof(aud);

    memcpy(&stream_with_aud[offset],
           &stream[access_units[k].offset], access_units[k].size);
    offset += access_units[k].size;
  }
}

}  // namespace

class EsParserH264Test : public testing::Test {
 public:
  EsParserH264Test() : buffer_count_(0) {
  }
  virtual ~EsParserH264Test() {}

 protected:
  void LoadStream(const char* filename);
  void GetPesTimestamps(std::vector<Packet>& pes_packets);
  void ProcessPesPackets(const std::vector<Packet>& pes_packets,
                         bool force_timing);

  // Stream with AUD NALUs.
  std::vector<uint8> stream_;

  // Access units of the stream with AUD NALUs.
  std::vector<Packet> access_units_;

  // Number of buffers generated while parsing the H264 stream.
  size_t buffer_count_;

 private:
  void EmitBuffer(scoped_refptr<StreamParserBuffer> buffer);

  void NewVideoConfig(const VideoDecoderConfig& config) {
  }

  DISALLOW_COPY_AND_ASSIGN(EsParserH264Test);
};

void EsParserH264Test::LoadStream(const char* filename) {
  base::FilePath file_path = GetTestDataFilePath(filename);

  base::MemoryMappedFile stream_without_aud;
  ASSERT_TRUE(stream_without_aud.Initialize(file_path))
      << "Couldn't open stream file: " << file_path.MaybeAsASCII();

  // The input file does not have AUDs.
  std::vector<Packet> access_units_without_aud = GetAccessUnits(
      stream_without_aud.data(), stream_without_aud.length());
  ASSERT_GT(access_units_without_aud.size(), 0u);
  AppendAUD(stream_without_aud.data(), stream_without_aud.length(),
            access_units_without_aud,
            stream_, access_units_);

  // Generate some timestamps based on a 25fps stream.
  for (size_t k = 0; k < access_units_.size(); k++)
    access_units_[k].pts = base::TimeDelta::FromMilliseconds(k * 40u);
}

void EsParserH264Test::GetPesTimestamps(std::vector<Packet>& pes_packets) {
  // Default: set to a negative timestamp to be able to differentiate from
  // real timestamps.
  // Note: we don't use kNoTimestamp() here since this one has already
  // a special meaning in EsParserH264. The negative timestamps should be
  // ultimately discarded by the H264 parser since not relevant.
  for (size_t k = 0; k < pes_packets.size(); k++) {
    pes_packets[k].pts = base::TimeDelta::FromMilliseconds(-1);
  }

  // Set a valid timestamp for PES packets which include the start
  // of an H264 access unit.
  size_t pes_idx = 0;
  for (size_t k = 0; k < access_units_.size(); k++) {
    for (; pes_idx < pes_packets.size(); pes_idx++) {
      size_t pes_start = pes_packets[pes_idx].offset;
      size_t pes_end = pes_packets[pes_idx].offset + pes_packets[pes_idx].size;
      if (pes_start <= access_units_[k].offset &&
          pes_end > access_units_[k].offset) {
        pes_packets[pes_idx].pts = access_units_[k].pts;
        break;
      }
    }
  }
}

void EsParserH264Test::ProcessPesPackets(
    const std::vector<Packet>& pes_packets,
    bool force_timing) {
  EsParserH264 es_parser(
      base::Bind(&EsParserH264Test::NewVideoConfig, base::Unretained(this)),
      base::Bind(&EsParserH264Test::EmitBuffer, base::Unretained(this)));

  for (size_t k = 0; k < pes_packets.size(); k++) {
    size_t cur_pes_offset = pes_packets[k].offset;
    size_t cur_pes_size = pes_packets[k].size;

    base::TimeDelta pts = kNoTimestamp();
    base::TimeDelta dts = kNoTimestamp();
    if (pes_packets[k].pts >= base::TimeDelta() || force_timing)
      pts = pes_packets[k].pts;

    ASSERT_TRUE(
        es_parser.Parse(&stream_[cur_pes_offset], cur_pes_size, pts, dts));
  }
  es_parser.Flush();
}

void EsParserH264Test::EmitBuffer(scoped_refptr<StreamParserBuffer> buffer) {
  ASSERT_LT(buffer_count_, access_units_.size());
  EXPECT_EQ(buffer->timestamp(), access_units_[buffer_count_].pts);
  buffer_count_++;
}

TEST_F(EsParserH264Test, OneAccessUnitPerPes) {
  LoadStream("bear.h264");

  // One to one equivalence between PES packets and access units.
  std::vector<Packet> pes_packets(access_units_);
  GetPesTimestamps(pes_packets);

  // Process each PES packet.
  ProcessPesPackets(pes_packets, false);
  EXPECT_EQ(buffer_count_, access_units_.size());
}

TEST_F(EsParserH264Test, NonAlignedPesPacket) {
  LoadStream("bear.h264");

  // Generate the PES packets.
  std::vector<Packet> pes_packets;
  Packet cur_pes_packet;
  cur_pes_packet.offset = 0;
  for (size_t k = 0; k < access_units_.size(); k++) {
    pes_packets.push_back(cur_pes_packet);

    // The current PES packet includes the remaining bytes of the previous
    // access unit and some bytes of the current access unit
    // (487 bytes in this unit test but no more than the current access unit
    // size).
    cur_pes_packet.offset = access_units_[k].offset +
        std::min<size_t>(487u, access_units_[k].size);
  }
  ComputePacketSize(pes_packets, stream_.size());
  GetPesTimestamps(pes_packets);

  // Process each PES packet.
  ProcessPesPackets(pes_packets, false);
  EXPECT_EQ(buffer_count_, access_units_.size());
}

TEST_F(EsParserH264Test, SeveralPesPerAccessUnit) {
  LoadStream("bear.h264");

  // Get the minimum size of an access unit.
  size_t min_access_unit_size = stream_.size();
  for (size_t k = 0; k < access_units_.size(); k++) {
    if (min_access_unit_size >= access_units_[k].size)
      min_access_unit_size = access_units_[k].size;
  }

  // Use a small PES packet size or the minimum access unit size
  // if it is even smaller.
  size_t pes_size = 512;
  if (min_access_unit_size < pes_size)
    pes_size = min_access_unit_size;

  std::vector<Packet> pes_packets;
  Packet cur_pes_packet;
  cur_pes_packet.offset = 0;
  while (cur_pes_packet.offset < stream_.size()) {
    pes_packets.push_back(cur_pes_packet);
    cur_pes_packet.offset += pes_size;
  }
  ComputePacketSize(pes_packets, stream_.size());
  GetPesTimestamps(pes_packets);

  // Process each PES packet.
  ProcessPesPackets(pes_packets, false);
  EXPECT_EQ(buffer_count_, access_units_.size());

  // Process PES packets forcing timings for each PES packet.
  buffer_count_ = 0;
  ProcessPesPackets(pes_packets, true);
  EXPECT_EQ(buffer_count_, access_units_.size());
}

}  // namespace mp2t
}  // namespace media