blob: d5bbfb5e604cf06cc03879a0f3ac97553706dbdd [file] [log] [blame]
// 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 <memory>
#include "base/big_endian.h"
#include "base/logging.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.
std::unique_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