399 lines
13 KiB
C++
399 lines
13 KiB
C++
// 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 <packager/media/formats/webm/tracks_builder.h>
|
|
|
|
#include <absl/log/check.h>
|
|
#include <absl/log/log.h>
|
|
|
|
#include <packager/media/formats/webm/webm_constants.h>
|
|
|
|
namespace shaka {
|
|
namespace media {
|
|
|
|
// Returns size of an integer, formatted using Matroska serialization.
|
|
static int GetUIntMkvSize(uint64_t value) {
|
|
if (value < 0x07FULL)
|
|
return 1;
|
|
if (value < 0x03FFFULL)
|
|
return 2;
|
|
if (value < 0x01FFFFFULL)
|
|
return 3;
|
|
if (value < 0x0FFFFFFFULL)
|
|
return 4;
|
|
if (value < 0x07FFFFFFFFULL)
|
|
return 5;
|
|
if (value < 0x03FFFFFFFFFFULL)
|
|
return 6;
|
|
if (value < 0x01FFFFFFFFFFFFULL)
|
|
return 7;
|
|
return 8;
|
|
}
|
|
|
|
// Returns the minimium size required to serialize an integer value.
|
|
static int GetUIntSize(uint64_t value) {
|
|
if (value < 0x0100ULL)
|
|
return 1;
|
|
if (value < 0x010000ULL)
|
|
return 2;
|
|
if (value < 0x01000000ULL)
|
|
return 3;
|
|
if (value < 0x0100000000ULL)
|
|
return 4;
|
|
if (value < 0x010000000000ULL)
|
|
return 5;
|
|
if (value < 0x01000000000000ULL)
|
|
return 6;
|
|
if (value < 0x0100000000000000ULL)
|
|
return 7;
|
|
return 8;
|
|
}
|
|
|
|
static int MasterElementSize(int element_id, int payload_size) {
|
|
return GetUIntSize(element_id) + GetUIntMkvSize(payload_size) + payload_size;
|
|
}
|
|
|
|
static int UIntElementSize(int element_id, uint64_t value) {
|
|
return GetUIntSize(element_id) + 1 + GetUIntSize(value);
|
|
}
|
|
|
|
static int DoubleElementSize(int element_id) {
|
|
return GetUIntSize(element_id) + 1 + 8;
|
|
}
|
|
|
|
static int StringElementSize(int element_id, const std::string& value) {
|
|
return GetUIntSize(element_id) + GetUIntMkvSize(value.length()) +
|
|
static_cast<int>(value.length());
|
|
}
|
|
|
|
static void SerializeInt(uint8_t** buf_ptr,
|
|
int* buf_size_ptr,
|
|
int64_t value,
|
|
int size) {
|
|
uint8_t*& buf = *buf_ptr;
|
|
int& buf_size = *buf_size_ptr;
|
|
|
|
for (int idx = 1; idx <= size; ++idx) {
|
|
*buf++ = static_cast<uint8_t>(value >> ((size - idx) * 8));
|
|
--buf_size;
|
|
}
|
|
}
|
|
|
|
static void SerializeDouble(uint8_t** buf_ptr,
|
|
int* buf_size_ptr,
|
|
double value) {
|
|
// Use a union to convert |value| to native endian integer bit pattern.
|
|
union {
|
|
double src;
|
|
int64_t dst;
|
|
} tmp;
|
|
tmp.src = value;
|
|
|
|
// Write the bytes from native endian |tmp.dst| to big-endian form in |buf|.
|
|
SerializeInt(buf_ptr, buf_size_ptr, tmp.dst, 8);
|
|
}
|
|
|
|
static void WriteElementId(uint8_t** buf, int* buf_size, int element_id) {
|
|
SerializeInt(buf, buf_size, element_id, GetUIntSize(element_id));
|
|
}
|
|
|
|
static void WriteUInt(uint8_t** buf, int* buf_size, uint64_t value) {
|
|
const int size = GetUIntMkvSize(value);
|
|
value |= (1ULL << (size * 7)); // Matroska formatting
|
|
SerializeInt(buf, buf_size, value, size);
|
|
}
|
|
|
|
static void WriteMasterElement(uint8_t** buf,
|
|
int* buf_size,
|
|
int element_id,
|
|
int payload_size) {
|
|
WriteElementId(buf, buf_size, element_id);
|
|
WriteUInt(buf, buf_size, payload_size);
|
|
}
|
|
|
|
static void WriteUIntElement(uint8_t** buf,
|
|
int* buf_size,
|
|
int element_id,
|
|
uint64_t value) {
|
|
WriteElementId(buf, buf_size, element_id);
|
|
|
|
const int size = GetUIntSize(value);
|
|
WriteUInt(buf, buf_size, size);
|
|
|
|
SerializeInt(buf, buf_size, value, size);
|
|
}
|
|
|
|
static void WriteDoubleElement(uint8_t** buf,
|
|
int* buf_size,
|
|
int element_id,
|
|
double value) {
|
|
WriteElementId(buf, buf_size, element_id);
|
|
WriteUInt(buf, buf_size, 8);
|
|
SerializeDouble(buf, buf_size, value);
|
|
}
|
|
|
|
static void WriteStringElement(uint8_t** buf_ptr,
|
|
int* buf_size_ptr,
|
|
int element_id,
|
|
const std::string& value) {
|
|
uint8_t*& buf = *buf_ptr;
|
|
int& buf_size = *buf_size_ptr;
|
|
|
|
WriteElementId(&buf, &buf_size, element_id);
|
|
|
|
const uint64_t size = value.length();
|
|
WriteUInt(&buf, &buf_size, size);
|
|
|
|
memcpy(buf, value.data(), size);
|
|
buf += size;
|
|
buf_size -= size;
|
|
}
|
|
|
|
TracksBuilder::TracksBuilder(bool allow_invalid_values)
|
|
: allow_invalid_values_(allow_invalid_values) {}
|
|
TracksBuilder::TracksBuilder()
|
|
: allow_invalid_values_(false) {}
|
|
TracksBuilder::~TracksBuilder() {}
|
|
|
|
void TracksBuilder::AddVideoTrack(int track_num,
|
|
uint64_t track_uid,
|
|
const std::string& codec_id,
|
|
const std::string& name,
|
|
const std::string& language,
|
|
int default_duration,
|
|
int video_pixel_width,
|
|
int video_pixel_height) {
|
|
AddTrackInternal(track_num, kWebMTrackTypeVideo, track_uid, codec_id, name,
|
|
language, default_duration, video_pixel_width,
|
|
video_pixel_height, -1, -1);
|
|
}
|
|
|
|
void TracksBuilder::AddAudioTrack(int track_num,
|
|
uint64_t track_uid,
|
|
const std::string& codec_id,
|
|
const std::string& name,
|
|
const std::string& language,
|
|
int default_duration,
|
|
int audio_channels,
|
|
double audio_sampling_frequency) {
|
|
AddTrackInternal(track_num, kWebMTrackTypeAudio, track_uid, codec_id, name,
|
|
language, default_duration, -1, -1, audio_channels,
|
|
audio_sampling_frequency);
|
|
}
|
|
|
|
void TracksBuilder::AddTextTrack(int track_num,
|
|
uint64_t track_uid,
|
|
const std::string& codec_id,
|
|
const std::string& name,
|
|
const std::string& language) {
|
|
AddTrackInternal(track_num, kWebMTrackTypeSubtitlesOrCaptions, track_uid,
|
|
codec_id, name, language, -1, -1, -1, -1, -1);
|
|
}
|
|
|
|
std::vector<uint8_t> TracksBuilder::Finish() {
|
|
// Allocate the storage
|
|
std::vector<uint8_t> buffer;
|
|
buffer.resize(GetTracksSize());
|
|
|
|
// Populate the storage with a tracks header
|
|
WriteTracks(&buffer[0], static_cast<int>(buffer.size()));
|
|
|
|
return buffer;
|
|
}
|
|
|
|
void TracksBuilder::AddTrackInternal(int track_num,
|
|
int track_type,
|
|
uint64_t track_uid,
|
|
const std::string& codec_id,
|
|
const std::string& name,
|
|
const std::string& language,
|
|
int default_duration,
|
|
int video_pixel_width,
|
|
int video_pixel_height,
|
|
int audio_channels,
|
|
double audio_sampling_frequency) {
|
|
tracks_.push_back(Track(track_num, track_type, track_uid, codec_id, name,
|
|
language, default_duration, video_pixel_width,
|
|
video_pixel_height, audio_channels,
|
|
audio_sampling_frequency, allow_invalid_values_));
|
|
}
|
|
|
|
int TracksBuilder::GetTracksSize() const {
|
|
return MasterElementSize(kWebMIdTracks, GetTracksPayloadSize());
|
|
}
|
|
|
|
int TracksBuilder::GetTracksPayloadSize() const {
|
|
int payload_size = 0;
|
|
|
|
for (TrackList::const_iterator itr = tracks_.begin();
|
|
itr != tracks_.end(); ++itr) {
|
|
payload_size += itr->GetSize();
|
|
}
|
|
|
|
return payload_size;
|
|
}
|
|
|
|
void TracksBuilder::WriteTracks(uint8_t* buf, int buf_size) const {
|
|
WriteMasterElement(&buf, &buf_size, kWebMIdTracks, GetTracksPayloadSize());
|
|
|
|
for (TrackList::const_iterator itr = tracks_.begin();
|
|
itr != tracks_.end(); ++itr) {
|
|
itr->Write(&buf, &buf_size);
|
|
}
|
|
}
|
|
|
|
TracksBuilder::Track::Track(int track_num,
|
|
int track_type,
|
|
uint64_t track_uid,
|
|
const std::string& codec_id,
|
|
const std::string& name,
|
|
const std::string& language,
|
|
int default_duration,
|
|
int video_pixel_width,
|
|
int video_pixel_height,
|
|
int audio_channels,
|
|
double audio_sampling_frequency,
|
|
bool allow_invalid_values)
|
|
: track_num_(track_num),
|
|
track_type_(track_type),
|
|
track_uid_(track_uid),
|
|
codec_id_(codec_id),
|
|
name_(name),
|
|
language_(language),
|
|
default_duration_(default_duration),
|
|
video_pixel_width_(video_pixel_width),
|
|
video_pixel_height_(video_pixel_height),
|
|
audio_channels_(audio_channels),
|
|
audio_sampling_frequency_(audio_sampling_frequency) {
|
|
if (!allow_invalid_values) {
|
|
CHECK_GT(track_num_, 0);
|
|
CHECK_GT(track_type_, 0);
|
|
CHECK_LT(track_type_, 255);
|
|
CHECK_GT(track_uid_, 0);
|
|
if (track_type != kWebMTrackTypeVideo &&
|
|
track_type != kWebMTrackTypeAudio) {
|
|
CHECK_EQ(default_duration_, -1);
|
|
} else {
|
|
CHECK(default_duration_ == -1 || default_duration_ > 0);
|
|
}
|
|
|
|
if (track_type == kWebMTrackTypeVideo) {
|
|
CHECK_GT(video_pixel_width_, 0);
|
|
CHECK_GT(video_pixel_height_, 0);
|
|
} else {
|
|
CHECK_EQ(video_pixel_width_, -1);
|
|
CHECK_EQ(video_pixel_height_, -1);
|
|
}
|
|
|
|
if (track_type == kWebMTrackTypeAudio) {
|
|
CHECK_GT(audio_channels_, 0);
|
|
CHECK_GT(audio_sampling_frequency_, 0.0);
|
|
} else {
|
|
CHECK_EQ(audio_channels_, -1);
|
|
CHECK_EQ(audio_sampling_frequency_, -1.0);
|
|
}
|
|
}
|
|
}
|
|
|
|
int TracksBuilder::Track::GetSize() const {
|
|
return MasterElementSize(kWebMIdTrackEntry, GetPayloadSize());
|
|
}
|
|
|
|
int TracksBuilder::Track::GetVideoPayloadSize() const {
|
|
int payload_size = 0;
|
|
|
|
if (video_pixel_width_ >= 0)
|
|
payload_size += UIntElementSize(kWebMIdPixelWidth, video_pixel_width_);
|
|
if (video_pixel_height_ >= 0)
|
|
payload_size += UIntElementSize(kWebMIdPixelHeight, video_pixel_height_);
|
|
|
|
return payload_size;
|
|
}
|
|
|
|
int TracksBuilder::Track::GetAudioPayloadSize() const {
|
|
int payload_size = 0;
|
|
|
|
if (audio_channels_ >= 0)
|
|
payload_size += UIntElementSize(kWebMIdChannels, audio_channels_);
|
|
if (audio_sampling_frequency_ >= 0)
|
|
payload_size += DoubleElementSize(kWebMIdSamplingFrequency);
|
|
|
|
return payload_size;
|
|
}
|
|
|
|
int TracksBuilder::Track::GetPayloadSize() const {
|
|
int size = 0;
|
|
|
|
size += UIntElementSize(kWebMIdTrackNumber, track_num_);
|
|
size += UIntElementSize(kWebMIdTrackType, track_type_);
|
|
size += UIntElementSize(kWebMIdTrackUID, track_uid_);
|
|
|
|
if (default_duration_ >= 0)
|
|
size += UIntElementSize(kWebMIdDefaultDuration, default_duration_);
|
|
|
|
if (!codec_id_.empty())
|
|
size += StringElementSize(kWebMIdCodecID, codec_id_);
|
|
|
|
if (!name_.empty())
|
|
size += StringElementSize(kWebMIdName, name_);
|
|
|
|
if (!language_.empty())
|
|
size += StringElementSize(kWebMIdLanguage, language_);
|
|
|
|
if (GetVideoPayloadSize() > 0) {
|
|
size += MasterElementSize(kWebMIdVideo, GetVideoPayloadSize());
|
|
}
|
|
|
|
if (GetAudioPayloadSize() > 0) {
|
|
size += MasterElementSize(kWebMIdAudio, GetAudioPayloadSize());
|
|
}
|
|
|
|
return size;
|
|
}
|
|
|
|
void TracksBuilder::Track::Write(uint8_t** buf, int* buf_size) const {
|
|
WriteMasterElement(buf, buf_size, kWebMIdTrackEntry, GetPayloadSize());
|
|
|
|
WriteUIntElement(buf, buf_size, kWebMIdTrackNumber, track_num_);
|
|
WriteUIntElement(buf, buf_size, kWebMIdTrackType, track_type_);
|
|
WriteUIntElement(buf, buf_size, kWebMIdTrackUID, track_uid_);
|
|
|
|
if (default_duration_ >= 0)
|
|
WriteUIntElement(buf, buf_size, kWebMIdDefaultDuration, default_duration_);
|
|
|
|
if (!codec_id_.empty())
|
|
WriteStringElement(buf, buf_size, kWebMIdCodecID, codec_id_);
|
|
|
|
if (!name_.empty())
|
|
WriteStringElement(buf, buf_size, kWebMIdName, name_);
|
|
|
|
if (!language_.empty())
|
|
WriteStringElement(buf, buf_size, kWebMIdLanguage, language_);
|
|
|
|
if (GetVideoPayloadSize() > 0) {
|
|
WriteMasterElement(buf, buf_size, kWebMIdVideo, GetVideoPayloadSize());
|
|
|
|
if (video_pixel_width_ >= 0)
|
|
WriteUIntElement(buf, buf_size, kWebMIdPixelWidth, video_pixel_width_);
|
|
|
|
if (video_pixel_height_ >= 0)
|
|
WriteUIntElement(buf, buf_size, kWebMIdPixelHeight, video_pixel_height_);
|
|
}
|
|
|
|
if (GetAudioPayloadSize() > 0) {
|
|
WriteMasterElement(buf, buf_size, kWebMIdAudio, GetAudioPayloadSize());
|
|
|
|
if (audio_channels_ >= 0)
|
|
WriteUIntElement(buf, buf_size, kWebMIdChannels, audio_channels_);
|
|
|
|
if (audio_sampling_frequency_ >= 0) {
|
|
WriteDoubleElement(buf, buf_size, kWebMIdSamplingFrequency,
|
|
audio_sampling_frequency_);
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace media
|
|
} // namespace shaka
|