Skip to content

Commit

Permalink
Updates for live packaging (#1)
Browse files Browse the repository at this point in the history
  • Loading branch information
chenda6 authored Oct 31, 2023
1 parent 2038339 commit 8644245
Show file tree
Hide file tree
Showing 6 changed files with 348 additions and 8 deletions.
100 changes: 100 additions & 0 deletions include/packager/live_packager.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// Copyright TBD
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd

#ifndef PACKAGER_LIVE_PACKAGER_H_
#define PACKAGER_LIVE_PACKAGER_H_

#include <memory>
#include <string>
#include <packager/packager.h>

namespace shaka {

class Segment {
public:
virtual ~Segment() = default;

virtual const uint8_t *Data() const = 0;
virtual size_t Size() const = 0;
};


class SegmentData final : public Segment {
public:
SegmentData(const uint8_t *data, size_t size);
~SegmentData() = default;

virtual const uint8_t *Data() const override;
virtual size_t Size() const override;

private:
const uint8_t *data_ = nullptr;
const size_t size_ = 0;
};

class FullSegmentBuffer final : public Segment {
public:
FullSegmentBuffer() = default;
~FullSegmentBuffer() = default;

void SetInitSegment(const uint8_t *data, size_t size);
void AppendData(const uint8_t *data, size_t size);

const uint8_t *InitSegmentData() const;
const uint8_t *SegmentData() const;

size_t InitSegmentSize() const;
size_t SegmentSize() const;

virtual const uint8_t *Data() const override;
virtual size_t Size() const override;

private:
// 'buffer' is expected to contain both the init and data segments, i.e.,
// (ftyp + moov) + (moof + mdat)
std::vector<uint8_t> buffer_;
// Indicates the how much the init segment occupies buffer_
size_t init_segment_size_ = 0;
};

struct LiveConfig {
enum class OutputFormat {
FMP4,
TS,
};

enum class TrackType {
AUDIO,
VIDEO,
};

OutputFormat format;
TrackType track_type;
// TOOD: do we need non-integer durations?
double segment_duration_sec;
};

class LivePackager {
public:
LivePackager(const LiveConfig &config);
~LivePackager();

/// Performs packaging of segment data.
/// @param full_segment contains the full segment data (init + media).
/// @param output contains the packaged segment data (init + media).
/// @return OK on success, an appropriate error code on failure.
Status Package(const Segment &full_segment, FullSegmentBuffer &output);

LivePackager(const LivePackager&) = delete;
LivePackager& operator=(const LivePackager&) = delete;

private:
LiveConfig config_;
};

} // namespace shaka

#endif // PACKAGER_LIVE_PACKAGER_H_
2 changes: 2 additions & 0 deletions packager/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,9 @@ set(libpackager_sources
app/single_thread_job_manager.cc
app/single_thread_job_manager.h
packager.cc
live_packager.cc
../include/packager/packager.h
../include/packager/live_packager.h
)

set(libpackager_deps
Expand Down
180 changes: 180 additions & 0 deletions packager/live_packager.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
// Copyright 2020 Google LLC. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd

#include <fstream>
#include <sstream>
#include <cstdint>
#include <cstring>
#include <algorithm>

#include <absl/log/globals.h>
#include <packager/packager.h>
#include <packager/file.h>
#include <packager/file/file_closer.h>
#include <packager/live_packager.h>
#include <packager/chunking_params.h>

namespace shaka {

namespace {

using StreamDescriptors = std::vector<shaka::StreamDescriptor>;

const std::string INPUT_FNAME = "memory://input_file";
const std::string INIT_SEGMENT_FNAME = "init.mp4";

std::string getSegmentTemplate(const LiveConfig &config) {
return LiveConfig::OutputFormat::TS == config.format ? "$Number$.ts" : "$Number$.m4s";
}

std::string getStreamSelector(const LiveConfig &config) {
return LiveConfig::TrackType::VIDEO == config.track_type ? "video" : "audio";
}

StreamDescriptors setupStreamDescriptors(const LiveConfig &config,
const BufferCallbackParams &cb_params,
const BufferCallbackParams &init_cb_params) {
shaka::StreamDescriptor desc;

desc.input = File::MakeCallbackFileName(cb_params, INPUT_FNAME);

desc.stream_selector = getStreamSelector(config);

if(LiveConfig::OutputFormat::FMP4 == config.format) {
// init segment
desc.output = File::MakeCallbackFileName(init_cb_params, INIT_SEGMENT_FNAME);
}

desc.segment_template =
File::MakeCallbackFileName(cb_params, getSegmentTemplate(config));

return StreamDescriptors { desc };
}

class SegmentDataReader{
public:
SegmentDataReader(const Segment &segment)
: segment_(segment) {
}

uint64_t Read(void *buffer, uint64_t size) {
if (position_ >= segment_.Size()) {
return 0;
}

const uint64_t bytes_to_read = std::min(size, segment_.Size() - position_);
memcpy(buffer, segment_.Data() + position_, bytes_to_read);

position_ += bytes_to_read;
return bytes_to_read;
}

private:
const Segment &segment_;
uint64_t position_ = 0;
};

} // namespace

SegmentData::SegmentData(const uint8_t *data, size_t size)
: data_(data), size_(size) {
}

const uint8_t *SegmentData::Data() const {
return data_;
}

size_t SegmentData::Size() const {
return size_;
}

void FullSegmentBuffer::SetInitSegment(const uint8_t *data, size_t size) {
buffer_.clear();
std::copy(data, data + size, std::back_inserter(buffer_));
init_segment_size_ = size;
}

void FullSegmentBuffer::AppendData(const uint8_t *data, size_t size) {
std::copy(data, data + size, std::back_inserter(buffer_));
}

const uint8_t *FullSegmentBuffer::InitSegmentData() const {
return buffer_.data();
}

const uint8_t *FullSegmentBuffer::SegmentData() const {
return buffer_.data() + InitSegmentSize();
}

size_t FullSegmentBuffer::InitSegmentSize() const {
return init_segment_size_;
}

size_t FullSegmentBuffer::SegmentSize() const {
return buffer_.size() - init_segment_size_;
}

size_t FullSegmentBuffer::Size() const {
return buffer_.size();
}

const uint8_t *FullSegmentBuffer::Data() const {
return buffer_.data();
}

LivePackager::LivePackager(const LiveConfig &config)
: config_(config) {
absl::SetMinLogLevel(absl::LogSeverityAtLeast::kWarning);
}

LivePackager::~LivePackager() {
}

Status LivePackager::Package(const Segment &in, FullSegmentBuffer &out) {
SegmentDataReader reader(in);
shaka::BufferCallbackParams callback_params;
callback_params.read_func = [&reader](const std::string &name,
void *buffer,
uint64_t size) {
return reader.Read(buffer, size);
};

callback_params.write_func = [&out](const std::string &name,
const void *data,
uint64_t size) {
out.AppendData(reinterpret_cast<const uint8_t *>(data), size);
return size;
};

shaka::BufferCallbackParams init_callback_params;
init_callback_params.write_func = [&out](const std::string &name,
const void *data,
uint64_t size) {
// TODO: this gets called more than once, why?
// TODO: this is a workaround to write this only once
if(out.InitSegmentSize() == 0) {
out.SetInitSegment(reinterpret_cast<const uint8_t *>(data), size);
}
return size;
};

shaka::PackagingParams params;
params.chunking_params.segment_duration_in_seconds = config_.segment_duration_sec;

StreamDescriptors descriptors =
setupStreamDescriptors(config_, callback_params, init_callback_params);

shaka::Packager packager;
shaka::Status status = packager.Initialize(params, descriptors);

if(status != Status::OK) {
return status;
}

return packager.Run();
}

} // namespace shaka
54 changes: 49 additions & 5 deletions packager/media/formats/mp4/box_definitions.cc
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

#include <algorithm>
#include <limits>
#include <vector>

#include <absl/flags/flag.h>
#include <absl/log/check.h>
Expand Down Expand Up @@ -75,6 +76,9 @@ bool IsIvSizeValid(uint8_t per_sample_iv_size) {
// bit(5) Reserved // 0
const uint8_t kDdtsExtraData[] = {0xe4, 0x7c, 0, 4, 0, 0x0f, 0};

const std::vector<uint8_t> kTfxdBoxUUID = {0x6d, 0x1d, 0x9b, 0x05, 0x42, 0xd5, 0x44, 0xe6,
0x80, 0xe2, 0x14, 0x1d, 0xaf, 0xf7, 0x57, 0xb2};

// Utility functions to check if the 64bit integers can fit in 32bit integer.
bool IsFitIn32Bits(uint64_t a) {
return a <= std::numeric_limits<uint32_t>::max();
Expand Down Expand Up @@ -2523,6 +2527,35 @@ size_t TrackFragmentDecodeTime::ComputeSizeInternal() {
return HeaderSize() + sizeof(uint32_t) * (1 + version);
}

SmoothUUID::SmoothUUID() = default;
SmoothUUID::~SmoothUUID() = default;

FourCC SmoothUUID::BoxType() const {
return FOURCC_uuid;
}

bool SmoothUUID::ReadWriteInternal(BoxBuffer* buffer) {
// Read and compare 16-bytes of uuid to check for existence a 'tfxd' box
std::vector<uint8_t> uuid(kTfxdBoxUUID.size());
if(!buffer->ReadWriteVector(&uuid, uuid.size()) && (kTfxdBoxUUID != uuid)) {
return true;
}

RCHECK(ReadWriteHeaderInternal(buffer));
size_t num_bytes = (version == 1) ? sizeof(uint64_t) : sizeof(uint32_t);

RCHECK(buffer->ReadWriteUInt64NBytes(&time, num_bytes));
RCHECK(buffer->ReadWriteUInt64NBytes(&duration, num_bytes));
tfxd_exists = true;

return true;
}

size_t SmoothUUID::ComputeSizeInternal() {
version = IsFitIn32Bits(duration) ? 0 : 1;
return HeaderSize() + sizeof(uint32_t) * (1 + version);
}

MovieFragmentHeader::MovieFragmentHeader() = default;
MovieFragmentHeader::~MovieFragmentHeader() = default;

Expand Down Expand Up @@ -2732,15 +2765,26 @@ bool TrackFragment::ReadWriteInternal(BoxBuffer* buffer) {
buffer->ReadWriteChild(&header));
if (buffer->Reading()) {
DCHECK(buffer->reader());
decode_time_absent = !buffer->reader()->ChildExist(&decode_time);
if (!decode_time_absent)
RCHECK(buffer->ReadWriteChild(&decode_time));
uuid_exists = buffer->reader()->ChildExist(&smooth_uuid);
decode_time_absent = !buffer->reader()->ChildExist(&decode_time) && !uuid_exists;
if (!decode_time_absent) {
if(uuid_exists) {
RCHECK(buffer->ReadWriteChild(&smooth_uuid));
} else {
RCHECK(buffer->ReadWriteChild(&decode_time));
}
}
RCHECK(buffer->reader()->TryReadChildren(&runs) &&
buffer->reader()->TryReadChildren(&sample_group_descriptions) &&
buffer->reader()->TryReadChildren(&sample_to_groups));
} else {
if (!decode_time_absent)
RCHECK(buffer->ReadWriteChild(&decode_time));
if (!decode_time_absent) {
if(uuid_exists) {
RCHECK(buffer->ReadWriteChild(&smooth_uuid));
} else {
RCHECK(buffer->ReadWriteChild(&decode_time));
}
}
for (uint32_t i = 0; i < runs.size(); ++i)
RCHECK(buffer->ReadWriteChild(&runs[i]));
for (uint32_t i = 0; i < sample_to_groups.size(); ++i)
Expand Down
Loading

0 comments on commit 8644245

Please sign in to comment.