1a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)// Copyright 2014 The Chromium Authors. All rights reserved.
2a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be
3a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)// found in the LICENSE file.
4a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)//
5a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)// The serialization format is as follows:
65c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu//   16-bit integer describing the following LogMetadata proto size in bytes.
75c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu//   The LogMetadata proto.
8a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)//   32-bit integer describing number of frame events.
9a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)//   (The following repeated for number of frame events):
10a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)//     16-bit integer describing the following AggregatedFrameEvent proto size
11a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)//         in bytes.
12a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)//     The AggregatedFrameEvent proto.
13a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)//   32-bit integer describing number of packet events.
14a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)//   (The following repeated for number of packet events):
15a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)//     16-bit integer describing the following AggregatedPacketEvent proto
16a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)//         size in bytes.
17a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)//     The AggregatedPacketEvent proto.
18a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
19a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)#include "media/cast/logging/log_serializer.h"
20a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
21a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)#include "base/big_endian.h"
225c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu#include "base/logging.h"
235c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu#include "base/memory/scoped_ptr.h"
24a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)#include "third_party/zlib/zlib.h"
25a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
26a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)namespace media {
27a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)namespace cast {
28a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
29a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)namespace {
30a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
31a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)using media::cast::proto::AggregatedFrameEvent;
32a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)using media::cast::proto::AggregatedPacketEvent;
33a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)using media::cast::proto::LogMetadata;
34a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
35a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)// Use 30MB of temp buffer to hold uncompressed data if |compress| is true.
36a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)const int kMaxUncompressedBytes = 30 * 1000 * 1000;
37a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
385c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu// The maximum allowed size per serialized proto.
395c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liuconst int kMaxSerializedProtoBytes = (1 << 16) - 1;
40a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)bool DoSerializeEvents(const LogMetadata& metadata,
415c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu                       const FrameEventList& frame_events,
425c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu                       const PacketEventList& packet_events,
43a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)                       const int max_output_bytes,
44a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)                       char* output,
45a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)                       int* output_bytes) {
46a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  base::BigEndianWriter writer(output, max_output_bytes);
47a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
48a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  int proto_size = metadata.ByteSize();
495c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  DCHECK(proto_size <= kMaxSerializedProtoBytes);
50a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  if (!writer.WriteU16(proto_size))
51a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    return false;
52a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  if (!metadata.SerializeToArray(writer.ptr(), writer.remaining()))
53a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    return false;
54a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  if (!writer.Skip(proto_size))
55a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    return false;
56a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
57a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  RtpTimestamp prev_rtp_timestamp = 0;
585c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  for (media::cast::FrameEventList::const_iterator it = frame_events.begin();
59a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)       it != frame_events.end();
60a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)       ++it) {
615c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    media::cast::proto::AggregatedFrameEvent frame_event(**it);
62a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
63a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    // Adjust relative RTP timestamp so that it is relative to previous frame,
64a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    // rather than relative to first RTP timestamp.
65a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    // This is done to improve encoding size.
665c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    RtpTimestamp old_relative_rtp_timestamp =
675c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        frame_event.relative_rtp_timestamp();
68a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    frame_event.set_relative_rtp_timestamp(
695c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        old_relative_rtp_timestamp - prev_rtp_timestamp);
705c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    prev_rtp_timestamp = old_relative_rtp_timestamp;
71a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
72a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    proto_size = frame_event.ByteSize();
735c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    DCHECK(proto_size <= kMaxSerializedProtoBytes);
74a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
75a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    // Write size of the proto, then write the proto.
76a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    if (!writer.WriteU16(proto_size))
77a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      return false;
78a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    if (!frame_event.SerializeToArray(writer.ptr(), writer.remaining()))
79a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      return false;
80a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    if (!writer.Skip(proto_size))
81a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      return false;
82a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  }
83a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
84a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  // Write packet events.
85a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  prev_rtp_timestamp = 0;
865c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  for (media::cast::PacketEventList::const_iterator it = packet_events.begin();
87a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)       it != packet_events.end();
88a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)       ++it) {
895c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    media::cast::proto::AggregatedPacketEvent packet_event(**it);
905c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    RtpTimestamp old_relative_rtp_timestamp =
915c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        packet_event.relative_rtp_timestamp();
92a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    packet_event.set_relative_rtp_timestamp(
935c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        old_relative_rtp_timestamp - prev_rtp_timestamp);
945c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    prev_rtp_timestamp = old_relative_rtp_timestamp;
95a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
96a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    proto_size = packet_event.ByteSize();
975c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    DCHECK(proto_size <= kMaxSerializedProtoBytes);
98a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
99a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    // Write size of the proto, then write the proto.
100a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    if (!writer.WriteU16(proto_size))
101a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      return false;
102a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    if (!packet_event.SerializeToArray(writer.ptr(), writer.remaining()))
103a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      return false;
104a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    if (!writer.Skip(proto_size))
105a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      return false;
106a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  }
107a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
108a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  *output_bytes = max_output_bytes - writer.remaining();
109a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  return true;
110a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)}
111a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
112a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)bool Compress(char* uncompressed_buffer,
113a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)              int uncompressed_bytes,
114a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)              int max_output_bytes,
115a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)              char* output,
116a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)              int* output_bytes) {
117a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  z_stream stream = {0};
118a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  int result = deflateInit2(&stream,
119a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)                            Z_DEFAULT_COMPRESSION,
120a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)                            Z_DEFLATED,
121a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)                            // 16 is added to produce a gzip header + trailer.
122a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)                            MAX_WBITS + 16,
123a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)                            8,  // memLevel = 8 is default.
124a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)                            Z_DEFAULT_STRATEGY);
125a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  DCHECK_EQ(Z_OK, result);
126a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
127a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  stream.next_in = reinterpret_cast<uint8*>(uncompressed_buffer);
128a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  stream.avail_in = uncompressed_bytes;
129a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  stream.next_out = reinterpret_cast<uint8*>(output);
130a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  stream.avail_out = max_output_bytes;
131a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
132a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  // Do a one-shot compression. This will return Z_STREAM_END only if |output|
133a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  // is large enough to hold all compressed data.
134a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  result = deflate(&stream, Z_FINISH);
135a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  bool success = (result == Z_STREAM_END);
136a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
137a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  if (!success)
138a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    DVLOG(2) << "deflate() failed. Result: " << result;
139a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
140a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  result = deflateEnd(&stream);
141a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  DCHECK(result == Z_OK || result == Z_DATA_ERROR);
142a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
143a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  if (success)
144a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    *output_bytes = max_output_bytes - stream.avail_out;
145a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
146a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  return success;
147a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)}
148a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
149a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)}  // namespace
150a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
151a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)bool SerializeEvents(const LogMetadata& log_metadata,
1525c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu                     const FrameEventList& frame_events,
1535c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu                     const PacketEventList& packet_events,
154a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)                     bool compress,
155a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)                     int max_output_bytes,
156a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)                     char* output,
157a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)                     int* output_bytes) {
158a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  DCHECK_GT(max_output_bytes, 0);
159a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  DCHECK(output);
160a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  DCHECK(output_bytes);
161a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
162a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  if (compress) {
163a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    // Allocate a reasonably large temp buffer to hold uncompressed data.
164a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    scoped_ptr<char[]> uncompressed_buffer(new char[kMaxUncompressedBytes]);
165a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    int uncompressed_bytes;
166a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    bool success = DoSerializeEvents(log_metadata,
167a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)                                     frame_events,
168a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)                                     packet_events,
169a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)                                     kMaxUncompressedBytes,
170a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)                                     uncompressed_buffer.get(),
171a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)                                     &uncompressed_bytes);
172a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    if (!success)
173a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      return false;
174a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    return Compress(uncompressed_buffer.get(),
175a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)                    uncompressed_bytes,
176a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)                    max_output_bytes,
177a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)                    output,
178a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)                    output_bytes);
179a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  } else {
180a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    return DoSerializeEvents(log_metadata,
181a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)                             frame_events,
182a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)                             packet_events,
183a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)                             max_output_bytes,
184a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)                             output,
185a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)                             output_bytes);
186a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  }
187a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)}
188a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
189a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)}  // namespace cast
190a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)}  // namespace media
191