summaryrefslogtreecommitdiffstats
path: root/media/cast/logging/log_serializer.cc
blob: eec4b2b83fbe1598d825b6822c29458d02c644e0 (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
// 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.
//
// The serialization format is as follows:
//   16-bit integer describing the following LogMetadata proto size in bytes.
//   The LogMetadata proto.
//   32-bit integer describing number of frame events.
//   (The following repeated for number of frame events):
//     16-bit integer describing the following AggregatedFrameEvent proto size
//         in bytes.
//     The AggregatedFrameEvent proto.
//   32-bit integer describing number of packet events.
//   (The following repeated for number of packet events):
//     16-bit integer describing the following AggregatedPacketEvent proto
//         size in bytes.
//     The AggregatedPacketEvent proto.

#include "media/cast/logging/log_serializer.h"

#include <stdint.h>

#include "base/big_endian.h"
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "third_party/zlib/zlib.h"

namespace media {
namespace cast {

namespace {

using media::cast::proto::AggregatedFrameEvent;
using media::cast::proto::AggregatedPacketEvent;
using media::cast::proto::LogMetadata;

// Use 30MB of temp buffer to hold uncompressed data if |compress| is true.
const int kMaxUncompressedBytes = 30 * 1000 * 1000;

// The maximum allowed size per serialized proto.
const int kMaxSerializedProtoBytes = (1 << 16) - 1;
bool DoSerializeEvents(const LogMetadata& metadata,
                       const FrameEventList& frame_events,
                       const PacketEventList& packet_events,
                       const int max_output_bytes,
                       char* output,
                       int* output_bytes) {
  base::BigEndianWriter writer(output, max_output_bytes);

  int proto_size = metadata.ByteSize();
  DCHECK(proto_size <= kMaxSerializedProtoBytes);
  if (!writer.WriteU16(static_cast<uint16_t>(proto_size)))
    return false;
  if (!metadata.SerializeToArray(writer.ptr(), writer.remaining()))
    return false;
  if (!writer.Skip(proto_size))
    return false;

  RtpTimeTicks prev_rtp_timestamp;
  for (media::cast::FrameEventList::const_iterator it = frame_events.begin();
       it != frame_events.end();
       ++it) {
    media::cast::proto::AggregatedFrameEvent frame_event(**it);

    // Adjust relative RTP timestamp so that it is relative to previous frame,
    // rather than relative to first RTP timestamp.
    // This is done to improve encoding size.
    const RtpTimeTicks rtp_timestamp =
        prev_rtp_timestamp.Expand(frame_event.relative_rtp_timestamp());
    frame_event.set_relative_rtp_timestamp(
        (rtp_timestamp - prev_rtp_timestamp).lower_32_bits());
    prev_rtp_timestamp = rtp_timestamp;

    proto_size = frame_event.ByteSize();
    DCHECK(proto_size <= kMaxSerializedProtoBytes);

    // Write size of the proto, then write the proto.
    if (!writer.WriteU16(static_cast<uint16_t>(proto_size)))
      return false;
    if (!frame_event.SerializeToArray(writer.ptr(), writer.remaining()))
      return false;
    if (!writer.Skip(proto_size))
      return false;
  }

  // Write packet events.
  prev_rtp_timestamp = RtpTimeTicks();
  for (media::cast::PacketEventList::const_iterator it = packet_events.begin();
       it != packet_events.end();
       ++it) {
    media::cast::proto::AggregatedPacketEvent packet_event(**it);

    const RtpTimeTicks rtp_timestamp =
        prev_rtp_timestamp.Expand(packet_event.relative_rtp_timestamp());
    packet_event.set_relative_rtp_timestamp(
        (rtp_timestamp - prev_rtp_timestamp).lower_32_bits());
    prev_rtp_timestamp = rtp_timestamp;

    proto_size = packet_event.ByteSize();
    DCHECK(proto_size <= kMaxSerializedProtoBytes);

    // Write size of the proto, then write the proto.
    if (!writer.WriteU16(static_cast<uint16_t>(proto_size)))
      return false;
    if (!packet_event.SerializeToArray(writer.ptr(), writer.remaining()))
      return false;
    if (!writer.Skip(proto_size))
      return false;
  }

  *output_bytes = max_output_bytes - writer.remaining();
  return true;
}

bool Compress(char* uncompressed_buffer,
              int uncompressed_bytes,
              int max_output_bytes,
              char* output,
              int* output_bytes) {
  z_stream stream = {0};
  int result = deflateInit2(&stream,
                            Z_DEFAULT_COMPRESSION,
                            Z_DEFLATED,
                            // 16 is added to produce a gzip header + trailer.
                            MAX_WBITS + 16,
                            8,  // memLevel = 8 is default.
                            Z_DEFAULT_STRATEGY);
  DCHECK_EQ(Z_OK, result);

  stream.next_in = reinterpret_cast<uint8_t*>(uncompressed_buffer);
  stream.avail_in = uncompressed_bytes;
  stream.next_out = reinterpret_cast<uint8_t*>(output);
  stream.avail_out = max_output_bytes;

  // Do a one-shot compression. This will return Z_STREAM_END only if |output|
  // is large enough to hold all compressed data.
  result = deflate(&stream, Z_FINISH);
  bool success = (result == Z_STREAM_END);

  if (!success)
    DVLOG(2) << "deflate() failed. Result: " << result;

  result = deflateEnd(&stream);
  DCHECK(result == Z_OK || result == Z_DATA_ERROR);

  if (success)
    *output_bytes = max_output_bytes - stream.avail_out;

  return success;
}

}  // namespace

bool SerializeEvents(const LogMetadata& log_metadata,
                     const FrameEventList& frame_events,
                     const PacketEventList& packet_events,
                     bool compress,
                     int max_output_bytes,
                     char* output,
                     int* output_bytes) {
  DCHECK_GT(max_output_bytes, 0);
  DCHECK(output);
  DCHECK(output_bytes);

  if (compress) {
    // Allocate a reasonably large temp buffer to hold uncompressed data.
    scoped_ptr<char[]> uncompressed_buffer(new char[kMaxUncompressedBytes]);
    int uncompressed_bytes;
    bool success = DoSerializeEvents(log_metadata,
                                     frame_events,
                                     packet_events,
                                     kMaxUncompressedBytes,
                                     uncompressed_buffer.get(),
                                     &uncompressed_bytes);
    if (!success)
      return false;
    return Compress(uncompressed_buffer.get(),
                    uncompressed_bytes,
                    max_output_bytes,
                    output,
                    output_bytes);
  } else {
    return DoSerializeEvents(log_metadata,
                             frame_events,
                             packet_events,
                             max_output_bytes,
                             output,
                             output_bytes);
  }
}

}  // namespace cast
}  // namespace media