feat: Add support for single file TS for HLS (#934)
This is based on comments at https://github.com/google/shaka-packager/pull/891. The muxer is deciding whether to write to a single file or a segment file based on the configuration. Example: ``` ../packager 'in=TOS.ts,stream=video,output=tos_video.ts,playlist_name=tos_video.m3u8' \ 'in=TOS.ts,stream=audio,output=tos_audio.ts,playlist_name=tos_audio.m3u8' \ --hls_master_playlist_output tos.m3u8 ``` Tested the content using Exoplayer. --------- Co-authored-by: Cosmin Stejerean <cstejerean@meta.com>
This commit is contained in:
parent
6acdcc394a
commit
4aa4b4b9aa
|
@ -1597,6 +1597,13 @@ class PackagerFunctionalTest(PackagerAppTest):
|
||||||
self._GetFlags(encryption=True, output_hls=True))
|
self._GetFlags(encryption=True, output_hls=True))
|
||||||
self._CheckTestResults('ec3-and-hls-single-segment-mp4-encrypted')
|
self._CheckTestResults('ec3-and-hls-single-segment-mp4-encrypted')
|
||||||
|
|
||||||
|
def testHlsSingleSegmentTs(self):
|
||||||
|
self.assertPackageSuccess(
|
||||||
|
self._GetStreams(
|
||||||
|
['audio', 'video'], hls=True, test_files=['bear-640x360.ts']),
|
||||||
|
self._GetFlags(output_hls=True))
|
||||||
|
self._CheckTestResults('hls-single-segment-ts')
|
||||||
|
|
||||||
def testEc3PackedAudioEncrypted(self):
|
def testEc3PackedAudioEncrypted(self):
|
||||||
streams = [
|
streams = [
|
||||||
self._GetStream(
|
self._GetStream(
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
#EXTM3U
|
||||||
|
#EXT-X-VERSION:6
|
||||||
|
## Generated with https://github.com/shaka-project/shaka-packager version <tag>-<hash>-<test>
|
||||||
|
#EXT-X-TARGETDURATION:2
|
||||||
|
#EXT-X-PLAYLIST-TYPE:VOD
|
||||||
|
#EXTINF:0.975,
|
||||||
|
#EXT-X-BYTERANGE:23312@0
|
||||||
|
bear-640x360-audio.ts
|
||||||
|
#EXTINF:0.998,
|
||||||
|
#EXT-X-BYTERANGE:24252
|
||||||
|
bear-640x360-audio.ts
|
||||||
|
#EXTINF:0.789,
|
||||||
|
#EXT-X-BYTERANGE:17296
|
||||||
|
bear-640x360-audio.ts
|
||||||
|
#EXT-X-ENDLIST
|
Binary file not shown.
16
packager/app/test/testdata/hls-single-segment-ts/bear-640x360-video-iframe.m3u8
vendored
Normal file
16
packager/app/test/testdata/hls-single-segment-ts/bear-640x360-video-iframe.m3u8
vendored
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
#EXTM3U
|
||||||
|
#EXT-X-VERSION:6
|
||||||
|
## Generated with https://github.com/shaka-project/shaka-packager version <tag>-<hash>-<test>
|
||||||
|
#EXT-X-TARGETDURATION:2
|
||||||
|
#EXT-X-PLAYLIST-TYPE:VOD
|
||||||
|
#EXT-X-I-FRAMES-ONLY
|
||||||
|
#EXTINF:1.001,
|
||||||
|
#EXT-X-BYTERANGE:15604@376
|
||||||
|
bear-640x360-video.ts
|
||||||
|
#EXTINF:1.001,
|
||||||
|
#EXT-X-BYTERANGE:18236@105656
|
||||||
|
bear-640x360-video.ts
|
||||||
|
#EXTINF:0.734,
|
||||||
|
#EXT-X-BYTERANGE:19928@233684
|
||||||
|
bear-640x360-video.ts
|
||||||
|
#EXT-X-ENDLIST
|
|
@ -0,0 +1,15 @@
|
||||||
|
#EXTM3U
|
||||||
|
#EXT-X-VERSION:6
|
||||||
|
## Generated with https://github.com/shaka-project/shaka-packager version <tag>-<hash>-<test>
|
||||||
|
#EXT-X-TARGETDURATION:2
|
||||||
|
#EXT-X-PLAYLIST-TYPE:VOD
|
||||||
|
#EXTINF:1.001,
|
||||||
|
#EXT-X-BYTERANGE:105280@0
|
||||||
|
bear-640x360-video.ts
|
||||||
|
#EXTINF:1.001,
|
||||||
|
#EXT-X-BYTERANGE:128028
|
||||||
|
bear-640x360-video.ts
|
||||||
|
#EXTINF:0.734,
|
||||||
|
#EXT-X-BYTERANGE:84600
|
||||||
|
bear-640x360-video.ts
|
||||||
|
#EXT-X-ENDLIST
|
Binary file not shown.
|
@ -0,0 +1,11 @@
|
||||||
|
#EXTM3U
|
||||||
|
## Generated with https://github.com/shaka-project/shaka-packager version <tag>-<hash>-<test>
|
||||||
|
|
||||||
|
#EXT-X-INDEPENDENT-SEGMENTS
|
||||||
|
|
||||||
|
#EXT-X-MEDIA:TYPE=AUDIO,URI="bear-640x360-audio.m3u8",GROUP-ID="default-audio-group",NAME="stream_0",DEFAULT=NO,AUTOSELECT=YES,CHANNELS="2"
|
||||||
|
|
||||||
|
#EXT-X-STREAM-INF:BANDWIDTH=1217520,AVERAGE-BANDWIDTH=1117320,CODECS="avc1.64001e,mp4a.40.2",RESOLUTION=640x360,FRAME-RATE=29.970,AUDIO="default-audio-group",CLOSED-CAPTIONS=NONE
|
||||||
|
bear-640x360-video.m3u8
|
||||||
|
|
||||||
|
#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=217180,AVERAGE-BANDWIDTH=157213,CODECS="avc1.64001e",RESOLUTION=640x360,CLOSED-CAPTIONS=NONE,URI="bear-640x360-video-iframe.m3u8"
|
|
@ -8,6 +8,9 @@
|
||||||
|
|
||||||
#include <absl/log/check.h>
|
#include <absl/log/check.h>
|
||||||
|
|
||||||
|
#include <packager/macros/status.h>
|
||||||
|
#include <packager/media/base/muxer_util.h>
|
||||||
|
|
||||||
namespace shaka {
|
namespace shaka {
|
||||||
namespace media {
|
namespace media {
|
||||||
namespace mp2t {
|
namespace mp2t {
|
||||||
|
@ -23,6 +26,16 @@ Status TsMuxer::InitializeMuxer() {
|
||||||
if (streams().size() > 1u)
|
if (streams().size() > 1u)
|
||||||
return Status(error::MUXER_FAILURE, "Cannot handle more than one streams.");
|
return Status(error::MUXER_FAILURE, "Cannot handle more than one streams.");
|
||||||
|
|
||||||
|
if (options().segment_template.empty()) {
|
||||||
|
const std::string& file_name = options().output_file_name;
|
||||||
|
DCHECK(!file_name.empty());
|
||||||
|
output_file_.reset(File::Open(file_name.c_str(), "w"));
|
||||||
|
if (!output_file_) {
|
||||||
|
return Status(error::FILE_FAILURE,
|
||||||
|
"Cannot open file for write " + file_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
segmenter_.reset(new TsSegmenter(options(), muxer_listener()));
|
segmenter_.reset(new TsSegmenter(options(), muxer_listener()));
|
||||||
Status status = segmenter_->Initialize(*streams()[0]);
|
Status status = segmenter_->Initialize(*streams()[0]);
|
||||||
FireOnMediaStartEvent();
|
FireOnMediaStartEvent();
|
||||||
|
@ -49,10 +62,81 @@ Status TsMuxer::AddMediaSample(size_t stream_id, const MediaSample& sample) {
|
||||||
Status TsMuxer::FinalizeSegment(size_t stream_id,
|
Status TsMuxer::FinalizeSegment(size_t stream_id,
|
||||||
const SegmentInfo& segment_info) {
|
const SegmentInfo& segment_info) {
|
||||||
DCHECK_EQ(stream_id, 0u);
|
DCHECK_EQ(stream_id, 0u);
|
||||||
return segment_info.is_subsegment
|
|
||||||
? Status::OK
|
if (segment_info.is_subsegment)
|
||||||
: segmenter_->FinalizeSegment(segment_info.start_timestamp,
|
return Status::OK;
|
||||||
|
|
||||||
|
Status s = segmenter_->FinalizeSegment(segment_info.start_timestamp,
|
||||||
segment_info.duration);
|
segment_info.duration);
|
||||||
|
if (!s.ok())
|
||||||
|
return s;
|
||||||
|
if (!segmenter_->segment_started())
|
||||||
|
return Status::OK;
|
||||||
|
|
||||||
|
int64_t segment_start_timestamp = segmenter_->segment_start_timestamp();
|
||||||
|
|
||||||
|
std::string segment_path =
|
||||||
|
options().segment_template.empty()
|
||||||
|
? options().output_file_name
|
||||||
|
: GetSegmentName(options().segment_template, segment_start_timestamp,
|
||||||
|
segment_number_++, options().bandwidth);
|
||||||
|
|
||||||
|
const int64_t file_size = segmenter_->segment_buffer()->Size();
|
||||||
|
|
||||||
|
RETURN_IF_ERROR(WriteSegment(segment_path, segmenter_->segment_buffer()));
|
||||||
|
|
||||||
|
total_duration_ += segment_info.duration;
|
||||||
|
|
||||||
|
if (muxer_listener()) {
|
||||||
|
muxer_listener()->OnNewSegment(
|
||||||
|
segment_path,
|
||||||
|
segment_info.start_timestamp * segmenter_->timescale() +
|
||||||
|
segmenter_->transport_stream_timestamp_offset(),
|
||||||
|
segment_info.duration * segmenter_->timescale(), file_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
segmenter_->set_segment_started(false);
|
||||||
|
|
||||||
|
return Status::OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
Status TsMuxer::WriteSegment(const std::string& segment_path,
|
||||||
|
BufferWriter* segment_buffer) {
|
||||||
|
std::unique_ptr<File, FileCloser> file;
|
||||||
|
|
||||||
|
if (output_file_) {
|
||||||
|
// This is in single segment mode.
|
||||||
|
Range range;
|
||||||
|
range.start = media_ranges_.subsegment_ranges.empty()
|
||||||
|
? 0
|
||||||
|
: (media_ranges_.subsegment_ranges.back().end + 1);
|
||||||
|
range.end = range.start + segment_buffer->Size() - 1;
|
||||||
|
media_ranges_.subsegment_ranges.push_back(range);
|
||||||
|
} else {
|
||||||
|
file.reset(File::Open(segment_path.c_str(), "w"));
|
||||||
|
if (!file) {
|
||||||
|
return Status(error::FILE_FAILURE,
|
||||||
|
"Cannot open file for write " + segment_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RETURN_IF_ERROR(segment_buffer->WriteToFile(output_file_ ? output_file_.get()
|
||||||
|
: file.get()));
|
||||||
|
|
||||||
|
if (file)
|
||||||
|
RETURN_IF_ERROR(CloseFile(std::move(file)));
|
||||||
|
return Status::OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
Status TsMuxer::CloseFile(std::unique_ptr<File, FileCloser> file) {
|
||||||
|
std::string file_name = file->file_name();
|
||||||
|
if (!file.release()->Close()) {
|
||||||
|
return Status(
|
||||||
|
error::FILE_FAILURE,
|
||||||
|
"Cannot close file " + file_name +
|
||||||
|
", possibly file permission issue or running out of disk space.");
|
||||||
|
}
|
||||||
|
return Status::OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TsMuxer::FireOnMediaStartEvent() {
|
void TsMuxer::FireOnMediaStartEvent() {
|
||||||
|
@ -66,10 +150,7 @@ void TsMuxer::FireOnMediaEndEvent() {
|
||||||
if (!muxer_listener())
|
if (!muxer_listener())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// For now, there is no single file TS segmenter. So all the values passed
|
muxer_listener()->OnMediaEnd(media_ranges_, total_duration_);
|
||||||
// here are left empty.
|
|
||||||
MuxerListener::MediaRanges range;
|
|
||||||
muxer_listener()->OnMediaEnd(range, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace mp2t
|
} // namespace mp2t
|
||||||
|
|
|
@ -30,6 +30,10 @@ class TsMuxer : public Muxer {
|
||||||
Status FinalizeSegment(size_t stream_id,
|
Status FinalizeSegment(size_t stream_id,
|
||||||
const SegmentInfo& sample) override;
|
const SegmentInfo& sample) override;
|
||||||
|
|
||||||
|
Status WriteSegment(const std::string& segment_path,
|
||||||
|
BufferWriter* segment_buffer);
|
||||||
|
Status CloseFile(std::unique_ptr<File, FileCloser> file);
|
||||||
|
|
||||||
void FireOnMediaStartEvent();
|
void FireOnMediaStartEvent();
|
||||||
void FireOnMediaEndEvent();
|
void FireOnMediaEndEvent();
|
||||||
|
|
||||||
|
@ -37,6 +41,17 @@ class TsMuxer : public Muxer {
|
||||||
int64_t sample_durations_[2];
|
int64_t sample_durations_[2];
|
||||||
int64_t num_samples_ = 0;
|
int64_t num_samples_ = 0;
|
||||||
|
|
||||||
|
// Used in multi-segment mode for segment template.
|
||||||
|
uint64_t segment_number_ = 0;
|
||||||
|
|
||||||
|
// Used in single segment mode.
|
||||||
|
std::unique_ptr<File, FileCloser> output_file_;
|
||||||
|
|
||||||
|
// Keeps track of segment ranges in single segment mode.
|
||||||
|
MuxerListener::MediaRanges media_ranges_;
|
||||||
|
|
||||||
|
uint64_t total_duration_ = 0;
|
||||||
|
|
||||||
DISALLOW_COPY_AND_ASSIGN(TsMuxer);
|
DISALLOW_COPY_AND_ASSIGN(TsMuxer);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -37,8 +37,7 @@ bool IsVideoCodec(Codec codec) {
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
TsSegmenter::TsSegmenter(const MuxerOptions& options, MuxerListener* listener)
|
TsSegmenter::TsSegmenter(const MuxerOptions& options, MuxerListener* listener)
|
||||||
: muxer_options_(options),
|
: listener_(listener),
|
||||||
listener_(listener),
|
|
||||||
transport_stream_timestamp_offset_(
|
transport_stream_timestamp_offset_(
|
||||||
options.transport_stream_timestamp_offset_ms * kTsTimescale / 1000),
|
options.transport_stream_timestamp_offset_ms * kTsTimescale / 1000),
|
||||||
pes_packet_generator_(
|
pes_packet_generator_(
|
||||||
|
@ -47,8 +46,6 @@ TsSegmenter::TsSegmenter(const MuxerOptions& options, MuxerListener* listener)
|
||||||
TsSegmenter::~TsSegmenter() {}
|
TsSegmenter::~TsSegmenter() {}
|
||||||
|
|
||||||
Status TsSegmenter::Initialize(const StreamInfo& stream_info) {
|
Status TsSegmenter::Initialize(const StreamInfo& stream_info) {
|
||||||
if (muxer_options_.segment_template.empty())
|
|
||||||
return Status(error::MUXER_FAILURE, "Segment template not specified.");
|
|
||||||
if (!pes_packet_generator_->Initialize(stream_info)) {
|
if (!pes_packet_generator_->Initialize(stream_info)) {
|
||||||
return Status(error::MUXER_FAILURE,
|
return Status(error::MUXER_FAILURE,
|
||||||
"Failed to initialize PesPacketGenerator.");
|
"Failed to initialize PesPacketGenerator.");
|
||||||
|
@ -172,40 +169,6 @@ Status TsSegmenter::FinalizeSegment(int64_t start_timestamp, int64_t duration) {
|
||||||
Status status = WritePesPackets();
|
Status status = WritePesPackets();
|
||||||
if (!status.ok())
|
if (!status.ok())
|
||||||
return status;
|
return status;
|
||||||
|
|
||||||
// This method may be called from Finalize() so segment_started_ could
|
|
||||||
// be false.
|
|
||||||
if (!segment_started_)
|
|
||||||
return Status::OK;
|
|
||||||
std::string segment_path =
|
|
||||||
GetSegmentName(muxer_options_.segment_template, segment_start_timestamp_,
|
|
||||||
segment_number_++, muxer_options_.bandwidth);
|
|
||||||
|
|
||||||
const int64_t file_size = segment_buffer_.Size();
|
|
||||||
std::unique_ptr<File, FileCloser> segment_file;
|
|
||||||
segment_file.reset(File::Open(segment_path.c_str(), "w"));
|
|
||||||
if (!segment_file) {
|
|
||||||
return Status(error::FILE_FAILURE,
|
|
||||||
"Cannot open file for write " + segment_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
RETURN_IF_ERROR(segment_buffer_.WriteToFile(segment_file.get()));
|
|
||||||
|
|
||||||
if (!segment_file.release()->Close()) {
|
|
||||||
return Status(
|
|
||||||
error::FILE_FAILURE,
|
|
||||||
"Cannot close file " + segment_path +
|
|
||||||
", possibly file permission issue or running out of disk space.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (listener_) {
|
|
||||||
listener_->OnNewSegment(segment_path,
|
|
||||||
start_timestamp * timescale_scale_ +
|
|
||||||
transport_stream_timestamp_offset_,
|
|
||||||
duration * timescale_scale_, file_size);
|
|
||||||
}
|
|
||||||
segment_started_ = false;
|
|
||||||
|
|
||||||
return Status::OK;
|
return Status::OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,9 +24,6 @@ class MuxerListener;
|
||||||
|
|
||||||
namespace mp2t {
|
namespace mp2t {
|
||||||
|
|
||||||
// TODO(rkuroiwa): For now, this implements multifile segmenter. Like other
|
|
||||||
// make this an abstract super class and implement multifile and single file
|
|
||||||
// segmenters.
|
|
||||||
class TsSegmenter {
|
class TsSegmenter {
|
||||||
public:
|
public:
|
||||||
// TODO(rkuroiwa): Add progress listener?
|
// TODO(rkuroiwa): Add progress listener?
|
||||||
|
@ -72,13 +69,22 @@ class TsSegmenter {
|
||||||
/// Only for testing.
|
/// Only for testing.
|
||||||
void SetSegmentStartedForTesting(bool value);
|
void SetSegmentStartedForTesting(bool value);
|
||||||
|
|
||||||
|
int64_t segment_start_timestamp() const { return segment_start_timestamp_; }
|
||||||
|
BufferWriter* segment_buffer() { return &segment_buffer_; }
|
||||||
|
void set_segment_started(bool value) { segment_started_ = value; }
|
||||||
|
bool segment_started() const { return segment_started_; }
|
||||||
|
|
||||||
|
double timescale() const { return timescale_scale_; }
|
||||||
|
uint32_t transport_stream_timestamp_offset() const {
|
||||||
|
return transport_stream_timestamp_offset_;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Status StartSegmentIfNeeded(int64_t next_pts);
|
Status StartSegmentIfNeeded(int64_t next_pts);
|
||||||
|
|
||||||
// Writes PES packets (carried in TsPackets) to a buffer.
|
// Writes PES packets (carried in TsPackets) to a buffer.
|
||||||
Status WritePesPackets();
|
Status WritePesPackets();
|
||||||
|
|
||||||
const MuxerOptions& muxer_options_;
|
|
||||||
MuxerListener* const listener_;
|
MuxerListener* const listener_;
|
||||||
|
|
||||||
// Codec for the stream.
|
// Codec for the stream.
|
||||||
|
@ -87,18 +93,16 @@ class TsSegmenter {
|
||||||
|
|
||||||
const int32_t transport_stream_timestamp_offset_ = 0;
|
const int32_t transport_stream_timestamp_offset_ = 0;
|
||||||
// Scale used to scale the input stream to TS's timesccale (which is 90000).
|
// Scale used to scale the input stream to TS's timesccale (which is 90000).
|
||||||
|
|
||||||
// Used for calculating the duration in seconds fo the current segment.
|
// Used for calculating the duration in seconds fo the current segment.
|
||||||
double timescale_scale_ = 1.0;
|
double timescale_scale_ = 1.0;
|
||||||
|
|
||||||
// Used for segment template.
|
|
||||||
uint64_t segment_number_ = 0;
|
|
||||||
|
|
||||||
std::unique_ptr<TsWriter> ts_writer_;
|
std::unique_ptr<TsWriter> ts_writer_;
|
||||||
|
|
||||||
BufferWriter segment_buffer_;
|
BufferWriter segment_buffer_;
|
||||||
|
|
||||||
// Set to true if segment_buffer_ is initialized, set to false after
|
// Set to true if segment_buffer_ is initialized, set to false after
|
||||||
// FinalizeSegment() succeeds.
|
// FinalizeSegment() succeeds in ts_muxer.
|
||||||
bool segment_started_ = false;
|
bool segment_started_ = false;
|
||||||
std::unique_ptr<PesPacketGenerator> pes_packet_generator_;
|
std::unique_ptr<PesPacketGenerator> pes_packet_generator_;
|
||||||
|
|
||||||
|
|
|
@ -207,11 +207,6 @@ TEST_F(TsSegmenterTest, PassedSegmentDuration) {
|
||||||
// Doesn't really matter how long this is.
|
// Doesn't really matter how long this is.
|
||||||
sample2->set_duration(kInputTimescale * 7);
|
sample2->set_duration(kInputTimescale * 7);
|
||||||
|
|
||||||
EXPECT_CALL(mock_listener,
|
|
||||||
OnNewSegment("memory://file1.ts",
|
|
||||||
kFirstPts * kTimeScale / kInputTimescale,
|
|
||||||
kTimeScale * 11, _));
|
|
||||||
|
|
||||||
Sequence writer_sequence;
|
Sequence writer_sequence;
|
||||||
EXPECT_CALL(*mock_ts_writer_, NewSegment(_))
|
EXPECT_CALL(*mock_ts_writer_, NewSegment(_))
|
||||||
.InSequence(writer_sequence)
|
.InSequence(writer_sequence)
|
||||||
|
@ -245,10 +240,6 @@ TEST_F(TsSegmenterTest, PassedSegmentDuration) {
|
||||||
EXPECT_CALL(*mock_pes_packet_generator_, Flush())
|
EXPECT_CALL(*mock_pes_packet_generator_, Flush())
|
||||||
.WillOnce(Return(true));
|
.WillOnce(Return(true));
|
||||||
|
|
||||||
EXPECT_CALL(*mock_ts_writer_, NewSegment(_))
|
|
||||||
.InSequence(writer_sequence)
|
|
||||||
.WillOnce(Return(true));
|
|
||||||
|
|
||||||
EXPECT_CALL(*mock_ts_writer_, AddPesPacketMock(_, _))
|
EXPECT_CALL(*mock_ts_writer_, AddPesPacketMock(_, _))
|
||||||
.Times(2)
|
.Times(2)
|
||||||
.WillRepeatedly(Return(true));
|
.WillRepeatedly(Return(true));
|
||||||
|
@ -391,8 +382,6 @@ TEST_F(TsSegmenterTest, EncryptedSample) {
|
||||||
.InSequence(pes_packet_sequence)
|
.InSequence(pes_packet_sequence)
|
||||||
.WillOnce(Return(new PesPacket()));
|
.WillOnce(Return(new PesPacket()));
|
||||||
|
|
||||||
EXPECT_CALL(mock_listener, OnNewSegment("memory://file1.ts", _, _, _));
|
|
||||||
|
|
||||||
MockTsWriter* mock_ts_writer_raw = mock_ts_writer_.get();
|
MockTsWriter* mock_ts_writer_raw = mock_ts_writer_.get();
|
||||||
|
|
||||||
segmenter.InjectPesPacketGeneratorForTesting(
|
segmenter.InjectPesPacketGeneratorForTesting(
|
||||||
|
|
|
@ -224,33 +224,17 @@ Status ValidateStreamDescriptor(bool dump_stream_info,
|
||||||
if (output_format == CONTAINER_UNKNOWN) {
|
if (output_format == CONTAINER_UNKNOWN) {
|
||||||
return Status(error::INVALID_ARGUMENT, "Unsupported output format.");
|
return Status(error::INVALID_ARGUMENT, "Unsupported output format.");
|
||||||
}
|
}
|
||||||
if (output_format == MediaContainerName::CONTAINER_MPEG2TS) {
|
|
||||||
if (stream.segment_template.empty()) {
|
|
||||||
return Status(
|
|
||||||
error::INVALID_ARGUMENT,
|
|
||||||
"Please specify 'segment_template'. Single file TS output is "
|
|
||||||
"not supported.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Right now the init segment is saved in |output| for multi-segment
|
if (output_format == CONTAINER_WEBVTT || output_format == CONTAINER_TTML ||
|
||||||
// content. However, for TS all segments must be self-initializing so
|
|
||||||
// there cannot be an init segment.
|
|
||||||
if (stream.output.length()) {
|
|
||||||
return Status(error::INVALID_ARGUMENT,
|
|
||||||
"All TS segments must be self-initializing. Stream "
|
|
||||||
"descriptors 'output' or 'init_segment' are not allowed.");
|
|
||||||
}
|
|
||||||
} else if (output_format == CONTAINER_WEBVTT ||
|
|
||||||
output_format == CONTAINER_TTML ||
|
|
||||||
output_format == CONTAINER_AAC || output_format == CONTAINER_MP3 ||
|
output_format == CONTAINER_AAC || output_format == CONTAINER_MP3 ||
|
||||||
output_format == CONTAINER_AC3 ||
|
output_format == CONTAINER_AC3 || output_format == CONTAINER_EAC3 ||
|
||||||
output_format == CONTAINER_EAC3) {
|
output_format == CONTAINER_MPEG2TS) {
|
||||||
// There is no need for an init segment when outputting because there is no
|
// There is no need for an init segment when outputting because there is no
|
||||||
// initialization data.
|
// initialization data.
|
||||||
if (stream.segment_template.length() && stream.output.length()) {
|
if (stream.segment_template.length() && stream.output.length()) {
|
||||||
return Status(
|
return Status(
|
||||||
error::INVALID_ARGUMENT,
|
error::INVALID_ARGUMENT,
|
||||||
"Segmented subtitles or PackedAudio output cannot have an init "
|
"Segmented subtitles, PackedAudio or TS output cannot have an init "
|
||||||
"segment. Do not specify stream descriptors 'output' or "
|
"segment. Do not specify stream descriptors 'output' or "
|
||||||
"'init_segment' when using 'segment_template'.");
|
"'init_segment' when using 'segment_template'.");
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue