Integrate ChunkingHandler
Also moved MediaHandler output validation to Initialize instead. This CL also addresses #122 with consistent chunking. Change-Id: I60c0da6d1b33421d7828bcb827d18899e71884ce
This commit is contained in:
parent
a3ce51785a
commit
160700b452
|
@ -34,6 +34,7 @@
|
||||||
#include "packager/media/base/key_source.h"
|
#include "packager/media/base/key_source.h"
|
||||||
#include "packager/media/base/muxer_options.h"
|
#include "packager/media/base/muxer_options.h"
|
||||||
#include "packager/media/base/muxer_util.h"
|
#include "packager/media/base/muxer_util.h"
|
||||||
|
#include "packager/media/chunking/chunking_handler.h"
|
||||||
#include "packager/media/event/hls_notify_muxer_listener.h"
|
#include "packager/media/event/hls_notify_muxer_listener.h"
|
||||||
#include "packager/media/event/mpd_notify_muxer_listener.h"
|
#include "packager/media/event/mpd_notify_muxer_listener.h"
|
||||||
#include "packager/media/event/vod_media_info_dump_muxer_listener.h"
|
#include "packager/media/event/vod_media_info_dump_muxer_listener.h"
|
||||||
|
@ -235,6 +236,7 @@ std::shared_ptr<Muxer> CreateOutputMuxer(const MuxerOptions& options,
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CreateRemuxJobs(const StreamDescriptorList& stream_descriptors,
|
bool CreateRemuxJobs(const StreamDescriptorList& stream_descriptors,
|
||||||
|
const ChunkingOptions& chunking_options,
|
||||||
const MuxerOptions& muxer_options,
|
const MuxerOptions& muxer_options,
|
||||||
FakeClock* fake_clock,
|
FakeClock* fake_clock,
|
||||||
KeySource* key_source,
|
KeySource* key_source,
|
||||||
|
@ -361,11 +363,15 @@ bool CreateRemuxJobs(const StreamDescriptorList& stream_descriptors,
|
||||||
if (muxer_listener)
|
if (muxer_listener)
|
||||||
muxer->SetMuxerListener(std::move(muxer_listener));
|
muxer->SetMuxerListener(std::move(muxer_listener));
|
||||||
|
|
||||||
|
auto chunking_handler = std::make_shared<ChunkingHandler>(chunking_options);
|
||||||
|
Status status = chunking_handler->SetHandler(0, std::move(muxer));
|
||||||
|
|
||||||
auto* demuxer = remux_jobs->back()->demuxer();
|
auto* demuxer = remux_jobs->back()->demuxer();
|
||||||
const std::string& stream_selector = stream_iter->stream_selector;
|
const std::string& stream_selector = stream_iter->stream_selector;
|
||||||
Status status = demuxer->SetHandler(stream_selector, std::move(muxer));
|
status.Update(demuxer->SetHandler(stream_selector, chunking_handler));
|
||||||
|
|
||||||
if (!status.ok()) {
|
if (!status.ok()) {
|
||||||
LOG(ERROR) << "Demuxer::SetHandler failed " << status;
|
LOG(ERROR) << "Failed to setup graph: " << status;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!stream_iter->language.empty())
|
if (!stream_iter->language.empty())
|
||||||
|
@ -426,10 +432,8 @@ bool RunPackager(const StreamDescriptorList& stream_descriptors) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get basic muxer options.
|
ChunkingOptions chunking_options = GetChunkingOptions();
|
||||||
MuxerOptions muxer_options;
|
MuxerOptions muxer_options = GetMuxerOptions();
|
||||||
if (!GetMuxerOptions(&muxer_options))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
DCHECK(!stream_descriptors.empty());
|
DCHECK(!stream_descriptors.empty());
|
||||||
// On demand profile generates single file segment while live profile
|
// On demand profile generates single file segment while live profile
|
||||||
|
@ -451,9 +455,7 @@ bool RunPackager(const StreamDescriptorList& stream_descriptors) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
MpdOptions mpd_options;
|
MpdOptions mpd_options = GetMpdOptions(on_demand_dash_profile);
|
||||||
if (!GetMpdOptions(on_demand_dash_profile, &mpd_options))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Create encryption key source if needed.
|
// Create encryption key source if needed.
|
||||||
std::unique_ptr<KeySource> encryption_key_source;
|
std::unique_ptr<KeySource> encryption_key_source;
|
||||||
|
@ -495,9 +497,9 @@ bool RunPackager(const StreamDescriptorList& stream_descriptors) {
|
||||||
|
|
||||||
std::vector<std::unique_ptr<RemuxJob>> remux_jobs;
|
std::vector<std::unique_ptr<RemuxJob>> remux_jobs;
|
||||||
FakeClock fake_clock;
|
FakeClock fake_clock;
|
||||||
if (!CreateRemuxJobs(stream_descriptors, muxer_options, &fake_clock,
|
if (!CreateRemuxJobs(stream_descriptors, chunking_options, muxer_options,
|
||||||
encryption_key_source.get(), mpd_notifier.get(),
|
&fake_clock, encryption_key_source.get(),
|
||||||
hls_notifier.get(), &remux_jobs)) {
|
mpd_notifier.get(), hls_notifier.get(), &remux_jobs)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
#include "packager/media/base/playready_key_source.h"
|
#include "packager/media/base/playready_key_source.h"
|
||||||
#include "packager/media/base/request_signer.h"
|
#include "packager/media/base/request_signer.h"
|
||||||
#include "packager/media/base/widevine_key_source.h"
|
#include "packager/media/base/widevine_key_source.h"
|
||||||
|
#include "packager/media/chunking/chunking_handler.h"
|
||||||
#include "packager/media/file/file.h"
|
#include "packager/media/file/file.h"
|
||||||
#include "packager/mpd/base/mpd_options.h"
|
#include "packager/mpd/base/mpd_options.h"
|
||||||
|
|
||||||
|
@ -146,45 +147,45 @@ std::unique_ptr<KeySource> CreateDecryptionKeySource() {
|
||||||
return decryption_key_source;
|
return decryption_key_source;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GetMuxerOptions(MuxerOptions* muxer_options) {
|
ChunkingOptions GetChunkingOptions() {
|
||||||
DCHECK(muxer_options);
|
ChunkingOptions chunking_options;
|
||||||
|
chunking_options.segment_duration_in_seconds = FLAGS_segment_duration;
|
||||||
|
chunking_options.subsegment_duration_in_seconds = FLAGS_fragment_duration;
|
||||||
|
chunking_options.segment_sap_aligned = FLAGS_segment_sap_aligned;
|
||||||
|
chunking_options.subsegment_sap_aligned = FLAGS_fragment_sap_aligned;
|
||||||
|
return chunking_options;
|
||||||
|
}
|
||||||
|
|
||||||
muxer_options->segment_duration = FLAGS_segment_duration;
|
MuxerOptions GetMuxerOptions() {
|
||||||
muxer_options->fragment_duration = FLAGS_fragment_duration;
|
MuxerOptions muxer_options;
|
||||||
muxer_options->segment_sap_aligned = FLAGS_segment_sap_aligned;
|
muxer_options.num_subsegments_per_sidx = FLAGS_num_subsegments_per_sidx;
|
||||||
muxer_options->fragment_sap_aligned = FLAGS_fragment_sap_aligned;
|
muxer_options.webm_subsample_encryption = FLAGS_webm_subsample_encryption;
|
||||||
muxer_options->num_subsegments_per_sidx = FLAGS_num_subsegments_per_sidx;
|
|
||||||
muxer_options->webm_subsample_encryption = FLAGS_webm_subsample_encryption;
|
|
||||||
if (FLAGS_mp4_use_decoding_timestamp_in_timeline) {
|
if (FLAGS_mp4_use_decoding_timestamp_in_timeline) {
|
||||||
LOG(WARNING) << "Flag --mp4_use_decoding_timestamp_in_timeline is set. "
|
LOG(WARNING) << "Flag --mp4_use_decoding_timestamp_in_timeline is set. "
|
||||||
"Note that it is a temporary hack to workaround Chromium "
|
"Note that it is a temporary hack to workaround Chromium "
|
||||||
"bug https://crbug.com/398130. The flag may be removed "
|
"bug https://crbug.com/398130. The flag may be removed "
|
||||||
"when the Chromium bug is fixed.";
|
"when the Chromium bug is fixed.";
|
||||||
}
|
}
|
||||||
muxer_options->mp4_use_decoding_timestamp_in_timeline =
|
muxer_options.mp4_use_decoding_timestamp_in_timeline =
|
||||||
FLAGS_mp4_use_decoding_timestamp_in_timeline;
|
FLAGS_mp4_use_decoding_timestamp_in_timeline;
|
||||||
|
muxer_options.temp_dir = FLAGS_temp_dir;
|
||||||
muxer_options->temp_dir = FLAGS_temp_dir;
|
return muxer_options;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GetMpdOptions(bool on_demand_profile, MpdOptions* mpd_options) {
|
MpdOptions GetMpdOptions(bool on_demand_profile) {
|
||||||
DCHECK(mpd_options);
|
MpdOptions mpd_options;
|
||||||
|
mpd_options.dash_profile =
|
||||||
mpd_options->dash_profile =
|
|
||||||
on_demand_profile ? DashProfile::kOnDemand : DashProfile::kLive;
|
on_demand_profile ? DashProfile::kOnDemand : DashProfile::kLive;
|
||||||
mpd_options->mpd_type =
|
mpd_options.mpd_type = (on_demand_profile || FLAGS_generate_static_mpd)
|
||||||
(on_demand_profile || FLAGS_generate_static_mpd)
|
|
||||||
? MpdType::kStatic
|
? MpdType::kStatic
|
||||||
: MpdType::kDynamic;
|
: MpdType::kDynamic;
|
||||||
mpd_options->availability_time_offset = FLAGS_availability_time_offset;
|
mpd_options.availability_time_offset = FLAGS_availability_time_offset;
|
||||||
mpd_options->minimum_update_period = FLAGS_minimum_update_period;
|
mpd_options.minimum_update_period = FLAGS_minimum_update_period;
|
||||||
mpd_options->min_buffer_time = FLAGS_min_buffer_time;
|
mpd_options.min_buffer_time = FLAGS_min_buffer_time;
|
||||||
mpd_options->time_shift_buffer_depth = FLAGS_time_shift_buffer_depth;
|
mpd_options.time_shift_buffer_depth = FLAGS_time_shift_buffer_depth;
|
||||||
mpd_options->suggested_presentation_delay =
|
mpd_options.suggested_presentation_delay = FLAGS_suggested_presentation_delay;
|
||||||
FLAGS_suggested_presentation_delay;
|
mpd_options.default_language = FLAGS_default_language;
|
||||||
mpd_options->default_language = FLAGS_default_language;
|
return mpd_options;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace media
|
} // namespace media
|
||||||
|
|
|
@ -22,6 +22,7 @@ struct MpdOptions;
|
||||||
namespace media {
|
namespace media {
|
||||||
|
|
||||||
class KeySource;
|
class KeySource;
|
||||||
|
struct ChunkingOptions;
|
||||||
struct MuxerOptions;
|
struct MuxerOptions;
|
||||||
|
|
||||||
/// Create KeySource based on provided command line options for content
|
/// Create KeySource based on provided command line options for content
|
||||||
|
@ -36,11 +37,14 @@ std::unique_ptr<KeySource> CreateEncryptionKeySource();
|
||||||
/// decryption is not required.
|
/// decryption is not required.
|
||||||
std::unique_ptr<KeySource> CreateDecryptionKeySource();
|
std::unique_ptr<KeySource> CreateDecryptionKeySource();
|
||||||
|
|
||||||
/// Fill MuxerOptions members using provided command line options.
|
/// @return ChunkingOptions from provided command line options.
|
||||||
bool GetMuxerOptions(MuxerOptions* muxer_options);
|
ChunkingOptions GetChunkingOptions();
|
||||||
|
|
||||||
/// Fill MpdOptions members using provided command line options.
|
/// @return MuxerOptions from provided command line options.
|
||||||
bool GetMpdOptions(bool on_demand_profile, MpdOptions* mpd_options);
|
MuxerOptions GetMuxerOptions();
|
||||||
|
|
||||||
|
/// @return MpdOptions from provided command line options.
|
||||||
|
MpdOptions GetMpdOptions(bool on_demand_profile);
|
||||||
|
|
||||||
} // namespace media
|
} // namespace media
|
||||||
} // namespace shaka
|
} // namespace shaka
|
||||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -3,12 +3,12 @@
|
||||||
## Generated with https://github.com/google/shaka-packager version <tag>-<hash>-<test>
|
## Generated with https://github.com/google/shaka-packager version <tag>-<hash>-<test>
|
||||||
#EXT-X-TARGETDURATION:2
|
#EXT-X-TARGETDURATION:2
|
||||||
#EXT-X-PLAYLIST-TYPE:VOD
|
#EXT-X-PLAYLIST-TYPE:VOD
|
||||||
#EXTINF:1.021,
|
#EXTINF:0.975,
|
||||||
output_audio-1.ts
|
output_audio-1.ts
|
||||||
|
#EXTINF:0.998,
|
||||||
|
output_audio-2.ts
|
||||||
#EXT-X-DISCONTINUITY
|
#EXT-X-DISCONTINUITY
|
||||||
#EXT-X-KEY:METHOD=SAMPLE-AES,URI="data:text/plain;base64,MTIzNDU2Nzg5MDEyMzQ1Ng==",IV=0x3334353637383930,KEYFORMAT="identity"
|
#EXT-X-KEY:METHOD=SAMPLE-AES,URI="data:text/plain;base64,MTIzNDU2Nzg5MDEyMzQ1Ng==",IV=0x3334353637383930,KEYFORMAT="identity"
|
||||||
#EXTINF:1.021,
|
#EXTINF:0.789,
|
||||||
output_audio-2.ts
|
|
||||||
#EXTINF:0.720,
|
|
||||||
output_audio-3.ts
|
output_audio-3.ts
|
||||||
#EXT-X-ENDLIST
|
#EXT-X-ENDLIST
|
||||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -3,10 +3,10 @@
|
||||||
## Generated with https://github.com/google/shaka-packager version <tag>-<hash>-<test>
|
## Generated with https://github.com/google/shaka-packager version <tag>-<hash>-<test>
|
||||||
#EXT-X-TARGETDURATION:2
|
#EXT-X-TARGETDURATION:2
|
||||||
#EXT-X-PLAYLIST-TYPE:VOD
|
#EXT-X-PLAYLIST-TYPE:VOD
|
||||||
#EXTINF:1.021,
|
#EXTINF:0.975,
|
||||||
output_audio-1.ts
|
output_audio-1.ts
|
||||||
#EXTINF:1.021,
|
#EXTINF:0.998,
|
||||||
output_audio-2.ts
|
output_audio-2.ts
|
||||||
#EXTINF:0.720,
|
#EXTINF:0.789,
|
||||||
output_audio-3.ts
|
output_audio-3.ts
|
||||||
#EXT-X-ENDLIST
|
#EXT-X-ENDLIST
|
||||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1,5 +1,5 @@
|
||||||
#EXTM3U
|
#EXTM3U
|
||||||
## Generated with https://github.com/google/shaka-packager version <tag>-<hash>-<test>
|
## Generated with https://github.com/google/shaka-packager version <tag>-<hash>-<test>
|
||||||
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio",NAME="stream_0",URI="audio.m3u8"
|
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio",NAME="stream_0",URI="audio.m3u8"
|
||||||
#EXT-X-STREAM-INF:AUDIO="audio",CODECS="avc1.64001e,mp4a.40.2",BANDWIDTH=1217603
|
#EXT-X-STREAM-INF:AUDIO="audio",CODECS="avc1.64001e,mp4a.40.2",BANDWIDTH=1217518
|
||||||
video.m3u8
|
video.m3u8
|
||||||
|
|
|
@ -21,12 +21,13 @@
|
||||||
<ContentProtection schemeIdUri="urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b">
|
<ContentProtection schemeIdUri="urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b">
|
||||||
<cenc:pssh>AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA==</cenc:pssh>
|
<cenc:pssh>AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA==</cenc:pssh>
|
||||||
</ContentProtection>
|
</ContentProtection>
|
||||||
<Representation id="1" bandwidth="124195" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
|
<Representation id="1" bandwidth="124560" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
|
||||||
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
|
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
|
||||||
<SegmentTemplate timescale="44100" initialization="output_audio-init.mp4" media="output_audio-$Number$.m4s" startNumber="1">
|
<SegmentTemplate timescale="44100" initialization="output_audio-init.mp4" media="output_audio-$Number$.m4s" startNumber="1">
|
||||||
<SegmentTimeline>
|
<SegmentTimeline>
|
||||||
<S t="0" d="45056" r="1"/>
|
<S t="0" d="45056"/>
|
||||||
<S t="90112" d="31744"/>
|
<S t="45056" d="44032"/>
|
||||||
|
<S t="89088" d="32768"/>
|
||||||
</SegmentTimeline>
|
</SegmentTimeline>
|
||||||
</SegmentTemplate>
|
</SegmentTemplate>
|
||||||
</Representation>
|
</Representation>
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
</Representation>
|
</Representation>
|
||||||
</AdaptationSet>
|
</AdaptationSet>
|
||||||
<AdaptationSet id="1" contentType="audio" segmentAlignment="true">
|
<AdaptationSet id="1" contentType="audio" segmentAlignment="true">
|
||||||
<Representation id="1" bandwidth="124195" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
|
<Representation id="1" bandwidth="124560" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
|
||||||
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
|
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
|
||||||
<ContentProtection value="cenc" schemeIdUri="urn:mpeg:dash:mp4protection:2011" cenc:default_KID="31323334-3536-3738-3930-313233343536"/>
|
<ContentProtection value="cenc" schemeIdUri="urn:mpeg:dash:mp4protection:2011" cenc:default_KID="31323334-3536-3738-3930-313233343536"/>
|
||||||
<ContentProtection schemeIdUri="urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b">
|
<ContentProtection schemeIdUri="urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b">
|
||||||
|
@ -25,8 +25,9 @@
|
||||||
</ContentProtection>
|
</ContentProtection>
|
||||||
<SegmentTemplate timescale="44100" initialization="output_audio-init.mp4" media="output_audio-$Number$.m4s" startNumber="1">
|
<SegmentTemplate timescale="44100" initialization="output_audio-init.mp4" media="output_audio-$Number$.m4s" startNumber="1">
|
||||||
<SegmentTimeline>
|
<SegmentTimeline>
|
||||||
<S t="0" d="45056" r="1"/>
|
<S t="0" d="45056"/>
|
||||||
<S t="90112" d="31744"/>
|
<S t="45056" d="44032"/>
|
||||||
|
<S t="89088" d="32768"/>
|
||||||
</SegmentTimeline>
|
</SegmentTimeline>
|
||||||
</SegmentTemplate>
|
</SegmentTemplate>
|
||||||
</Representation>
|
</Representation>
|
||||||
|
|
|
@ -17,12 +17,13 @@
|
||||||
<AdaptationSet id="1" contentType="audio" segmentAlignment="true">
|
<AdaptationSet id="1" contentType="audio" segmentAlignment="true">
|
||||||
<ContentProtection value="cenc" schemeIdUri="urn:mpeg:dash:mp4protection:2011" cenc:default_KID="00000000-0000-0000-0000-000000000000"/>
|
<ContentProtection value="cenc" schemeIdUri="urn:mpeg:dash:mp4protection:2011" cenc:default_KID="00000000-0000-0000-0000-000000000000"/>
|
||||||
<ContentProtection schemeIdUri="urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b"/>
|
<ContentProtection schemeIdUri="urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b"/>
|
||||||
<Representation id="1" bandwidth="125175" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
|
<Representation id="1" bandwidth="125525" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
|
||||||
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
|
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
|
||||||
<SegmentTemplate timescale="44100" initialization="output_audio-init.mp4" media="output_audio-$Number$.m4s" startNumber="1">
|
<SegmentTemplate timescale="44100" initialization="output_audio-init.mp4" media="output_audio-$Number$.m4s" startNumber="1">
|
||||||
<SegmentTimeline>
|
<SegmentTimeline>
|
||||||
<S t="0" d="45056" r="1"/>
|
<S t="0" d="45056"/>
|
||||||
<S t="90112" d="31744"/>
|
<S t="45056" d="44032"/>
|
||||||
|
<S t="89088" d="32768"/>
|
||||||
</SegmentTimeline>
|
</SegmentTimeline>
|
||||||
</SegmentTemplate>
|
</SegmentTemplate>
|
||||||
</Representation>
|
</Representation>
|
||||||
|
|
|
@ -15,14 +15,15 @@
|
||||||
</Representation>
|
</Representation>
|
||||||
</AdaptationSet>
|
</AdaptationSet>
|
||||||
<AdaptationSet id="1" contentType="audio" segmentAlignment="true">
|
<AdaptationSet id="1" contentType="audio" segmentAlignment="true">
|
||||||
<Representation id="1" bandwidth="125175" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
|
<Representation id="1" bandwidth="125525" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
|
||||||
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
|
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
|
||||||
<ContentProtection value="cenc" schemeIdUri="urn:mpeg:dash:mp4protection:2011" cenc:default_KID="00000000-0000-0000-0000-000000000000"/>
|
<ContentProtection value="cenc" schemeIdUri="urn:mpeg:dash:mp4protection:2011" cenc:default_KID="00000000-0000-0000-0000-000000000000"/>
|
||||||
<ContentProtection schemeIdUri="urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b"/>
|
<ContentProtection schemeIdUri="urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b"/>
|
||||||
<SegmentTemplate timescale="44100" initialization="output_audio-init.mp4" media="output_audio-$Number$.m4s" startNumber="1">
|
<SegmentTemplate timescale="44100" initialization="output_audio-init.mp4" media="output_audio-$Number$.m4s" startNumber="1">
|
||||||
<SegmentTimeline>
|
<SegmentTimeline>
|
||||||
<S t="0" d="45056" r="1"/>
|
<S t="0" d="45056"/>
|
||||||
<S t="90112" d="31744"/>
|
<S t="45056" d="44032"/>
|
||||||
|
<S t="89088" d="32768"/>
|
||||||
</SegmentTimeline>
|
</SegmentTimeline>
|
||||||
</SegmentTemplate>
|
</SegmentTemplate>
|
||||||
</Representation>
|
</Representation>
|
||||||
|
|
|
@ -13,12 +13,13 @@
|
||||||
</Representation>
|
</Representation>
|
||||||
</AdaptationSet>
|
</AdaptationSet>
|
||||||
<AdaptationSet id="1" contentType="audio" segmentAlignment="true">
|
<AdaptationSet id="1" contentType="audio" segmentAlignment="true">
|
||||||
<Representation id="1" bandwidth="121855" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
|
<Representation id="1" bandwidth="122235" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
|
||||||
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
|
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
|
||||||
<SegmentTemplate timescale="44100" initialization="output_audio-init.mp4" media="output_audio-$Number$.m4s" startNumber="1">
|
<SegmentTemplate timescale="44100" initialization="output_audio-init.mp4" media="output_audio-$Number$.m4s" startNumber="1">
|
||||||
<SegmentTimeline>
|
<SegmentTimeline>
|
||||||
<S t="0" d="45056" r="1"/>
|
<S t="0" d="45056"/>
|
||||||
<S t="90112" d="31744"/>
|
<S t="45056" d="44032"/>
|
||||||
|
<S t="89088" d="32768"/>
|
||||||
</SegmentTimeline>
|
</SegmentTimeline>
|
||||||
</SegmentTemplate>
|
</SegmentTemplate>
|
||||||
</Representation>
|
</Representation>
|
||||||
|
|
|
@ -13,12 +13,13 @@
|
||||||
</Representation>
|
</Representation>
|
||||||
</AdaptationSet>
|
</AdaptationSet>
|
||||||
<AdaptationSet id="1" contentType="audio" segmentAlignment="true">
|
<AdaptationSet id="1" contentType="audio" segmentAlignment="true">
|
||||||
<Representation id="1" bandwidth="121855" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
|
<Representation id="1" bandwidth="122235" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
|
||||||
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
|
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
|
||||||
<SegmentTemplate timescale="44100" initialization="output_audio-init.mp4" media="output_audio-$Number$.m4s" startNumber="1">
|
<SegmentTemplate timescale="44100" initialization="output_audio-init.mp4" media="output_audio-$Number$.m4s" startNumber="1">
|
||||||
<SegmentTimeline>
|
<SegmentTimeline>
|
||||||
<S t="0" d="45056" r="1"/>
|
<S t="0" d="45056"/>
|
||||||
<S t="90112" d="31744"/>
|
<S t="45056" d="44032"/>
|
||||||
|
<S t="89088" d="32768"/>
|
||||||
</SegmentTimeline>
|
</SegmentTimeline>
|
||||||
</SegmentTemplate>
|
</SegmentTemplate>
|
||||||
</Representation>
|
</Representation>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#EXTM3U
|
#EXTM3U
|
||||||
## Generated with https://github.com/google/shaka-packager version <tag>-<hash>-<test>
|
## Generated with https://github.com/google/shaka-packager version <tag>-<hash>-<test>
|
||||||
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio",NAME="stream_0",URI="audio.m3u8"
|
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio",NAME="stream_0",URI="audio.m3u8"
|
||||||
#EXT-X-STREAM-INF:AUDIO="audio",CODECS="avc1.64001e,mp4a.40.2",BANDWIDTH=1217603
|
#EXT-X-STREAM-INF:AUDIO="audio",CODECS="avc1.64001e,mp4a.40.2",BANDWIDTH=1217518
|
||||||
video.m3u8
|
video.m3u8
|
||||||
|
|
|
@ -11,7 +11,7 @@ namespace media {
|
||||||
|
|
||||||
Status MediaHandler::SetHandler(int output_stream_index,
|
Status MediaHandler::SetHandler(int output_stream_index,
|
||||||
std::shared_ptr<MediaHandler> handler) {
|
std::shared_ptr<MediaHandler> handler) {
|
||||||
if (!ValidateOutputStreamIndex(output_stream_index))
|
if (output_stream_index < 0)
|
||||||
return Status(error::INVALID_ARGUMENT, "Invalid output stream index");
|
return Status(error::INVALID_ARGUMENT, "Invalid output stream index");
|
||||||
if (output_handlers_.find(output_stream_index) != output_handlers_.end()) {
|
if (output_handlers_.find(output_stream_index) != output_handlers_.end()) {
|
||||||
return Status(error::ALREADY_EXISTS,
|
return Status(error::ALREADY_EXISTS,
|
||||||
|
@ -30,6 +30,8 @@ Status MediaHandler::Initialize() {
|
||||||
if (!status.ok())
|
if (!status.ok())
|
||||||
return status;
|
return status;
|
||||||
for (auto& pair : output_handlers_) {
|
for (auto& pair : output_handlers_) {
|
||||||
|
if (!ValidateOutputStreamIndex(pair.first))
|
||||||
|
return Status(error::INVALID_ARGUMENT, "Invalid output stream index");
|
||||||
status = pair.second.first->Initialize();
|
status = pair.second.first->Initialize();
|
||||||
if (!status.ok())
|
if (!status.ok())
|
||||||
return status;
|
return status;
|
||||||
|
|
|
@ -64,8 +64,12 @@ Status Muxer::Process(std::unique_ptr<StreamData> stream_data) {
|
||||||
case StreamDataType::kStreamInfo:
|
case StreamDataType::kStreamInfo:
|
||||||
streams_.push_back(std::move(stream_data->stream_info));
|
streams_.push_back(std::move(stream_data->stream_info));
|
||||||
return InitializeMuxer();
|
return InitializeMuxer();
|
||||||
|
case StreamDataType::kSegmentInfo:
|
||||||
|
return FinalizeSegment(stream_data->stream_index,
|
||||||
|
std::move(stream_data->segment_info));
|
||||||
case StreamDataType::kMediaSample:
|
case StreamDataType::kMediaSample:
|
||||||
return DoAddSample(stream_data->media_sample);
|
return AddSample(stream_data->stream_index,
|
||||||
|
std::move(stream_data->media_sample));
|
||||||
default:
|
default:
|
||||||
VLOG(3) << "Stream data type "
|
VLOG(3) << "Stream data type "
|
||||||
<< static_cast<int>(stream_data->stream_data_type) << " ignored.";
|
<< static_cast<int>(stream_data->stream_data_type) << " ignored.";
|
||||||
|
|
|
@ -123,8 +123,13 @@ class Muxer : public MediaHandler {
|
||||||
// Final clean up.
|
// Final clean up.
|
||||||
virtual Status Finalize() = 0;
|
virtual Status Finalize() = 0;
|
||||||
|
|
||||||
// AddSample implementation.
|
// Add a new sample.
|
||||||
virtual Status DoAddSample(std::shared_ptr<MediaSample> sample) = 0;
|
virtual Status AddSample(int stream_id,
|
||||||
|
std::shared_ptr<MediaSample> sample) = 0;
|
||||||
|
|
||||||
|
// Finalize the segment or subsegment.
|
||||||
|
virtual Status FinalizeSegment(int stream_id,
|
||||||
|
std::shared_ptr<SegmentInfo> segment_info) = 0;
|
||||||
|
|
||||||
MuxerOptions options_;
|
MuxerOptions options_;
|
||||||
std::vector<std::shared_ptr<StreamInfo>> streams_;
|
std::vector<std::shared_ptr<StreamInfo>> streams_;
|
||||||
|
|
|
@ -19,25 +19,6 @@ struct MuxerOptions {
|
||||||
MuxerOptions();
|
MuxerOptions();
|
||||||
~MuxerOptions();
|
~MuxerOptions();
|
||||||
|
|
||||||
/// Segment duration in seconds. If single_segment is specified, this
|
|
||||||
/// parameter sets the duration of a subsegment; otherwise, this parameter
|
|
||||||
/// sets the duration of a segment. A segment can contain one or many
|
|
||||||
/// fragments.
|
|
||||||
double segment_duration = 0;
|
|
||||||
|
|
||||||
/// Fragment duration in seconds. Should not be larger than the segment
|
|
||||||
/// duration.
|
|
||||||
double fragment_duration = 0;
|
|
||||||
|
|
||||||
/// Force segments to begin with stream access points. Segment duration may
|
|
||||||
/// not be exactly what specified by segment_duration.
|
|
||||||
bool segment_sap_aligned = false;
|
|
||||||
|
|
||||||
/// Force fragments to begin with stream access points. Fragment duration
|
|
||||||
/// may not be exactly what specified by segment_duration. Setting to true
|
|
||||||
/// implies that segment_sap_aligned is true as well.
|
|
||||||
bool fragment_sap_aligned = false;
|
|
||||||
|
|
||||||
/// For ISO BMFF only.
|
/// For ISO BMFF only.
|
||||||
/// Set the number of subsegments in each SIDX box. If 0, a single SIDX box
|
/// Set the number of subsegments in each SIDX box. If 0, a single SIDX box
|
||||||
/// is used per segment. If -1, no SIDX box is used. Otherwise, the Muxer
|
/// is used per segment. If -1, no SIDX box is used. Otherwise, the Muxer
|
||||||
|
|
|
@ -142,6 +142,8 @@ Status ChunkingHandler::ProcessMediaSample(const MediaSample* sample) {
|
||||||
const int64_t segment_index = timestamp / segment_duration_;
|
const int64_t segment_index = timestamp / segment_duration_;
|
||||||
if (segment_index != current_segment_index_) {
|
if (segment_index != current_segment_index_) {
|
||||||
current_segment_index_ = segment_index;
|
current_segment_index_ = segment_index;
|
||||||
|
// Reset subsegment index.
|
||||||
|
current_subsegment_index_ = 0;
|
||||||
new_segment = true;
|
new_segment = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -86,6 +86,30 @@ TEST_F(ChunkingHandlerTest, AudioNoSubsegmentsThenFlush) {
|
||||||
kDuration1 * 2, !kIsSubsegment)));
|
kDuration1 * 2, !kIsSubsegment)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(ChunkingHandlerTest, AudioWithSubsegments) {
|
||||||
|
ChunkingOptions chunking_options;
|
||||||
|
chunking_options.segment_duration_in_seconds = 1;
|
||||||
|
chunking_options.subsegment_duration_in_seconds = 0.5;
|
||||||
|
SetUpChunkingHandler(1, chunking_options);
|
||||||
|
|
||||||
|
ASSERT_OK(Process(GetAudioStreamInfoStreamData(kStreamIndex0, kTimeScale0)));
|
||||||
|
for (int i = 0; i < 5; ++i) {
|
||||||
|
ASSERT_OK(Process(GetMediaSampleStreamData(kStreamIndex0, i * kDuration1,
|
||||||
|
kDuration1, kKeyFrame)));
|
||||||
|
}
|
||||||
|
EXPECT_THAT(
|
||||||
|
GetOutputStreamDataVector(),
|
||||||
|
ElementsAre(
|
||||||
|
IsStreamInfo(kStreamIndex0, kTimeScale0, !kEncrypted),
|
||||||
|
IsMediaSample(kStreamIndex0, 0, kDuration1),
|
||||||
|
IsMediaSample(kStreamIndex0, kDuration1, kDuration1),
|
||||||
|
IsSegmentInfo(kStreamIndex0, 0, kDuration1 * 2, kIsSubsegment),
|
||||||
|
IsMediaSample(kStreamIndex0, 2 * kDuration1, kDuration1),
|
||||||
|
IsSegmentInfo(kStreamIndex0, 0, kDuration1 * 3, !kIsSubsegment),
|
||||||
|
IsMediaSample(kStreamIndex0, 3 * kDuration1, kDuration1),
|
||||||
|
IsMediaSample(kStreamIndex0, 4 * kDuration1, kDuration1)));
|
||||||
|
}
|
||||||
|
|
||||||
TEST_F(ChunkingHandlerTest, VideoAndSubsegmentAndNonzeroStart) {
|
TEST_F(ChunkingHandlerTest, VideoAndSubsegmentAndNonzeroStart) {
|
||||||
ChunkingOptions chunking_options;
|
ChunkingOptions chunking_options;
|
||||||
chunking_options.segment_duration_in_seconds = 1;
|
chunking_options.segment_duration_in_seconds = 1;
|
||||||
|
|
|
@ -91,8 +91,9 @@ TEST_F(EncryptionHandlerTest, Initialize) {
|
||||||
|
|
||||||
TEST_F(EncryptionHandlerTest, OnlyOneOutput) {
|
TEST_F(EncryptionHandlerTest, OnlyOneOutput) {
|
||||||
// Connecting another handler will fail.
|
// Connecting another handler will fail.
|
||||||
|
ASSERT_OK(encryption_handler_->AddHandler(some_handler()));
|
||||||
ASSERT_EQ(error::INVALID_ARGUMENT,
|
ASSERT_EQ(error::INVALID_ARGUMENT,
|
||||||
encryption_handler_->AddHandler(some_handler()).error_code());
|
encryption_handler_->Initialize().error_code());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(EncryptionHandlerTest, OnlyOneInput) {
|
TEST_F(EncryptionHandlerTest, OnlyOneInput) {
|
||||||
|
|
|
@ -44,10 +44,6 @@ MediaInfo ConvertToMediaInfo(const std::string& media_info_string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetDefaultLiveMuxerOptionsValues(media::MuxerOptions* muxer_options) {
|
void SetDefaultLiveMuxerOptionsValues(media::MuxerOptions* muxer_options) {
|
||||||
muxer_options->segment_duration = 10.0;
|
|
||||||
muxer_options->fragment_duration = 10.0;
|
|
||||||
muxer_options->segment_sap_aligned = true;
|
|
||||||
muxer_options->fragment_sap_aligned = true;
|
|
||||||
muxer_options->num_subsegments_per_sidx = 0;
|
muxer_options->num_subsegments_per_sidx = 0;
|
||||||
muxer_options->output_file_name = "liveinit.mp4";
|
muxer_options->output_file_name = "liveinit.mp4";
|
||||||
muxer_options->segment_template = "live-$NUMBER$.mp4";
|
muxer_options->segment_template = "live-$NUMBER$.mp4";
|
||||||
|
|
|
@ -73,10 +73,6 @@ OnMediaEndParameters GetDefaultOnMediaEndParams() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetDefaultMuxerOptionsValues(MuxerOptions* muxer_options) {
|
void SetDefaultMuxerOptionsValues(MuxerOptions* muxer_options) {
|
||||||
muxer_options->segment_duration = 10.0;
|
|
||||||
muxer_options->fragment_duration = 10.0;
|
|
||||||
muxer_options->segment_sap_aligned = true;
|
|
||||||
muxer_options->fragment_sap_aligned = true;
|
|
||||||
muxer_options->num_subsegments_per_sidx = 0;
|
muxer_options->num_subsegments_per_sidx = 0;
|
||||||
muxer_options->output_file_name = "test_output_file_name.mp4";
|
muxer_options->output_file_name = "test_output_file_name.mp4";
|
||||||
muxer_options->segment_template.clear();
|
muxer_options->segment_template.clear();
|
||||||
|
|
|
@ -34,10 +34,20 @@ Status TsMuxer::Finalize() {
|
||||||
return segmenter_->Finalize();
|
return segmenter_->Finalize();
|
||||||
}
|
}
|
||||||
|
|
||||||
Status TsMuxer::DoAddSample(std::shared_ptr<MediaSample> sample) {
|
Status TsMuxer::AddSample(int stream_id, std::shared_ptr<MediaSample> sample) {
|
||||||
|
DCHECK_EQ(stream_id, 0);
|
||||||
return segmenter_->AddSample(sample);
|
return segmenter_->AddSample(sample);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Status TsMuxer::FinalizeSegment(int stream_id,
|
||||||
|
std::shared_ptr<SegmentInfo> segment_info) {
|
||||||
|
DCHECK_EQ(stream_id, 0);
|
||||||
|
return segment_info->is_subsegment
|
||||||
|
? Status::OK
|
||||||
|
: segmenter_->FinalizeSegment(segment_info->start_timestamp,
|
||||||
|
segment_info->duration);
|
||||||
|
}
|
||||||
|
|
||||||
void TsMuxer::FireOnMediaStartEvent() {
|
void TsMuxer::FireOnMediaStartEvent() {
|
||||||
if (!muxer_listener())
|
if (!muxer_listener())
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -26,7 +26,9 @@ class TsMuxer : public Muxer {
|
||||||
// Muxer implementation.
|
// Muxer implementation.
|
||||||
Status InitializeMuxer() override;
|
Status InitializeMuxer() override;
|
||||||
Status Finalize() override;
|
Status Finalize() override;
|
||||||
Status DoAddSample(std::shared_ptr<MediaSample> sample) override;
|
Status AddSample(int stream_id, std::shared_ptr<MediaSample> sample) override;
|
||||||
|
Status FinalizeSegment(int stream_id,
|
||||||
|
std::shared_ptr<SegmentInfo> sample) override;
|
||||||
|
|
||||||
void FireOnMediaStartEvent();
|
void FireOnMediaStartEvent();
|
||||||
void FireOnMediaEndEvent();
|
void FireOnMediaEndEvent();
|
||||||
|
|
|
@ -82,21 +82,10 @@ Status TsSegmenter::Initialize(const StreamInfo& stream_info,
|
||||||
}
|
}
|
||||||
|
|
||||||
Status TsSegmenter::Finalize() {
|
Status TsSegmenter::Finalize() {
|
||||||
return Flush();
|
return Status::OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
// First checks whether the sample is a key frame. If so and the segment has
|
|
||||||
// passed the segment duration, then flush the generator and write all the data
|
|
||||||
// to file.
|
|
||||||
Status TsSegmenter::AddSample(std::shared_ptr<MediaSample> sample) {
|
Status TsSegmenter::AddSample(std::shared_ptr<MediaSample> sample) {
|
||||||
const bool passed_segment_duration =
|
|
||||||
current_segment_total_sample_duration_ > muxer_options_.segment_duration;
|
|
||||||
if (sample->is_key_frame() && passed_segment_duration) {
|
|
||||||
Status status = Flush();
|
|
||||||
if (!status.ok())
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ts_writer_file_opened_ && !sample->is_key_frame())
|
if (!ts_writer_file_opened_ && !sample->is_key_frame())
|
||||||
LOG(WARNING) << "A segment will start with a non key frame.";
|
LOG(WARNING) << "A segment will start with a non key frame.";
|
||||||
|
|
||||||
|
@ -104,11 +93,6 @@ Status TsSegmenter::AddSample(std::shared_ptr<MediaSample> sample) {
|
||||||
return Status(error::MUXER_FAILURE,
|
return Status(error::MUXER_FAILURE,
|
||||||
"Failed to add sample to PesPacketGenerator.");
|
"Failed to add sample to PesPacketGenerator.");
|
||||||
}
|
}
|
||||||
|
|
||||||
const double scaled_sample_duration = sample->duration() * timescale_scale_;
|
|
||||||
current_segment_total_sample_duration_ +=
|
|
||||||
scaled_sample_duration / kTsTimescale;
|
|
||||||
|
|
||||||
return WritePesPacketsToFile();
|
return WritePesPacketsToFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,7 +117,6 @@ Status TsSegmenter::OpenNewSegmentIfClosed(uint32_t next_pts) {
|
||||||
segment_number_++, muxer_options_.bandwidth);
|
segment_number_++, muxer_options_.bandwidth);
|
||||||
if (!ts_writer_->NewSegment(segment_name))
|
if (!ts_writer_->NewSegment(segment_name))
|
||||||
return Status(error::MUXER_FAILURE, "Failed to initilize TsPacketWriter.");
|
return Status(error::MUXER_FAILURE, "Failed to initilize TsPacketWriter.");
|
||||||
current_segment_start_time_ = next_pts;
|
|
||||||
current_segment_path_ = segment_name;
|
current_segment_path_ = segment_name;
|
||||||
ts_writer_file_opened_ = true;
|
ts_writer_file_opened_ = true;
|
||||||
return Status::OK;
|
return Status::OK;
|
||||||
|
@ -154,7 +137,8 @@ Status TsSegmenter::WritePesPacketsToFile() {
|
||||||
return Status::OK;
|
return Status::OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
Status TsSegmenter::Flush() {
|
Status TsSegmenter::FinalizeSegment(uint64_t start_timestamp,
|
||||||
|
uint64_t duration) {
|
||||||
if (!pes_packet_generator_->Flush()) {
|
if (!pes_packet_generator_->Flush()) {
|
||||||
return Status(error::MUXER_FAILURE,
|
return Status(error::MUXER_FAILURE,
|
||||||
"Failed to flush PesPacketGenerator.");
|
"Failed to flush PesPacketGenerator.");
|
||||||
|
@ -172,15 +156,13 @@ Status TsSegmenter::Flush() {
|
||||||
if (listener_) {
|
if (listener_) {
|
||||||
const int64_t file_size =
|
const int64_t file_size =
|
||||||
File::GetFileSize(current_segment_path_.c_str());
|
File::GetFileSize(current_segment_path_.c_str());
|
||||||
listener_->OnNewSegment(
|
listener_->OnNewSegment(current_segment_path_,
|
||||||
current_segment_path_, current_segment_start_time_,
|
start_timestamp * timescale_scale_,
|
||||||
current_segment_total_sample_duration_ * kTsTimescale, file_size);
|
duration * timescale_scale_, file_size);
|
||||||
}
|
}
|
||||||
ts_writer_file_opened_ = false;
|
ts_writer_file_opened_ = false;
|
||||||
total_duration_in_seconds_ += current_segment_total_sample_duration_;
|
total_duration_in_seconds_ += duration * timescale_scale_ / kTsTimescale;
|
||||||
}
|
}
|
||||||
current_segment_total_sample_duration_ = 0.0;
|
|
||||||
current_segment_start_time_ = 0;
|
|
||||||
current_segment_path_.clear();
|
current_segment_path_.clear();
|
||||||
return NotifyEncrypted();
|
return NotifyEncrypted();
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,6 +54,18 @@ class TsSegmenter {
|
||||||
/// @return OK on success.
|
/// @return OK on success.
|
||||||
Status AddSample(std::shared_ptr<MediaSample> sample);
|
Status AddSample(std::shared_ptr<MediaSample> sample);
|
||||||
|
|
||||||
|
/// Flush all the samples that are (possibly) buffered and write them to the
|
||||||
|
/// current segment, this will close the file. If a file is not already opened
|
||||||
|
/// before calling this, this will open one and write them to file.
|
||||||
|
/// @param start_timestamp is the segment's start timestamp in the input
|
||||||
|
/// stream's time scale.
|
||||||
|
/// @param duration is the segment's duration in the input stream's time
|
||||||
|
/// scale.
|
||||||
|
// TODO(kqyang): Remove the usage of segment start timestamp and duration in
|
||||||
|
// xx_segmenter, which could cause confusions on which is the source of truth
|
||||||
|
// as the segment start timestamp and duration could be tracked locally.
|
||||||
|
Status FinalizeSegment(uint64_t start_timestamp, uint64_t duration);
|
||||||
|
|
||||||
/// Only for testing.
|
/// Only for testing.
|
||||||
void InjectTsWriterForTesting(std::unique_ptr<TsWriter> writer);
|
void InjectTsWriterForTesting(std::unique_ptr<TsWriter> writer);
|
||||||
|
|
||||||
|
@ -71,11 +83,6 @@ class TsSegmenter {
|
||||||
// it will open one. This will not close the file.
|
// it will open one. This will not close the file.
|
||||||
Status WritePesPacketsToFile();
|
Status WritePesPacketsToFile();
|
||||||
|
|
||||||
// Flush all the samples that are (possibly) buffered and write them to the
|
|
||||||
// current segment, this will close the file. If a file is not already opened
|
|
||||||
// before calling this, this will open one and write them to file.
|
|
||||||
Status Flush();
|
|
||||||
|
|
||||||
// If conditions are met, notify objects that the data is encrypted.
|
// If conditions are met, notify objects that the data is encrypted.
|
||||||
Status NotifyEncrypted();
|
Status NotifyEncrypted();
|
||||||
|
|
||||||
|
@ -86,12 +93,6 @@ class TsSegmenter {
|
||||||
// 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;
|
||||||
|
|
||||||
// This is the sum of the durations of the samples that were added to
|
|
||||||
// PesPacketGenerator for the current segment (in seconds). Note that this is
|
|
||||||
// not necessarily the same as the length of the PesPackets that have been
|
|
||||||
// written to the current segment in WritePesPacketsToFile().
|
|
||||||
double current_segment_total_sample_duration_ = 0.0;
|
|
||||||
|
|
||||||
// Used for segment template.
|
// Used for segment template.
|
||||||
uint64_t segment_number_ = 0;
|
uint64_t segment_number_ = 0;
|
||||||
|
|
||||||
|
@ -102,7 +103,6 @@ class TsSegmenter {
|
||||||
std::unique_ptr<PesPacketGenerator> pes_packet_generator_;
|
std::unique_ptr<PesPacketGenerator> pes_packet_generator_;
|
||||||
|
|
||||||
// For OnNewSegment().
|
// For OnNewSegment().
|
||||||
uint64_t current_segment_start_time_ = 0;
|
|
||||||
// Path of the current segment so that File::GetFileSize() can be used after
|
// Path of the current segment so that File::GetFileSize() can be used after
|
||||||
// the segment has been finalized.
|
// the segment has been finalized.
|
||||||
std::string current_segment_path_;
|
std::string current_segment_path_;
|
||||||
|
|
|
@ -133,7 +133,6 @@ TEST_F(TsSegmenterTest, AddSample) {
|
||||||
arraysize(kExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight,
|
arraysize(kExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight,
|
||||||
kTrickPlayRate, kNaluLengthSize, kLanguage, kIsEncrypted));
|
kTrickPlayRate, kNaluLengthSize, kLanguage, kIsEncrypted));
|
||||||
MuxerOptions options;
|
MuxerOptions options;
|
||||||
options.segment_duration = 10.0;
|
|
||||||
options.segment_template = "file$Number$.ts";
|
options.segment_template = "file$Number$.ts";
|
||||||
TsSegmenter segmenter(options, nullptr);
|
TsSegmenter segmenter(options, nullptr);
|
||||||
|
|
||||||
|
@ -175,10 +174,7 @@ TEST_F(TsSegmenterTest, AddSample) {
|
||||||
EXPECT_OK(segmenter.AddSample(sample));
|
EXPECT_OK(segmenter.AddSample(sample));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify the case where the segment is long enough and the current segment
|
// This will add one sample then finalize segment then add another sample.
|
||||||
// should be closed.
|
|
||||||
// This will add 2 samples and verify that the first segment is closed when the
|
|
||||||
// second sample is added.
|
|
||||||
TEST_F(TsSegmenterTest, PassedSegmentDuration) {
|
TEST_F(TsSegmenterTest, PassedSegmentDuration) {
|
||||||
// Use something significantly smaller than 90000 to check that the scaling is
|
// Use something significantly smaller than 90000 to check that the scaling is
|
||||||
// done correctly in the segmenter.
|
// done correctly in the segmenter.
|
||||||
|
@ -188,7 +184,6 @@ TEST_F(TsSegmenterTest, PassedSegmentDuration) {
|
||||||
kExtraData, arraysize(kExtraData), kWidth, kHeight, kPixelWidth,
|
kExtraData, arraysize(kExtraData), kWidth, kHeight, kPixelWidth,
|
||||||
kPixelHeight, kTrickPlayRate, kNaluLengthSize, kLanguage, kIsEncrypted));
|
kPixelHeight, kTrickPlayRate, kNaluLengthSize, kLanguage, kIsEncrypted));
|
||||||
MuxerOptions options;
|
MuxerOptions options;
|
||||||
options.segment_duration = 10.0;
|
|
||||||
options.segment_template = "file$Number$.ts";
|
options.segment_template = "file$Number$.ts";
|
||||||
|
|
||||||
MockMuxerListener mock_listener;
|
MockMuxerListener mock_listener;
|
||||||
|
@ -202,21 +197,18 @@ TEST_F(TsSegmenterTest, PassedSegmentDuration) {
|
||||||
|
|
||||||
std::shared_ptr<MediaSample> sample1 =
|
std::shared_ptr<MediaSample> sample1 =
|
||||||
MediaSample::CopyFrom(kAnyData, arraysize(kAnyData), kIsKeyFrame);
|
MediaSample::CopyFrom(kAnyData, arraysize(kAnyData), kIsKeyFrame);
|
||||||
|
sample1->set_duration(kInputTimescale * 11);
|
||||||
std::shared_ptr<MediaSample> sample2 =
|
std::shared_ptr<MediaSample> sample2 =
|
||||||
MediaSample::CopyFrom(kAnyData, arraysize(kAnyData), kIsKeyFrame);
|
MediaSample::CopyFrom(kAnyData, arraysize(kAnyData), kIsKeyFrame);
|
||||||
|
// Doesn't really matter how long this is.
|
||||||
// 11 seconds > 10 seconds (segment duration).
|
sample2->set_duration(kInputTimescale * 7);
|
||||||
// Expect the segment to be finalized.
|
|
||||||
sample1->set_duration(kInputTimescale * 11);
|
|
||||||
|
|
||||||
// (Finalize is not called at the end of this test so) Expect one segment
|
// (Finalize is not called at the end of this test so) Expect one segment
|
||||||
// event. The length should be the same as the above sample that exceeds the
|
// event. The length should be the same as the above sample that exceeds the
|
||||||
// duration.
|
// duration.
|
||||||
EXPECT_CALL(mock_listener,
|
EXPECT_CALL(mock_listener,
|
||||||
OnNewSegment("file1.ts", kFirstPts, kTimeScale * 11, _));
|
OnNewSegment("file1.ts", kFirstPts * kTimeScale / kInputTimescale,
|
||||||
|
kTimeScale * 11, _));
|
||||||
// Doesn't really matter how long this is.
|
|
||||||
sample2->set_duration(kInputTimescale * 7);
|
|
||||||
|
|
||||||
Sequence writer_sequence;
|
Sequence writer_sequence;
|
||||||
EXPECT_CALL(*mock_ts_writer_, NewSegment(StrEq("file1.ts")))
|
EXPECT_CALL(*mock_ts_writer_, NewSegment(StrEq("file1.ts")))
|
||||||
|
@ -263,11 +255,9 @@ TEST_F(TsSegmenterTest, PassedSegmentDuration) {
|
||||||
|
|
||||||
// The pointers are released inside the segmenter.
|
// The pointers are released inside the segmenter.
|
||||||
Sequence pes_packet_sequence;
|
Sequence pes_packet_sequence;
|
||||||
PesPacket* first_pes = new PesPacket();
|
|
||||||
first_pes->set_pts(kFirstPts);
|
|
||||||
EXPECT_CALL(*mock_pes_packet_generator_, GetNextPesPacketMock())
|
EXPECT_CALL(*mock_pes_packet_generator_, GetNextPesPacketMock())
|
||||||
.InSequence(pes_packet_sequence)
|
.InSequence(pes_packet_sequence)
|
||||||
.WillOnce(Return(first_pes));
|
.WillOnce(Return(new PesPacket));
|
||||||
EXPECT_CALL(*mock_pes_packet_generator_, GetNextPesPacketMock())
|
EXPECT_CALL(*mock_pes_packet_generator_, GetNextPesPacketMock())
|
||||||
.InSequence(pes_packet_sequence)
|
.InSequence(pes_packet_sequence)
|
||||||
.WillOnce(Return(new PesPacket()));
|
.WillOnce(Return(new PesPacket()));
|
||||||
|
@ -277,6 +267,7 @@ TEST_F(TsSegmenterTest, PassedSegmentDuration) {
|
||||||
std::move(mock_pes_packet_generator_));
|
std::move(mock_pes_packet_generator_));
|
||||||
EXPECT_OK(segmenter.Initialize(*stream_info, nullptr, 0, 0, 0, 0));
|
EXPECT_OK(segmenter.Initialize(*stream_info, nullptr, 0, 0, 0, 0));
|
||||||
EXPECT_OK(segmenter.AddSample(sample1));
|
EXPECT_OK(segmenter.AddSample(sample1));
|
||||||
|
EXPECT_OK(segmenter.FinalizeSegment(kFirstPts, sample1->duration()));
|
||||||
EXPECT_OK(segmenter.AddSample(sample2));
|
EXPECT_OK(segmenter.AddSample(sample2));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -287,7 +278,6 @@ TEST_F(TsSegmenterTest, InitializeThenFinalize) {
|
||||||
arraysize(kExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight,
|
arraysize(kExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight,
|
||||||
kTrickPlayRate, kNaluLengthSize, kLanguage, kIsEncrypted));
|
kTrickPlayRate, kNaluLengthSize, kLanguage, kIsEncrypted));
|
||||||
MuxerOptions options;
|
MuxerOptions options;
|
||||||
options.segment_duration = 10.0;
|
|
||||||
options.segment_template = "file$Number$.ts";
|
options.segment_template = "file$Number$.ts";
|
||||||
TsSegmenter segmenter(options, nullptr);
|
TsSegmenter segmenter(options, nullptr);
|
||||||
|
|
||||||
|
@ -295,7 +285,7 @@ TEST_F(TsSegmenterTest, InitializeThenFinalize) {
|
||||||
EXPECT_CALL(*mock_pes_packet_generator_, Initialize(_))
|
EXPECT_CALL(*mock_pes_packet_generator_, Initialize(_))
|
||||||
.WillOnce(Return(true));
|
.WillOnce(Return(true));
|
||||||
|
|
||||||
EXPECT_CALL(*mock_pes_packet_generator_, Flush()).WillOnce(Return(true));
|
EXPECT_CALL(*mock_pes_packet_generator_, Flush()).Times(0);
|
||||||
ON_CALL(*mock_pes_packet_generator_, NumberOfReadyPesPackets())
|
ON_CALL(*mock_pes_packet_generator_, NumberOfReadyPesPackets())
|
||||||
.WillByDefault(Return(0));
|
.WillByDefault(Return(0));
|
||||||
|
|
||||||
|
@ -310,13 +300,12 @@ TEST_F(TsSegmenterTest, InitializeThenFinalize) {
|
||||||
// been initialized.
|
// been initialized.
|
||||||
// The test does not really add any samples but instead simulates an initialized
|
// The test does not really add any samples but instead simulates an initialized
|
||||||
// writer with a mock.
|
// writer with a mock.
|
||||||
TEST_F(TsSegmenterTest, Finalize) {
|
TEST_F(TsSegmenterTest, FinalizeSegment) {
|
||||||
std::shared_ptr<VideoStreamInfo> stream_info(new VideoStreamInfo(
|
std::shared_ptr<VideoStreamInfo> stream_info(new VideoStreamInfo(
|
||||||
kTrackId, kTimeScale, kDuration, kH264Codec, kCodecString, kExtraData,
|
kTrackId, kTimeScale, kDuration, kH264Codec, kCodecString, kExtraData,
|
||||||
arraysize(kExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight,
|
arraysize(kExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight,
|
||||||
kTrickPlayRate, kNaluLengthSize, kLanguage, kIsEncrypted));
|
kTrickPlayRate, kNaluLengthSize, kLanguage, kIsEncrypted));
|
||||||
MuxerOptions options;
|
MuxerOptions options;
|
||||||
options.segment_duration = 10.0;
|
|
||||||
options.segment_template = "file$Number$.ts";
|
options.segment_template = "file$Number$.ts";
|
||||||
TsSegmenter segmenter(options, nullptr);
|
TsSegmenter segmenter(options, nullptr);
|
||||||
|
|
||||||
|
@ -335,114 +324,7 @@ TEST_F(TsSegmenterTest, Finalize) {
|
||||||
std::move(mock_pes_packet_generator_));
|
std::move(mock_pes_packet_generator_));
|
||||||
EXPECT_OK(segmenter.Initialize(*stream_info, nullptr, 0, 0, 0, 0));
|
EXPECT_OK(segmenter.Initialize(*stream_info, nullptr, 0, 0, 0, 0));
|
||||||
segmenter.SetTsWriterFileOpenedForTesting(true);
|
segmenter.SetTsWriterFileOpenedForTesting(true);
|
||||||
EXPECT_OK(segmenter.Finalize());
|
EXPECT_OK(segmenter.FinalizeSegment(0, 100 /* arbitrary duration */));
|
||||||
}
|
|
||||||
|
|
||||||
// Verify that it won't finish a segment if the sample is not a key frame.
|
|
||||||
TEST_F(TsSegmenterTest, SegmentOnlyBeforeKeyFrame) {
|
|
||||||
std::shared_ptr<VideoStreamInfo> stream_info(new VideoStreamInfo(
|
|
||||||
kTrackId, kTimeScale, kDuration, kH264Codec, kCodecString, kExtraData,
|
|
||||||
arraysize(kExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight,
|
|
||||||
kTrickPlayRate, kNaluLengthSize, kLanguage, kIsEncrypted));
|
|
||||||
MuxerOptions options;
|
|
||||||
options.segment_duration = 10.0;
|
|
||||||
options.segment_template = "file$Number$.ts";
|
|
||||||
TsSegmenter segmenter(options, nullptr);
|
|
||||||
|
|
||||||
EXPECT_CALL(*mock_ts_writer_, Initialize(_)).WillOnce(Return(true));
|
|
||||||
EXPECT_CALL(*mock_pes_packet_generator_, Initialize(_))
|
|
||||||
.WillOnce(Return(true));
|
|
||||||
|
|
||||||
const uint8_t kAnyData[] = {
|
|
||||||
0x01, 0x0F, 0x3C,
|
|
||||||
};
|
|
||||||
std::shared_ptr<MediaSample> key_frame_sample1 =
|
|
||||||
MediaSample::CopyFrom(kAnyData, arraysize(kAnyData), kIsKeyFrame);
|
|
||||||
std::shared_ptr<MediaSample> non_key_frame_sample =
|
|
||||||
MediaSample::CopyFrom(kAnyData, arraysize(kAnyData), !kIsKeyFrame);
|
|
||||||
std::shared_ptr<MediaSample> key_frame_sample2 =
|
|
||||||
MediaSample::CopyFrom(kAnyData, arraysize(kAnyData), kIsKeyFrame);
|
|
||||||
|
|
||||||
// 11 seconds > 10 seconds (segment duration).
|
|
||||||
key_frame_sample1->set_duration(kTimeScale * 11);
|
|
||||||
|
|
||||||
// But since the second sample is not a key frame, it shouldn't be segmented.
|
|
||||||
non_key_frame_sample->set_duration(kTimeScale * 7);
|
|
||||||
|
|
||||||
// Since this is a key frame, it should be segmented when this is added.
|
|
||||||
key_frame_sample2->set_duration(kTimeScale * 3);
|
|
||||||
|
|
||||||
EXPECT_CALL(*mock_pes_packet_generator_, PushSample(_))
|
|
||||||
.Times(3)
|
|
||||||
.WillRepeatedly(Return(true));
|
|
||||||
|
|
||||||
Sequence writer_sequence;
|
|
||||||
EXPECT_CALL(*mock_ts_writer_, NewSegment(StrEq("file1.ts")))
|
|
||||||
.InSequence(writer_sequence)
|
|
||||||
.WillOnce(Return(true));
|
|
||||||
|
|
||||||
Sequence ready_pes_sequence;
|
|
||||||
// First AddSample().
|
|
||||||
EXPECT_CALL(*mock_pes_packet_generator_, NumberOfReadyPesPackets())
|
|
||||||
.InSequence(ready_pes_sequence)
|
|
||||||
.WillOnce(Return(1u));
|
|
||||||
EXPECT_CALL(*mock_pes_packet_generator_, NumberOfReadyPesPackets())
|
|
||||||
.InSequence(ready_pes_sequence)
|
|
||||||
.WillOnce(Return(0u));
|
|
||||||
// Second AddSample().
|
|
||||||
EXPECT_CALL(*mock_pes_packet_generator_, NumberOfReadyPesPackets())
|
|
||||||
.InSequence(ready_pes_sequence)
|
|
||||||
.WillOnce(Return(1u));
|
|
||||||
EXPECT_CALL(*mock_pes_packet_generator_, NumberOfReadyPesPackets())
|
|
||||||
.InSequence(ready_pes_sequence)
|
|
||||||
.WillOnce(Return(0u));
|
|
||||||
// Third AddSample(), in Flush().
|
|
||||||
EXPECT_CALL(*mock_pes_packet_generator_, NumberOfReadyPesPackets())
|
|
||||||
.InSequence(ready_pes_sequence)
|
|
||||||
.WillOnce(Return(0u));
|
|
||||||
// Third AddSample() after Flush().
|
|
||||||
EXPECT_CALL(*mock_pes_packet_generator_, NumberOfReadyPesPackets())
|
|
||||||
.InSequence(ready_pes_sequence)
|
|
||||||
.WillOnce(Return(1u));
|
|
||||||
EXPECT_CALL(*mock_pes_packet_generator_, NumberOfReadyPesPackets())
|
|
||||||
.InSequence(ready_pes_sequence)
|
|
||||||
.WillOnce(Return(0u));
|
|
||||||
|
|
||||||
EXPECT_CALL(*mock_pes_packet_generator_, Flush())
|
|
||||||
.WillOnce(Return(true));
|
|
||||||
|
|
||||||
EXPECT_CALL(*mock_ts_writer_, FinalizeSegment())
|
|
||||||
.InSequence(writer_sequence)
|
|
||||||
.WillOnce(Return(true));
|
|
||||||
|
|
||||||
// Expectations for second AddSample() for the second segment.
|
|
||||||
EXPECT_CALL(*mock_ts_writer_, NewSegment(StrEq("file2.ts")))
|
|
||||||
.InSequence(writer_sequence)
|
|
||||||
.WillOnce(Return(true));
|
|
||||||
|
|
||||||
EXPECT_CALL(*mock_ts_writer_, AddPesPacketMock(_))
|
|
||||||
.Times(3)
|
|
||||||
.WillRepeatedly(Return(true));
|
|
||||||
|
|
||||||
// The pointers are released inside the segmenter.
|
|
||||||
Sequence pes_packet_sequence;
|
|
||||||
EXPECT_CALL(*mock_pes_packet_generator_, GetNextPesPacketMock())
|
|
||||||
.InSequence(pes_packet_sequence)
|
|
||||||
.WillOnce(Return(new PesPacket()));
|
|
||||||
EXPECT_CALL(*mock_pes_packet_generator_, GetNextPesPacketMock())
|
|
||||||
.InSequence(pes_packet_sequence)
|
|
||||||
.WillOnce(Return(new PesPacket()));
|
|
||||||
EXPECT_CALL(*mock_pes_packet_generator_, GetNextPesPacketMock())
|
|
||||||
.InSequence(pes_packet_sequence)
|
|
||||||
.WillOnce(Return(new PesPacket()));
|
|
||||||
|
|
||||||
segmenter.InjectTsWriterForTesting(std::move(mock_ts_writer_));
|
|
||||||
segmenter.InjectPesPacketGeneratorForTesting(
|
|
||||||
std::move(mock_pes_packet_generator_));
|
|
||||||
EXPECT_OK(segmenter.Initialize(*stream_info, nullptr, 0, 0, 0, 0));
|
|
||||||
EXPECT_OK(segmenter.AddSample(key_frame_sample1));
|
|
||||||
EXPECT_OK(segmenter.AddSample(non_key_frame_sample));
|
|
||||||
EXPECT_OK(segmenter.AddSample(key_frame_sample2));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(TsSegmenterTest, WithEncryptionNoClearLead) {
|
TEST_F(TsSegmenterTest, WithEncryptionNoClearLead) {
|
||||||
|
@ -451,7 +333,6 @@ TEST_F(TsSegmenterTest, WithEncryptionNoClearLead) {
|
||||||
arraysize(kExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight,
|
arraysize(kExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight,
|
||||||
kTrickPlayRate, kNaluLengthSize, kLanguage, kIsEncrypted));
|
kTrickPlayRate, kNaluLengthSize, kLanguage, kIsEncrypted));
|
||||||
MuxerOptions options;
|
MuxerOptions options;
|
||||||
options.segment_duration = 10.0;
|
|
||||||
options.segment_template = "file$Number$.ts";
|
options.segment_template = "file$Number$.ts";
|
||||||
|
|
||||||
MockMuxerListener mock_listener;
|
MockMuxerListener mock_listener;
|
||||||
|
@ -494,7 +375,7 @@ TEST_F(TsSegmenterTest, WithEncryptionNoClearLeadNoMuxerListener) {
|
||||||
arraysize(kExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight,
|
arraysize(kExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight,
|
||||||
kTrickPlayRate, kNaluLengthSize, kLanguage, kIsEncrypted));
|
kTrickPlayRate, kNaluLengthSize, kLanguage, kIsEncrypted));
|
||||||
MuxerOptions options;
|
MuxerOptions options;
|
||||||
options.segment_duration = 10.0;
|
// options.segment_duration = 10.0;
|
||||||
options.segment_template = "file$Number$.ts";
|
options.segment_template = "file$Number$.ts";
|
||||||
|
|
||||||
TsSegmenter segmenter(options, nullptr);
|
TsSegmenter segmenter(options, nullptr);
|
||||||
|
@ -535,7 +416,6 @@ TEST_F(TsSegmenterTest, WithEncryptionWithClearLead) {
|
||||||
kTrickPlayRate, kNaluLengthSize, kLanguage, kIsEncrypted));
|
kTrickPlayRate, kNaluLengthSize, kLanguage, kIsEncrypted));
|
||||||
MuxerOptions options;
|
MuxerOptions options;
|
||||||
|
|
||||||
options.segment_duration = 1.0;
|
|
||||||
const uint32_t k1080pPixels = 1920 * 1080;
|
const uint32_t k1080pPixels = 1920 * 1080;
|
||||||
const uint32_t k2160pPixels = 4096 * 2160;
|
const uint32_t k2160pPixels = 4096 * 2160;
|
||||||
const double kClearLeadSeconds = 1.0;
|
const double kClearLeadSeconds = 1.0;
|
||||||
|
@ -557,12 +437,9 @@ TEST_F(TsSegmenterTest, WithEncryptionWithClearLead) {
|
||||||
};
|
};
|
||||||
std::shared_ptr<MediaSample> sample1 =
|
std::shared_ptr<MediaSample> sample1 =
|
||||||
MediaSample::CopyFrom(kAnyData, arraysize(kAnyData), kIsKeyFrame);
|
MediaSample::CopyFrom(kAnyData, arraysize(kAnyData), kIsKeyFrame);
|
||||||
|
sample1->set_duration(kTimeScale * 2);
|
||||||
std::shared_ptr<MediaSample> sample2 =
|
std::shared_ptr<MediaSample> sample2 =
|
||||||
MediaSample::CopyFrom(kAnyData, arraysize(kAnyData), kIsKeyFrame);
|
MediaSample::CopyFrom(kAnyData, arraysize(kAnyData), kIsKeyFrame);
|
||||||
|
|
||||||
// Something longer than 1.0 (segment duration and clear lead).
|
|
||||||
sample1->set_duration(kTimeScale * 2);
|
|
||||||
// The length of the second sample doesn't really matter.
|
|
||||||
sample2->set_duration(kTimeScale * 3);
|
sample2->set_duration(kTimeScale * 3);
|
||||||
|
|
||||||
EXPECT_CALL(*mock_pes_packet_generator_, PushSample(_))
|
EXPECT_CALL(*mock_pes_packet_generator_, PushSample(_))
|
||||||
|
@ -622,12 +499,13 @@ TEST_F(TsSegmenterTest, WithEncryptionWithClearLead) {
|
||||||
kClearLeadSeconds));
|
kClearLeadSeconds));
|
||||||
EXPECT_OK(segmenter.AddSample(sample1));
|
EXPECT_OK(segmenter.AddSample(sample1));
|
||||||
|
|
||||||
// These should be called AFTER the first AddSample(), before the second
|
// Encryption should be setup after finalizing the first segment.
|
||||||
// segment.
|
// These should be called when finalize segment is called.
|
||||||
EXPECT_CALL(mock_listener, OnEncryptionStart());
|
EXPECT_CALL(mock_listener, OnEncryptionStart());
|
||||||
EXPECT_CALL(*mock_pes_packet_generator_raw, SetEncryptionKeyMock(_))
|
EXPECT_CALL(*mock_pes_packet_generator_raw, SetEncryptionKeyMock(_))
|
||||||
.WillOnce(Return(true));
|
.WillOnce(Return(true));
|
||||||
EXPECT_CALL(*mock_ts_writer_raw, SignalEncrypted());
|
EXPECT_CALL(*mock_ts_writer_raw, SignalEncrypted());
|
||||||
|
EXPECT_OK(segmenter.FinalizeSegment(0, sample1->duration()));
|
||||||
EXPECT_OK(segmenter.AddSample(sample2));
|
EXPECT_OK(segmenter.AddSample(sample2));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -52,6 +52,8 @@ class Fragmenter {
|
||||||
/// Fill @a reference with current fragment information.
|
/// Fill @a reference with current fragment information.
|
||||||
void GenerateSegmentReference(SegmentReference* reference);
|
void GenerateSegmentReference(SegmentReference* reference);
|
||||||
|
|
||||||
|
void ClearFragmentFinalized() { fragment_finalized_ = false; }
|
||||||
|
|
||||||
uint64_t fragment_duration() const { return fragment_duration_; }
|
uint64_t fragment_duration() const { return fragment_duration_; }
|
||||||
uint64_t first_sap_time() const { return first_sap_time_; }
|
uint64_t first_sap_time() const { return first_sap_time_; }
|
||||||
uint64_t earliest_presentation_time() const {
|
uint64_t earliest_presentation_time() const {
|
||||||
|
|
|
@ -161,9 +161,18 @@ Status MP4Muxer::Finalize() {
|
||||||
return Status::OK;
|
return Status::OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
Status MP4Muxer::DoAddSample(std::shared_ptr<MediaSample> sample) {
|
Status MP4Muxer::AddSample(int stream_id, std::shared_ptr<MediaSample> sample) {
|
||||||
DCHECK(segmenter_);
|
DCHECK(segmenter_);
|
||||||
return segmenter_->AddSample(*streams()[0], sample);
|
return segmenter_->AddSample(stream_id, sample);
|
||||||
|
}
|
||||||
|
|
||||||
|
Status MP4Muxer::FinalizeSegment(int stream_id,
|
||||||
|
std::shared_ptr<SegmentInfo> segment_info) {
|
||||||
|
DCHECK(segmenter_);
|
||||||
|
VLOG(3) << "Finalize " << (segment_info->is_subsegment ? "sub" : "")
|
||||||
|
<< "segment " << segment_info->start_timestamp << " duration "
|
||||||
|
<< segment_info->duration;
|
||||||
|
return segmenter_->FinalizeSegment(stream_id, segment_info->is_subsegment);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MP4Muxer::InitializeTrak(const StreamInfo* info, Track* trak) {
|
void MP4Muxer::InitializeTrak(const StreamInfo* info, Track* trak) {
|
||||||
|
|
|
@ -37,7 +37,9 @@ class MP4Muxer : public Muxer {
|
||||||
// Muxer implementation overrides.
|
// Muxer implementation overrides.
|
||||||
Status InitializeMuxer() override;
|
Status InitializeMuxer() override;
|
||||||
Status Finalize() override;
|
Status Finalize() override;
|
||||||
Status DoAddSample(std::shared_ptr<MediaSample> sample) override;
|
Status AddSample(int stream_id, std::shared_ptr<MediaSample> sample) override;
|
||||||
|
Status FinalizeSegment(int stream_id,
|
||||||
|
std::shared_ptr<SegmentInfo> segment_info) override;
|
||||||
|
|
||||||
// Generate Audio/Video Track box.
|
// Generate Audio/Video Track box.
|
||||||
void InitializeTrak(const StreamInfo* info, Track* trak);
|
void InitializeTrak(const StreamInfo* info, Track* trak);
|
||||||
|
|
|
@ -178,7 +178,6 @@ Status Segmenter::Initialize(
|
||||||
moof_->header.sequence_number = 0;
|
moof_->header.sequence_number = 0;
|
||||||
|
|
||||||
moof_->tracks.resize(streams.size());
|
moof_->tracks.resize(streams.size());
|
||||||
segment_durations_.resize(streams.size());
|
|
||||||
fragmenters_.resize(streams.size());
|
fragmenters_.resize(streams.size());
|
||||||
const bool key_rotation_enabled = crypto_period_duration_in_seconds != 0;
|
const bool key_rotation_enabled = crypto_period_duration_in_seconds != 0;
|
||||||
const bool kInitialEncryptionInfo = true;
|
const bool kInitialEncryptionInfo = true;
|
||||||
|
@ -292,12 +291,6 @@ Status Segmenter::Initialize(
|
||||||
}
|
}
|
||||||
|
|
||||||
Status Segmenter::Finalize() {
|
Status Segmenter::Finalize() {
|
||||||
for (const std::unique_ptr<Fragmenter>& fragmenter : fragmenters_) {
|
|
||||||
Status status = FinalizeFragment(true, fragmenter.get());
|
|
||||||
if (!status.ok())
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set tracks and moov durations.
|
// Set tracks and moov durations.
|
||||||
// Note that the updated moov box will be written to output file for VOD case
|
// Note that the updated moov box will be written to output file for VOD case
|
||||||
// only.
|
// only.
|
||||||
|
@ -315,111 +308,35 @@ Status Segmenter::Finalize() {
|
||||||
return DoFinalize();
|
return DoFinalize();
|
||||||
}
|
}
|
||||||
|
|
||||||
Status Segmenter::AddSample(const StreamInfo& stream_info,
|
Status Segmenter::AddSample(int stream_id,
|
||||||
std::shared_ptr<MediaSample> sample) {
|
std::shared_ptr<MediaSample> sample) {
|
||||||
// TODO(kqyang): Stream id should be passed in.
|
|
||||||
const uint32_t stream_id = 0;
|
|
||||||
Fragmenter* fragmenter = fragmenters_[stream_id].get();
|
|
||||||
|
|
||||||
// Set default sample duration if it has not been set yet.
|
// Set default sample duration if it has not been set yet.
|
||||||
if (moov_->extends.tracks[stream_id].default_sample_duration == 0) {
|
if (moov_->extends.tracks[stream_id].default_sample_duration == 0) {
|
||||||
moov_->extends.tracks[stream_id].default_sample_duration =
|
moov_->extends.tracks[stream_id].default_sample_duration =
|
||||||
sample->duration();
|
sample->duration();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DCHECK_LT(stream_id, static_cast<int>(fragmenters_.size()));
|
||||||
|
Fragmenter* fragmenter = fragmenters_[stream_id].get();
|
||||||
if (fragmenter->fragment_finalized()) {
|
if (fragmenter->fragment_finalized()) {
|
||||||
return Status(error::FRAGMENT_FINALIZED,
|
return Status(error::FRAGMENT_FINALIZED,
|
||||||
"Current fragment is finalized already.");
|
"Current fragment is finalized already.");
|
||||||
}
|
}
|
||||||
|
|
||||||
bool finalize_fragment = false;
|
Status status = fragmenter->AddSample(sample);
|
||||||
if (fragmenter->fragment_duration() >=
|
|
||||||
options_.fragment_duration * stream_info.time_scale()) {
|
|
||||||
if (sample->is_key_frame() || !options_.fragment_sap_aligned) {
|
|
||||||
finalize_fragment = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bool finalize_segment = false;
|
|
||||||
if (segment_durations_[stream_id] >=
|
|
||||||
options_.segment_duration * stream_info.time_scale()) {
|
|
||||||
if (sample->is_key_frame() || !options_.segment_sap_aligned) {
|
|
||||||
finalize_segment = true;
|
|
||||||
finalize_fragment = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Status status;
|
|
||||||
if (finalize_fragment) {
|
|
||||||
status = FinalizeFragment(finalize_segment, fragmenter);
|
|
||||||
if (!status.ok())
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
status = fragmenter->AddSample(sample);
|
|
||||||
if (!status.ok())
|
if (!status.ok())
|
||||||
return status;
|
return status;
|
||||||
|
|
||||||
if (sample_duration_ == 0)
|
if (sample_duration_ == 0)
|
||||||
sample_duration_ = sample->duration();
|
sample_duration_ = sample->duration();
|
||||||
moov_->tracks[stream_id].media.header.duration += sample->duration();
|
moov_->tracks[stream_id].media.header.duration += sample->duration();
|
||||||
segment_durations_[stream_id] += sample->duration();
|
|
||||||
DCHECK_GE(segment_durations_[stream_id], fragmenter->fragment_duration());
|
|
||||||
return Status::OK;
|
return Status::OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t Segmenter::GetReferenceTimeScale() const {
|
Status Segmenter::FinalizeSegment(int stream_id, bool is_subsegment) {
|
||||||
return moov_->header.timescale;
|
DCHECK_LT(stream_id, static_cast<int>(fragmenters_.size()));
|
||||||
}
|
Fragmenter* fragmenter = fragmenters_[stream_id].get();
|
||||||
|
DCHECK(fragmenter);
|
||||||
double Segmenter::GetDuration() const {
|
|
||||||
if (moov_->header.timescale == 0) {
|
|
||||||
// Handling the case where this is not properly initialized.
|
|
||||||
return 0.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return static_cast<double>(moov_->header.duration) / moov_->header.timescale;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Segmenter::UpdateProgress(uint64_t progress) {
|
|
||||||
accumulated_progress_ += progress;
|
|
||||||
|
|
||||||
if (!progress_listener_) return;
|
|
||||||
if (progress_target_ == 0) return;
|
|
||||||
// It might happen that accumulated progress exceeds progress_target due to
|
|
||||||
// computation errors, e.g. rounding error. Cap it so it never reports > 100%
|
|
||||||
// progress.
|
|
||||||
if (accumulated_progress_ >= progress_target_) {
|
|
||||||
progress_listener_->OnProgress(1.0);
|
|
||||||
} else {
|
|
||||||
progress_listener_->OnProgress(static_cast<double>(accumulated_progress_) /
|
|
||||||
progress_target_);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Segmenter::SetComplete() {
|
|
||||||
if (!progress_listener_) return;
|
|
||||||
progress_listener_->OnProgress(1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
Status Segmenter::FinalizeSegment() {
|
|
||||||
Status status = DoFinalizeSegment();
|
|
||||||
|
|
||||||
// Reset segment information to initial state.
|
|
||||||
sidx_->references.clear();
|
|
||||||
std::vector<uint64_t>::iterator it = segment_durations_.begin();
|
|
||||||
for (; it != segment_durations_.end(); ++it)
|
|
||||||
*it = 0;
|
|
||||||
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t Segmenter::GetReferenceStreamId() {
|
|
||||||
DCHECK(sidx_);
|
|
||||||
return sidx_->reference_id - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
Status Segmenter::FinalizeFragment(bool finalize_segment,
|
|
||||||
Fragmenter* fragmenter) {
|
|
||||||
fragmenter->FinalizeFragment();
|
fragmenter->FinalizeFragment();
|
||||||
|
|
||||||
// Check if all tracks are ready for fragmentation.
|
// Check if all tracks are ready for fragmentation.
|
||||||
|
@ -468,12 +385,56 @@ Status Segmenter::FinalizeFragment(bool finalize_segment,
|
||||||
// Increase sequence_number for next fragment.
|
// Increase sequence_number for next fragment.
|
||||||
++moof_->header.sequence_number;
|
++moof_->header.sequence_number;
|
||||||
|
|
||||||
if (finalize_segment)
|
for (std::unique_ptr<Fragmenter>& fragmenter : fragmenters_)
|
||||||
return FinalizeSegment();
|
fragmenter->ClearFragmentFinalized();
|
||||||
|
if (!is_subsegment) {
|
||||||
|
Status status = DoFinalizeSegment();
|
||||||
|
// Reset segment information to initial state.
|
||||||
|
sidx_->references.clear();
|
||||||
|
return status;
|
||||||
|
}
|
||||||
return Status::OK;
|
return Status::OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint32_t Segmenter::GetReferenceTimeScale() const {
|
||||||
|
return moov_->header.timescale;
|
||||||
|
}
|
||||||
|
|
||||||
|
double Segmenter::GetDuration() const {
|
||||||
|
if (moov_->header.timescale == 0) {
|
||||||
|
// Handling the case where this is not properly initialized.
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return static_cast<double>(moov_->header.duration) / moov_->header.timescale;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Segmenter::UpdateProgress(uint64_t progress) {
|
||||||
|
accumulated_progress_ += progress;
|
||||||
|
|
||||||
|
if (!progress_listener_) return;
|
||||||
|
if (progress_target_ == 0) return;
|
||||||
|
// It might happen that accumulated progress exceeds progress_target due to
|
||||||
|
// computation errors, e.g. rounding error. Cap it so it never reports > 100%
|
||||||
|
// progress.
|
||||||
|
if (accumulated_progress_ >= progress_target_) {
|
||||||
|
progress_listener_->OnProgress(1.0);
|
||||||
|
} else {
|
||||||
|
progress_listener_->OnProgress(static_cast<double>(accumulated_progress_) /
|
||||||
|
progress_target_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Segmenter::SetComplete() {
|
||||||
|
if (!progress_listener_) return;
|
||||||
|
progress_listener_->OnProgress(1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t Segmenter::GetReferenceStreamId() {
|
||||||
|
DCHECK(sidx_);
|
||||||
|
return sidx_->reference_id - 1;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace mp4
|
} // namespace mp4
|
||||||
} // namespace media
|
} // namespace media
|
||||||
} // namespace shaka
|
} // namespace shaka
|
||||||
|
|
|
@ -85,10 +85,16 @@ class Segmenter {
|
||||||
Status Finalize();
|
Status Finalize();
|
||||||
|
|
||||||
/// Add sample to the indicated stream.
|
/// Add sample to the indicated stream.
|
||||||
|
/// @param stream_id is the zero-based stream index.
|
||||||
/// @param sample points to the sample to be added.
|
/// @param sample points to the sample to be added.
|
||||||
/// @return OK on success, an error status otherwise.
|
/// @return OK on success, an error status otherwise.
|
||||||
Status AddSample(const StreamInfo& stream_Info,
|
Status AddSample(int stream_id, std::shared_ptr<MediaSample> sample);
|
||||||
std::shared_ptr<MediaSample> sample);
|
|
||||||
|
/// Finalize the segment / subsegment.
|
||||||
|
/// @param stream_id is the zero-based stream index.
|
||||||
|
/// @param is_subsegment indicates if it is a subsegment (fragment).
|
||||||
|
/// @return OK on success, an error status otherwise.
|
||||||
|
Status FinalizeSegment(int stream_id, bool is_subsegment);
|
||||||
|
|
||||||
/// @return true if there is an initialization range, while setting @a offset
|
/// @return true if there is an initialization range, while setting @a offset
|
||||||
/// and @a size; or false if initialization range does not apply.
|
/// and @a size; or false if initialization range does not apply.
|
||||||
|
@ -130,11 +136,8 @@ class Segmenter {
|
||||||
virtual Status DoFinalize() = 0;
|
virtual Status DoFinalize() = 0;
|
||||||
virtual Status DoFinalizeSegment() = 0;
|
virtual Status DoFinalizeSegment() = 0;
|
||||||
|
|
||||||
Status FinalizeSegment();
|
|
||||||
uint32_t GetReferenceStreamId();
|
uint32_t GetReferenceStreamId();
|
||||||
|
|
||||||
Status FinalizeFragment(bool finalize_segment, Fragmenter* fragment);
|
|
||||||
|
|
||||||
const MuxerOptions& options_;
|
const MuxerOptions& options_;
|
||||||
std::unique_ptr<FileType> ftyp_;
|
std::unique_ptr<FileType> ftyp_;
|
||||||
std::unique_ptr<Movie> moov_;
|
std::unique_ptr<Movie> moov_;
|
||||||
|
@ -142,7 +145,6 @@ class Segmenter {
|
||||||
std::unique_ptr<BufferWriter> fragment_buffer_;
|
std::unique_ptr<BufferWriter> fragment_buffer_;
|
||||||
std::unique_ptr<SegmentIndex> sidx_;
|
std::unique_ptr<SegmentIndex> sidx_;
|
||||||
std::vector<std::unique_ptr<Fragmenter>> fragmenters_;
|
std::vector<std::unique_ptr<Fragmenter>> fragmenters_;
|
||||||
std::vector<uint64_t> segment_durations_;
|
|
||||||
MuxerListener* muxer_listener_;
|
MuxerListener* muxer_listener_;
|
||||||
ProgressListener* progress_listener_;
|
ProgressListener* progress_listener_;
|
||||||
uint64_t progress_target_;
|
uint64_t progress_target_;
|
||||||
|
|
|
@ -14,6 +14,7 @@ namespace media {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
const uint64_t kDuration = 1000;
|
const uint64_t kDuration = 1000;
|
||||||
|
const bool kSubsegment = true;
|
||||||
const std::string kKeyId = "4c6f72656d20697073756d20646f6c6f";
|
const std::string kKeyId = "4c6f72656d20697073756d20646f6c6f";
|
||||||
const std::string kIv = "0123456789012345";
|
const std::string kIv = "0123456789012345";
|
||||||
const std::string kKey = "01234567890123456789012345678901";
|
const std::string kKey = "01234567890123456789012345678901";
|
||||||
|
@ -210,17 +211,20 @@ class EncrypedSegmenterTest : public SegmentTestBase {
|
||||||
|
|
||||||
TEST_F(EncrypedSegmenterTest, BasicSupport) {
|
TEST_F(EncrypedSegmenterTest, BasicSupport) {
|
||||||
MuxerOptions options = CreateMuxerOptions();
|
MuxerOptions options = CreateMuxerOptions();
|
||||||
options.segment_duration = 3.0;
|
|
||||||
ASSERT_NO_FATAL_FAILURE(InitializeSegmenter(options));
|
ASSERT_NO_FATAL_FAILURE(InitializeSegmenter(options));
|
||||||
|
|
||||||
// Write the samples to the Segmenter.
|
// Write the samples to the Segmenter.
|
||||||
// There should be 2 segments with the first segment in clear and the second
|
// There should be 2 segments with the first segment in clear and the second
|
||||||
// segment encrypted.
|
// segment encrypted.
|
||||||
for (int i = 0; i < 5; i++) {
|
for (int i = 0; i < 5; i++) {
|
||||||
|
if (i == 3)
|
||||||
|
ASSERT_OK(segmenter_->FinalizeSegment(0, 3 * kDuration, !kSubsegment));
|
||||||
std::shared_ptr<MediaSample> sample =
|
std::shared_ptr<MediaSample> sample =
|
||||||
CreateSample(kKeyFrame, kDuration, kNoSideData);
|
CreateSample(kKeyFrame, kDuration, kNoSideData);
|
||||||
ASSERT_OK(segmenter_->AddSample(sample));
|
ASSERT_OK(segmenter_->AddSample(sample));
|
||||||
}
|
}
|
||||||
|
ASSERT_OK(
|
||||||
|
segmenter_->FinalizeSegment(3 * kDuration, 2 * kDuration, !kSubsegment));
|
||||||
ASSERT_OK(segmenter_->Finalize());
|
ASSERT_OK(segmenter_->Finalize());
|
||||||
|
|
||||||
ASSERT_FILE_ENDS_WITH(OutputFileName().c_str(), kBasicSupportData);
|
ASSERT_FILE_ENDS_WITH(OutputFileName().c_str(), kBasicSupportData);
|
||||||
|
|
|
@ -15,11 +15,33 @@
|
||||||
namespace shaka {
|
namespace shaka {
|
||||||
namespace media {
|
namespace media {
|
||||||
namespace webm {
|
namespace webm {
|
||||||
|
|
||||||
MultiSegmentSegmenter::MultiSegmentSegmenter(const MuxerOptions& options)
|
MultiSegmentSegmenter::MultiSegmentSegmenter(const MuxerOptions& options)
|
||||||
: Segmenter(options), num_segment_(0) {}
|
: Segmenter(options), num_segment_(0) {}
|
||||||
|
|
||||||
MultiSegmentSegmenter::~MultiSegmentSegmenter() {}
|
MultiSegmentSegmenter::~MultiSegmentSegmenter() {}
|
||||||
|
|
||||||
|
Status MultiSegmentSegmenter::FinalizeSegment(uint64_t start_timescale,
|
||||||
|
uint64_t duration_timescale,
|
||||||
|
bool is_subsegment) {
|
||||||
|
CHECK(cluster());
|
||||||
|
Status status = Segmenter::FinalizeSegment(start_timescale,
|
||||||
|
duration_timescale, is_subsegment);
|
||||||
|
if (!status.ok())
|
||||||
|
return status;
|
||||||
|
if (!cluster()->Finalize())
|
||||||
|
return Status(error::FILE_FAILURE, "Error finalizing segment.");
|
||||||
|
if (!is_subsegment) {
|
||||||
|
if (muxer_listener()) {
|
||||||
|
const uint64_t size = cluster()->Size();
|
||||||
|
muxer_listener()->OnNewSegment(writer_->file()->file_name(),
|
||||||
|
start_timescale, duration_timescale, size);
|
||||||
|
}
|
||||||
|
VLOG(1) << "WEBM file '" << writer_->file()->file_name() << "' finalized.";
|
||||||
|
}
|
||||||
|
return Status::OK;
|
||||||
|
}
|
||||||
|
|
||||||
bool MultiSegmentSegmenter::GetInitRangeStartAndEnd(uint64_t* start,
|
bool MultiSegmentSegmenter::GetInitRangeStartAndEnd(uint64_t* start,
|
||||||
uint64_t* end) {
|
uint64_t* end) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -36,52 +58,22 @@ Status MultiSegmentSegmenter::DoInitialize(std::unique_ptr<MkvWriter> writer) {
|
||||||
}
|
}
|
||||||
|
|
||||||
Status MultiSegmentSegmenter::DoFinalize() {
|
Status MultiSegmentSegmenter::DoFinalize() {
|
||||||
Status status = FinalizeSegment();
|
return writer_->Close();
|
||||||
status.Update(writer_->Close());
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
Status MultiSegmentSegmenter::FinalizeSegment() {
|
|
||||||
if (!cluster()->Finalize())
|
|
||||||
return Status(error::FILE_FAILURE, "Error finalizing segment.");
|
|
||||||
|
|
||||||
if (muxer_listener()) {
|
|
||||||
const uint64_t size = cluster()->Size();
|
|
||||||
const uint64_t start_webm_timecode = cluster()->timecode();
|
|
||||||
const uint64_t start_timescale = FromWebMTimecode(start_webm_timecode);
|
|
||||||
muxer_listener()->OnNewSegment(writer_->file()->file_name(),
|
|
||||||
start_timescale,
|
|
||||||
cluster_length_in_time_scale(), size);
|
|
||||||
}
|
|
||||||
|
|
||||||
VLOG(1) << "WEBM file '" << writer_->file()->file_name() << "' finalized.";
|
|
||||||
return Status::OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
Status MultiSegmentSegmenter::NewSubsegment(uint64_t start_timescale) {
|
|
||||||
if (cluster() && !cluster()->Finalize())
|
|
||||||
return Status(error::FILE_FAILURE, "Error finalizing segment.");
|
|
||||||
|
|
||||||
uint64_t start_webm_timecode = FromBMFFTimescale(start_timescale);
|
|
||||||
return SetCluster(start_webm_timecode, 0, writer_.get());
|
|
||||||
}
|
|
||||||
|
|
||||||
Status MultiSegmentSegmenter::NewSegment(uint64_t start_timescale) {
|
|
||||||
if (cluster()) {
|
|
||||||
Status temp = FinalizeSegment();
|
|
||||||
if (!temp.ok())
|
|
||||||
return temp;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Status MultiSegmentSegmenter::NewSegment(uint64_t start_timescale,
|
||||||
|
bool is_subsegment) {
|
||||||
|
if (!is_subsegment) {
|
||||||
// Create a new file for the new segment.
|
// Create a new file for the new segment.
|
||||||
std::string segment_name =
|
std::string segment_name =
|
||||||
GetSegmentName(options().segment_template, start_timescale, num_segment_,
|
GetSegmentName(options().segment_template, start_timescale,
|
||||||
options().bandwidth);
|
num_segment_, options().bandwidth);
|
||||||
writer_.reset(new MkvWriter);
|
writer_.reset(new MkvWriter);
|
||||||
Status status = writer_->Open(segment_name);
|
Status status = writer_->Open(segment_name);
|
||||||
if (!status.ok())
|
if (!status.ok())
|
||||||
return status;
|
return status;
|
||||||
num_segment_++;
|
num_segment_++;
|
||||||
|
}
|
||||||
|
|
||||||
uint64_t start_webm_timecode = FromBMFFTimescale(start_timescale);
|
uint64_t start_webm_timecode = FromBMFFTimescale(start_timescale);
|
||||||
return SetCluster(start_webm_timecode, 0, writer_.get());
|
return SetCluster(start_webm_timecode, 0, writer_.get());
|
||||||
|
|
|
@ -28,6 +28,9 @@ class MultiSegmentSegmenter : public Segmenter {
|
||||||
|
|
||||||
/// @name Segmenter implementation overrides.
|
/// @name Segmenter implementation overrides.
|
||||||
/// @{
|
/// @{
|
||||||
|
Status FinalizeSegment(uint64_t start_timescale,
|
||||||
|
uint64_t duration_timescale,
|
||||||
|
bool is_subsegment) override;
|
||||||
bool GetInitRangeStartAndEnd(uint64_t* start, uint64_t* end) override;
|
bool GetInitRangeStartAndEnd(uint64_t* start, uint64_t* end) override;
|
||||||
bool GetIndexRangeStartAndEnd(uint64_t* start, uint64_t* end) override;
|
bool GetIndexRangeStartAndEnd(uint64_t* start, uint64_t* end) override;
|
||||||
/// @}
|
/// @}
|
||||||
|
@ -39,10 +42,7 @@ class MultiSegmentSegmenter : public Segmenter {
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Segmenter implementation overrides.
|
// Segmenter implementation overrides.
|
||||||
Status NewSubsegment(uint64_t start_timescale) override;
|
Status NewSegment(uint64_t start_timescale, bool is_subsegment) override;
|
||||||
Status NewSegment(uint64_t start_timescale) override;
|
|
||||||
|
|
||||||
Status FinalizeSegment();
|
|
||||||
|
|
||||||
std::unique_ptr<MkvWriter> writer_;
|
std::unique_ptr<MkvWriter> writer_;
|
||||||
uint32_t num_segment_;
|
uint32_t num_segment_;
|
||||||
|
|
|
@ -14,6 +14,7 @@ namespace media {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
const uint64_t kDuration = 1000;
|
const uint64_t kDuration = 1000;
|
||||||
|
const bool kSubsegment = true;
|
||||||
|
|
||||||
const uint8_t kBasicSupportDataInit[] = {
|
const uint8_t kBasicSupportDataInit[] = {
|
||||||
// ID: EBML Header omitted.
|
// ID: EBML Header omitted.
|
||||||
|
@ -124,6 +125,7 @@ TEST_F(MultiSegmentSegmenterTest, BasicSupport) {
|
||||||
CreateSample(kKeyFrame, kDuration, kNoSideData);
|
CreateSample(kKeyFrame, kDuration, kNoSideData);
|
||||||
ASSERT_OK(segmenter_->AddSample(sample));
|
ASSERT_OK(segmenter_->AddSample(sample));
|
||||||
}
|
}
|
||||||
|
ASSERT_OK(segmenter_->FinalizeSegment(0, 8 * kDuration, !kSubsegment));
|
||||||
ASSERT_OK(segmenter_->Finalize());
|
ASSERT_OK(segmenter_->Finalize());
|
||||||
|
|
||||||
// Verify the resulting data.
|
// Verify the resulting data.
|
||||||
|
@ -134,18 +136,21 @@ TEST_F(MultiSegmentSegmenterTest, BasicSupport) {
|
||||||
EXPECT_FALSE(File::Open(TemplateFileName(1).c_str(), "r"));
|
EXPECT_FALSE(File::Open(TemplateFileName(1).c_str(), "r"));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(MultiSegmentSegmenterTest, SplitsFilesOnSegmentDuration) {
|
TEST_F(MultiSegmentSegmenterTest, SplitsFilesOnSegment) {
|
||||||
MuxerOptions options = CreateMuxerOptions();
|
MuxerOptions options = CreateMuxerOptions();
|
||||||
options.segment_template = segment_template_;
|
options.segment_template = segment_template_;
|
||||||
options.segment_duration = 5; // seconds
|
|
||||||
ASSERT_NO_FATAL_FAILURE(InitializeSegmenter(options));
|
ASSERT_NO_FATAL_FAILURE(InitializeSegmenter(options));
|
||||||
|
|
||||||
// Write the samples to the Segmenter.
|
// Write the samples to the Segmenter.
|
||||||
for (int i = 0; i < 8; i++) {
|
for (int i = 0; i < 8; i++) {
|
||||||
|
if (i == 5)
|
||||||
|
ASSERT_OK(segmenter_->FinalizeSegment(0, 5 * kDuration, !kSubsegment));
|
||||||
std::shared_ptr<MediaSample> sample =
|
std::shared_ptr<MediaSample> sample =
|
||||||
CreateSample(kKeyFrame, kDuration, kNoSideData);
|
CreateSample(kKeyFrame, kDuration, kNoSideData);
|
||||||
ASSERT_OK(segmenter_->AddSample(sample));
|
ASSERT_OK(segmenter_->AddSample(sample));
|
||||||
}
|
}
|
||||||
|
ASSERT_OK(
|
||||||
|
segmenter_->FinalizeSegment(5 * kDuration, 8 * kDuration, !kSubsegment));
|
||||||
ASSERT_OK(segmenter_->Finalize());
|
ASSERT_OK(segmenter_->Finalize());
|
||||||
|
|
||||||
// Verify the resulting data.
|
// Verify the resulting data.
|
||||||
|
@ -161,47 +166,20 @@ TEST_F(MultiSegmentSegmenterTest, SplitsFilesOnSegmentDuration) {
|
||||||
EXPECT_FALSE(File::Open(TemplateFileName(2).c_str(), "r"));
|
EXPECT_FALSE(File::Open(TemplateFileName(2).c_str(), "r"));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(MultiSegmentSegmenterTest, RespectsSegmentSAPAlign) {
|
TEST_F(MultiSegmentSegmenterTest, SplitsClustersOnSubsegment) {
|
||||||
MuxerOptions options = CreateMuxerOptions();
|
MuxerOptions options = CreateMuxerOptions();
|
||||||
options.segment_template = segment_template_;
|
options.segment_template = segment_template_;
|
||||||
options.segment_duration = 3; // seconds
|
|
||||||
options.segment_sap_aligned = true;
|
|
||||||
ASSERT_NO_FATAL_FAILURE(InitializeSegmenter(options));
|
|
||||||
|
|
||||||
// Write the samples to the Segmenter.
|
|
||||||
for (int i = 0; i < 10; i++) {
|
|
||||||
const KeyFrameFlag key_frame_flag = i == 6 ? kKeyFrame : kNotKeyFrame;
|
|
||||||
std::shared_ptr<MediaSample> sample =
|
|
||||||
CreateSample(key_frame_flag, kDuration, kNoSideData);
|
|
||||||
ASSERT_OK(segmenter_->AddSample(sample));
|
|
||||||
}
|
|
||||||
ASSERT_OK(segmenter_->Finalize());
|
|
||||||
|
|
||||||
// Verify the resulting data.
|
|
||||||
ClusterParser parser;
|
|
||||||
ASSERT_NO_FATAL_FAILURE(parser.PopulateFromCluster(TemplateFileName(0)));
|
|
||||||
ASSERT_EQ(1u, parser.cluster_count());
|
|
||||||
EXPECT_EQ(6, parser.GetFrameCountForCluster(0));
|
|
||||||
|
|
||||||
ASSERT_NO_FATAL_FAILURE(parser.PopulateFromCluster(TemplateFileName(1)));
|
|
||||||
ASSERT_EQ(1u, parser.cluster_count());
|
|
||||||
EXPECT_EQ(4, parser.GetFrameCountForCluster(0));
|
|
||||||
|
|
||||||
EXPECT_FALSE(File::Open(TemplateFileName(2).c_str(), "r"));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(MultiSegmentSegmenterTest, SplitsClustersOnFragmentDuration) {
|
|
||||||
MuxerOptions options = CreateMuxerOptions();
|
|
||||||
options.segment_template = segment_template_;
|
|
||||||
options.fragment_duration = 5; // seconds
|
|
||||||
ASSERT_NO_FATAL_FAILURE(InitializeSegmenter(options));
|
ASSERT_NO_FATAL_FAILURE(InitializeSegmenter(options));
|
||||||
|
|
||||||
// Write the samples to the Segmenter.
|
// Write the samples to the Segmenter.
|
||||||
for (int i = 0; i < 8; i++) {
|
for (int i = 0; i < 8; i++) {
|
||||||
|
if (i == 5)
|
||||||
|
ASSERT_OK(segmenter_->FinalizeSegment(0, 5 * kDuration, kSubsegment));
|
||||||
std::shared_ptr<MediaSample> sample =
|
std::shared_ptr<MediaSample> sample =
|
||||||
CreateSample(kKeyFrame, kDuration, kNoSideData);
|
CreateSample(kKeyFrame, kDuration, kNoSideData);
|
||||||
ASSERT_OK(segmenter_->AddSample(sample));
|
ASSERT_OK(segmenter_->AddSample(sample));
|
||||||
}
|
}
|
||||||
|
ASSERT_OK(segmenter_->FinalizeSegment(0, 8 * kDuration, !kSubsegment));
|
||||||
ASSERT_OK(segmenter_->Finalize());
|
ASSERT_OK(segmenter_->Finalize());
|
||||||
|
|
||||||
// Verify the resulting data.
|
// Verify the resulting data.
|
||||||
|
@ -214,31 +192,5 @@ TEST_F(MultiSegmentSegmenterTest, SplitsClustersOnFragmentDuration) {
|
||||||
EXPECT_FALSE(File::Open(TemplateFileName(1).c_str(), "r"));
|
EXPECT_FALSE(File::Open(TemplateFileName(1).c_str(), "r"));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(MultiSegmentSegmenterTest, RespectsFragmentSAPAlign) {
|
|
||||||
MuxerOptions options = CreateMuxerOptions();
|
|
||||||
options.segment_template = segment_template_;
|
|
||||||
options.fragment_duration = 3; // seconds
|
|
||||||
options.fragment_sap_aligned = true;
|
|
||||||
ASSERT_NO_FATAL_FAILURE(InitializeSegmenter(options));
|
|
||||||
|
|
||||||
// Write the samples to the Segmenter.
|
|
||||||
for (int i = 0; i < 10; i++) {
|
|
||||||
const KeyFrameFlag key_frame_flag = i == 6 ? kKeyFrame : kNotKeyFrame;
|
|
||||||
std::shared_ptr<MediaSample> sample =
|
|
||||||
CreateSample(key_frame_flag, kDuration, kNoSideData);
|
|
||||||
ASSERT_OK(segmenter_->AddSample(sample));
|
|
||||||
}
|
|
||||||
ASSERT_OK(segmenter_->Finalize());
|
|
||||||
|
|
||||||
// Verify the resulting data.
|
|
||||||
ClusterParser parser;
|
|
||||||
ASSERT_NO_FATAL_FAILURE(parser.PopulateFromCluster(TemplateFileName(0)));
|
|
||||||
ASSERT_EQ(2u, parser.cluster_count());
|
|
||||||
EXPECT_EQ(6, parser.GetFrameCountForCluster(0));
|
|
||||||
EXPECT_EQ(4, parser.GetFrameCountForCluster(1));
|
|
||||||
|
|
||||||
EXPECT_FALSE(File::Open(TemplateFileName(1).c_str(), "r"));
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace media
|
} // namespace media
|
||||||
} // namespace shaka
|
} // namespace shaka
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
#include "packager/base/time/time.h"
|
#include "packager/base/time/time.h"
|
||||||
#include "packager/media/base/audio_stream_info.h"
|
#include "packager/media/base/audio_stream_info.h"
|
||||||
|
#include "packager/media/base/media_handler.h"
|
||||||
#include "packager/media/base/media_sample.h"
|
#include "packager/media/base/media_sample.h"
|
||||||
#include "packager/media/base/muxer_options.h"
|
#include "packager/media/base/muxer_options.h"
|
||||||
#include "packager/media/base/muxer_util.h"
|
#include "packager/media/base/muxer_util.h"
|
||||||
|
@ -28,22 +29,7 @@ int64_t kTimecodeScale = 1000000;
|
||||||
int64_t kSecondsToNs = 1000000000L;
|
int64_t kSecondsToNs = 1000000000L;
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
Segmenter::Segmenter(const MuxerOptions& options)
|
Segmenter::Segmenter(const MuxerOptions& options) : options_(options) {}
|
||||||
: reference_frame_timestamp_(0),
|
|
||||||
options_(options),
|
|
||||||
clear_lead_(0),
|
|
||||||
enable_encryption_(false),
|
|
||||||
info_(NULL),
|
|
||||||
muxer_listener_(NULL),
|
|
||||||
progress_listener_(NULL),
|
|
||||||
progress_target_(0),
|
|
||||||
accumulated_progress_(0),
|
|
||||||
first_timestamp_(0),
|
|
||||||
sample_duration_(0),
|
|
||||||
segment_payload_pos_(0),
|
|
||||||
cluster_length_in_time_scale_(0),
|
|
||||||
segment_length_in_time_scale_(0),
|
|
||||||
track_id_(0) {}
|
|
||||||
|
|
||||||
Segmenter::~Segmenter() {}
|
Segmenter::~Segmenter() {}
|
||||||
|
|
||||||
|
@ -110,10 +96,6 @@ Status Segmenter::Initialize(std::unique_ptr<MkvWriter> writer,
|
||||||
}
|
}
|
||||||
|
|
||||||
Status Segmenter::Finalize() {
|
Status Segmenter::Finalize() {
|
||||||
Status status = WriteFrame(true /* write_duration */);
|
|
||||||
if (!status.ok())
|
|
||||||
return status;
|
|
||||||
|
|
||||||
uint64_t duration =
|
uint64_t duration =
|
||||||
prev_sample_->pts() - first_timestamp_ + prev_sample_->duration();
|
prev_sample_->pts() - first_timestamp_ + prev_sample_->duration();
|
||||||
segment_info_.set_duration(FromBMFFTimescale(duration));
|
segment_info_.set_duration(FromBMFFTimescale(duration));
|
||||||
|
@ -137,33 +119,9 @@ Status Segmenter::AddSample(std::shared_ptr<MediaSample> sample) {
|
||||||
// previous frame first before creating the new Cluster.
|
// previous frame first before creating the new Cluster.
|
||||||
|
|
||||||
Status status;
|
Status status;
|
||||||
bool wrote_frame = false;
|
if (new_segment_ || new_subsegment_) {
|
||||||
bool new_segment = false;
|
status = NewSegment(sample->pts(), new_subsegment_);
|
||||||
if (!cluster_) {
|
} else {
|
||||||
status = NewSegment(sample->pts());
|
|
||||||
new_segment = true;
|
|
||||||
// First frame, so no previous frame to write.
|
|
||||||
wrote_frame = true;
|
|
||||||
} else if (segment_length_in_time_scale_ >=
|
|
||||||
options_.segment_duration * info_->time_scale()) {
|
|
||||||
if (sample->is_key_frame() || !options_.segment_sap_aligned) {
|
|
||||||
status = WriteFrame(true /* write_duration */);
|
|
||||||
status.Update(NewSegment(sample->pts()));
|
|
||||||
new_segment = true;
|
|
||||||
segment_length_in_time_scale_ = 0;
|
|
||||||
cluster_length_in_time_scale_ = 0;
|
|
||||||
wrote_frame = true;
|
|
||||||
}
|
|
||||||
} else if (cluster_length_in_time_scale_ >=
|
|
||||||
options_.fragment_duration * info_->time_scale()) {
|
|
||||||
if (sample->is_key_frame() || !options_.fragment_sap_aligned) {
|
|
||||||
status = WriteFrame(true /* write_duration */);
|
|
||||||
status.Update(NewSubsegment(sample->pts()));
|
|
||||||
cluster_length_in_time_scale_ = 0;
|
|
||||||
wrote_frame = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!wrote_frame) {
|
|
||||||
status = WriteFrame(false /* write_duration */);
|
status = WriteFrame(false /* write_duration */);
|
||||||
}
|
}
|
||||||
if (!status.ok())
|
if (!status.ok())
|
||||||
|
@ -173,7 +131,7 @@ Status Segmenter::AddSample(std::shared_ptr<MediaSample> sample) {
|
||||||
if (encryptor_) {
|
if (encryptor_) {
|
||||||
// Don't enable encryption in the middle of a segment, i.e. only at the
|
// Don't enable encryption in the middle of a segment, i.e. only at the
|
||||||
// first frame of a segment.
|
// first frame of a segment.
|
||||||
if (new_segment && !enable_encryption_) {
|
if (new_segment_ && !enable_encryption_) {
|
||||||
if (sample->pts() - first_timestamp_ >=
|
if (sample->pts() - first_timestamp_ >=
|
||||||
clear_lead_ * info_->time_scale()) {
|
clear_lead_ * info_->time_scale()) {
|
||||||
enable_encryption_ = true;
|
enable_encryption_ = true;
|
||||||
|
@ -189,16 +147,22 @@ Status Segmenter::AddSample(std::shared_ptr<MediaSample> sample) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the sample to the durations even though we have not written the frame
|
new_subsegment_ = false;
|
||||||
// yet. This is needed to make sure we split Clusters at the correct point.
|
new_segment_ = false;
|
||||||
// These are only used in this method.
|
|
||||||
cluster_length_in_time_scale_ += sample->duration();
|
|
||||||
segment_length_in_time_scale_ += sample->duration();
|
|
||||||
|
|
||||||
prev_sample_ = sample;
|
prev_sample_ = sample;
|
||||||
return Status::OK;
|
return Status::OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Status Segmenter::FinalizeSegment(uint64_t start_timescale,
|
||||||
|
uint64_t duration_timescale,
|
||||||
|
bool is_subsegment) {
|
||||||
|
if (is_subsegment)
|
||||||
|
new_subsegment_ = true;
|
||||||
|
else
|
||||||
|
new_segment_ = true;
|
||||||
|
return WriteFrame(true /* write duration */);
|
||||||
|
}
|
||||||
|
|
||||||
float Segmenter::GetDuration() const {
|
float Segmenter::GetDuration() const {
|
||||||
return static_cast<float>(segment_info_.duration()) *
|
return static_cast<float>(segment_info_.duration()) *
|
||||||
segment_info_.timecode_scale() / kSecondsToNs;
|
segment_info_.timecode_scale() / kSecondsToNs;
|
||||||
|
|
|
@ -76,6 +76,11 @@ class Segmenter {
|
||||||
/// @return OK on success, an error status otherwise.
|
/// @return OK on success, an error status otherwise.
|
||||||
Status AddSample(std::shared_ptr<MediaSample> sample);
|
Status AddSample(std::shared_ptr<MediaSample> sample);
|
||||||
|
|
||||||
|
/// Finalize the (sub)segment.
|
||||||
|
virtual Status FinalizeSegment(uint64_t start_timescale,
|
||||||
|
uint64_t duration_timescale,
|
||||||
|
bool is_subsegment) = 0;
|
||||||
|
|
||||||
/// @return true if there is an initialization range, while setting @a start
|
/// @return true if there is an initialization range, while setting @a start
|
||||||
/// and @a end; or false if initialization range does not apply.
|
/// and @a end; or false if initialization range does not apply.
|
||||||
virtual bool GetInitRangeStartAndEnd(uint64_t* start, uint64_t* end) = 0;
|
virtual bool GetInitRangeStartAndEnd(uint64_t* start, uint64_t* end) = 0;
|
||||||
|
@ -113,9 +118,6 @@ class Segmenter {
|
||||||
|
|
||||||
int track_id() const { return track_id_; }
|
int track_id() const { return track_id_; }
|
||||||
uint64_t segment_payload_pos() const { return segment_payload_pos_; }
|
uint64_t segment_payload_pos() const { return segment_payload_pos_; }
|
||||||
uint64_t cluster_length_in_time_scale() const {
|
|
||||||
return cluster_length_in_time_scale_;
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual Status DoInitialize(std::unique_ptr<MkvWriter> writer) = 0;
|
virtual Status DoInitialize(std::unique_ptr<MkvWriter> writer) = 0;
|
||||||
virtual Status DoFinalize() = 0;
|
virtual Status DoFinalize() = 0;
|
||||||
|
@ -129,25 +131,23 @@ class Segmenter {
|
||||||
// Writes the previous frame to the file.
|
// Writes the previous frame to the file.
|
||||||
Status WriteFrame(bool write_duration);
|
Status WriteFrame(bool write_duration);
|
||||||
|
|
||||||
// This is called when there needs to be a new subsegment. This does nothing
|
// This is called when there needs to be a new (sub)segment.
|
||||||
// in single-segment mode. In multi-segment mode this creates a new Cluster
|
// In single-segment mode, a Cluster is a segment and there is no subsegment.
|
||||||
// element.
|
// In multi-segment mode, a new file is a segment and the clusters in the file
|
||||||
virtual Status NewSubsegment(uint64_t start_timescale) = 0;
|
// are subsegments.
|
||||||
// This is called when there needs to be a new segment. In single-segment
|
virtual Status NewSegment(uint64_t start_timescale, bool is_subsegment) = 0;
|
||||||
// mode, this creates a new Cluster element. In multi-segment mode this
|
|
||||||
// creates a new output file.
|
|
||||||
virtual Status NewSegment(uint64_t start_timescale) = 0;
|
|
||||||
|
|
||||||
// Store the previous sample so we know which one is the last frame.
|
// Store the previous sample so we know which one is the last frame.
|
||||||
std::shared_ptr<MediaSample> prev_sample_;
|
std::shared_ptr<MediaSample> prev_sample_;
|
||||||
// The reference frame timestamp; used to populate the ReferenceBlock element
|
// The reference frame timestamp; used to populate the ReferenceBlock element
|
||||||
// when writing non-keyframe BlockGroups.
|
// when writing non-keyframe BlockGroups.
|
||||||
uint64_t reference_frame_timestamp_;
|
uint64_t reference_frame_timestamp_ = 0;
|
||||||
|
|
||||||
const MuxerOptions& options_;
|
const MuxerOptions& options_;
|
||||||
std::unique_ptr<Encryptor> encryptor_;
|
std::unique_ptr<Encryptor> encryptor_;
|
||||||
double clear_lead_;
|
double clear_lead_ = 0;
|
||||||
bool enable_encryption_; // Encryption is enabled only after clear_lead_.
|
// Encryption is enabled only after clear_lead_.
|
||||||
|
bool enable_encryption_ = false;
|
||||||
|
|
||||||
std::unique_ptr<mkvmuxer::Cluster> cluster_;
|
std::unique_ptr<mkvmuxer::Cluster> cluster_;
|
||||||
mkvmuxer::Cues cues_;
|
mkvmuxer::Cues cues_;
|
||||||
|
@ -155,22 +155,23 @@ class Segmenter {
|
||||||
mkvmuxer::SegmentInfo segment_info_;
|
mkvmuxer::SegmentInfo segment_info_;
|
||||||
mkvmuxer::Tracks tracks_;
|
mkvmuxer::Tracks tracks_;
|
||||||
|
|
||||||
StreamInfo* info_;
|
StreamInfo* info_ = nullptr;
|
||||||
MuxerListener* muxer_listener_;
|
MuxerListener* muxer_listener_ = nullptr;
|
||||||
ProgressListener* progress_listener_;
|
ProgressListener* progress_listener_ = nullptr;
|
||||||
uint64_t progress_target_;
|
uint64_t progress_target_ = 0;
|
||||||
uint64_t accumulated_progress_;
|
uint64_t accumulated_progress_ = 0;
|
||||||
uint64_t first_timestamp_;
|
uint64_t first_timestamp_ = 0;
|
||||||
int64_t sample_duration_;
|
int64_t sample_duration_ = 0;
|
||||||
// The position (in bytes) of the start of the Segment payload in the init
|
// The position (in bytes) of the start of the Segment payload in the init
|
||||||
// file. This is also the size of the header before the SeekHead.
|
// file. This is also the size of the header before the SeekHead.
|
||||||
uint64_t segment_payload_pos_;
|
uint64_t segment_payload_pos_ = 0;
|
||||||
|
|
||||||
// Durations in timescale.
|
// Indicate whether a new segment needed to be created, which is always true
|
||||||
uint64_t cluster_length_in_time_scale_;
|
// in the beginning.
|
||||||
uint64_t segment_length_in_time_scale_;
|
bool new_segment_ = true;
|
||||||
|
// Indicate whether a new subsegment needed to be created.
|
||||||
int track_id_;
|
bool new_subsegment_ = false;
|
||||||
|
int track_id_ = 0;
|
||||||
|
|
||||||
DISALLOW_COPY_AND_ASSIGN(Segmenter);
|
DISALLOW_COPY_AND_ASSIGN(Segmenter);
|
||||||
};
|
};
|
||||||
|
|
|
@ -75,10 +75,6 @@ std::shared_ptr<MediaSample> SegmentTestBase::CreateSample(
|
||||||
MuxerOptions SegmentTestBase::CreateMuxerOptions() const {
|
MuxerOptions SegmentTestBase::CreateMuxerOptions() const {
|
||||||
MuxerOptions ret;
|
MuxerOptions ret;
|
||||||
ret.output_file_name = output_file_name_;
|
ret.output_file_name = output_file_name_;
|
||||||
ret.segment_duration = 30; // seconds
|
|
||||||
ret.fragment_duration = 30; // seconds
|
|
||||||
ret.segment_sap_aligned = false;
|
|
||||||
ret.fragment_sap_aligned = false;
|
|
||||||
// Use memory files for temp storage. Normally this would be a bad idea
|
// Use memory files for temp storage. Normally this would be a bad idea
|
||||||
// since it wouldn't support large files, but for tests the files are small.
|
// since it wouldn't support large files, but for tests the files are small.
|
||||||
ret.temp_dir = std::string(kMemoryFilePrefix) + "temp/";
|
ret.temp_dir = std::string(kMemoryFilePrefix) + "temp/";
|
||||||
|
|
|
@ -18,55 +18,22 @@ SingleSegmentSegmenter::SingleSegmentSegmenter(const MuxerOptions& options)
|
||||||
|
|
||||||
SingleSegmentSegmenter::~SingleSegmentSegmenter() {}
|
SingleSegmentSegmenter::~SingleSegmentSegmenter() {}
|
||||||
|
|
||||||
Status SingleSegmentSegmenter::DoInitialize(std::unique_ptr<MkvWriter> writer) {
|
Status SingleSegmentSegmenter::FinalizeSegment(uint64_t start_timescale,
|
||||||
writer_ = std::move(writer);
|
uint64_t duration_timescale,
|
||||||
Status ret = WriteSegmentHeader(0, writer_.get());
|
bool is_subsegment) {
|
||||||
init_end_ = writer_->Position() - 1;
|
Status status = Segmenter::FinalizeSegment(start_timescale,
|
||||||
seek_head()->set_cluster_pos(init_end_ + 1 - segment_payload_pos());
|
duration_timescale, is_subsegment);
|
||||||
return ret;
|
if (!status.ok())
|
||||||
}
|
return status;
|
||||||
|
// No-op for subsegment in single segment mode.
|
||||||
Status SingleSegmentSegmenter::DoFinalize() {
|
if (is_subsegment)
|
||||||
|
return Status::OK;
|
||||||
|
CHECK(cluster());
|
||||||
if (!cluster()->Finalize())
|
if (!cluster()->Finalize())
|
||||||
return Status(error::FILE_FAILURE, "Error finalizing cluster.");
|
return Status(error::FILE_FAILURE, "Error finalizing cluster.");
|
||||||
|
|
||||||
// Write the Cues to the end of the file.
|
|
||||||
index_start_ = writer_->Position();
|
|
||||||
seek_head()->set_cues_pos(index_start_ - segment_payload_pos());
|
|
||||||
if (!cues()->Write(writer_.get()))
|
|
||||||
return Status(error::FILE_FAILURE, "Error writing Cues data.");
|
|
||||||
|
|
||||||
// The WebM index is at the end of the file.
|
|
||||||
index_end_ = writer_->Position() - 1;
|
|
||||||
writer_->Position(0);
|
|
||||||
|
|
||||||
Status status = WriteSegmentHeader(index_end_ + 1, writer_.get());
|
|
||||||
status.Update(writer_->Close());
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
Status SingleSegmentSegmenter::NewSubsegment(uint64_t start_timescale) {
|
|
||||||
return Status::OK;
|
return Status::OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
Status SingleSegmentSegmenter::NewSegment(uint64_t start_timescale) {
|
|
||||||
if (cluster() && !cluster()->Finalize())
|
|
||||||
return Status(error::FILE_FAILURE, "Error finalizing cluster.");
|
|
||||||
|
|
||||||
// Create a new Cue point.
|
|
||||||
uint64_t position = writer_->Position();
|
|
||||||
uint64_t start_webm_timecode = FromBMFFTimescale(start_timescale);
|
|
||||||
|
|
||||||
mkvmuxer::CuePoint* cue_point = new mkvmuxer::CuePoint;
|
|
||||||
cue_point->set_time(start_webm_timecode);
|
|
||||||
cue_point->set_track(track_id());
|
|
||||||
cue_point->set_cluster_pos(position - segment_payload_pos());
|
|
||||||
if (!cues()->AddCue(cue_point))
|
|
||||||
return Status(error::INTERNAL_ERROR, "Error adding CuePoint.");
|
|
||||||
|
|
||||||
return SetCluster(start_webm_timecode, position, writer_.get());
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SingleSegmentSegmenter::GetInitRangeStartAndEnd(uint64_t* start,
|
bool SingleSegmentSegmenter::GetInitRangeStartAndEnd(uint64_t* start,
|
||||||
uint64_t* end) {
|
uint64_t* end) {
|
||||||
// The init range is the header, from the start of the file to the size of
|
// The init range is the header, from the start of the file to the size of
|
||||||
|
@ -85,6 +52,49 @@ bool SingleSegmentSegmenter::GetIndexRangeStartAndEnd(uint64_t* start,
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Status SingleSegmentSegmenter::DoInitialize(std::unique_ptr<MkvWriter> writer) {
|
||||||
|
writer_ = std::move(writer);
|
||||||
|
Status ret = WriteSegmentHeader(0, writer_.get());
|
||||||
|
init_end_ = writer_->Position() - 1;
|
||||||
|
seek_head()->set_cluster_pos(init_end_ + 1 - segment_payload_pos());
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
Status SingleSegmentSegmenter::DoFinalize() {
|
||||||
|
// Write the Cues to the end of the file.
|
||||||
|
index_start_ = writer_->Position();
|
||||||
|
seek_head()->set_cues_pos(index_start_ - segment_payload_pos());
|
||||||
|
if (!cues()->Write(writer_.get()))
|
||||||
|
return Status(error::FILE_FAILURE, "Error writing Cues data.");
|
||||||
|
|
||||||
|
// The WebM index is at the end of the file.
|
||||||
|
index_end_ = writer_->Position() - 1;
|
||||||
|
writer_->Position(0);
|
||||||
|
|
||||||
|
Status status = WriteSegmentHeader(index_end_ + 1, writer_.get());
|
||||||
|
status.Update(writer_->Close());
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
Status SingleSegmentSegmenter::NewSegment(uint64_t start_timescale,
|
||||||
|
bool is_subsegment) {
|
||||||
|
// No-op for subsegment in single segment mode.
|
||||||
|
if (is_subsegment)
|
||||||
|
return Status::OK;
|
||||||
|
// Create a new Cue point.
|
||||||
|
uint64_t position = writer_->Position();
|
||||||
|
uint64_t start_webm_timecode = FromBMFFTimescale(start_timescale);
|
||||||
|
|
||||||
|
mkvmuxer::CuePoint* cue_point = new mkvmuxer::CuePoint;
|
||||||
|
cue_point->set_time(start_webm_timecode);
|
||||||
|
cue_point->set_track(track_id());
|
||||||
|
cue_point->set_cluster_pos(position - segment_payload_pos());
|
||||||
|
if (!cues()->AddCue(cue_point))
|
||||||
|
return Status(error::INTERNAL_ERROR, "Error adding CuePoint.");
|
||||||
|
|
||||||
|
return SetCluster(start_webm_timecode, position, writer_.get());
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace webm
|
} // namespace webm
|
||||||
} // namespace media
|
} // namespace media
|
||||||
} // namespace shaka
|
} // namespace shaka
|
||||||
|
|
|
@ -30,6 +30,9 @@ class SingleSegmentSegmenter : public Segmenter {
|
||||||
|
|
||||||
/// @name Segmenter implementation overrides.
|
/// @name Segmenter implementation overrides.
|
||||||
/// @{
|
/// @{
|
||||||
|
Status FinalizeSegment(uint64_t start_timescale,
|
||||||
|
uint64_t duration_timescale,
|
||||||
|
bool is_subsegment) override;
|
||||||
bool GetInitRangeStartAndEnd(uint64_t* start, uint64_t* end) override;
|
bool GetInitRangeStartAndEnd(uint64_t* start, uint64_t* end) override;
|
||||||
bool GetIndexRangeStartAndEnd(uint64_t* start, uint64_t* end) override;
|
bool GetIndexRangeStartAndEnd(uint64_t* start, uint64_t* end) override;
|
||||||
/// @}
|
/// @}
|
||||||
|
@ -50,8 +53,7 @@ class SingleSegmentSegmenter : public Segmenter {
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Segmenter implementation overrides.
|
// Segmenter implementation overrides.
|
||||||
Status NewSubsegment(uint64_t start_timescale) override;
|
Status NewSegment(uint64_t start_timescale, bool is_subsegment) override;
|
||||||
Status NewSegment(uint64_t start_timescale) override;
|
|
||||||
|
|
||||||
std::unique_ptr<MkvWriter> writer_;
|
std::unique_ptr<MkvWriter> writer_;
|
||||||
uint64_t init_end_;
|
uint64_t init_end_;
|
||||||
|
|
|
@ -13,6 +13,7 @@ namespace media {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
const uint64_t kDuration = 1000;
|
const uint64_t kDuration = 1000;
|
||||||
|
const bool kSubsegment = true;
|
||||||
|
|
||||||
const uint8_t kBasicSupportData[] = {
|
const uint8_t kBasicSupportData[] = {
|
||||||
// ID: EBML Header omitted.
|
// ID: EBML Header omitted.
|
||||||
|
@ -159,22 +160,26 @@ TEST_F(SingleSegmentSegmenterTest, BasicSupport) {
|
||||||
CreateSample(kKeyFrame, kDuration, side_data_flag);
|
CreateSample(kKeyFrame, kDuration, side_data_flag);
|
||||||
ASSERT_OK(segmenter_->AddSample(sample));
|
ASSERT_OK(segmenter_->AddSample(sample));
|
||||||
}
|
}
|
||||||
|
ASSERT_OK(segmenter_->FinalizeSegment(0, 5 * kDuration, !kSubsegment));
|
||||||
ASSERT_OK(segmenter_->Finalize());
|
ASSERT_OK(segmenter_->Finalize());
|
||||||
|
|
||||||
ASSERT_FILE_ENDS_WITH(OutputFileName().c_str(), kBasicSupportData);
|
ASSERT_FILE_ENDS_WITH(OutputFileName().c_str(), kBasicSupportData);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(SingleSegmentSegmenterTest, SplitsClustersOnSegmentDuration) {
|
TEST_F(SingleSegmentSegmenterTest, SplitsClustersOnSegment) {
|
||||||
MuxerOptions options = CreateMuxerOptions();
|
MuxerOptions options = CreateMuxerOptions();
|
||||||
options.segment_duration = 4.5; // seconds
|
|
||||||
ASSERT_NO_FATAL_FAILURE(InitializeSegmenter(options));
|
ASSERT_NO_FATAL_FAILURE(InitializeSegmenter(options));
|
||||||
|
|
||||||
// Write the samples to the Segmenter.
|
// Write the samples to the Segmenter.
|
||||||
for (int i = 0; i < 8; i++) {
|
for (int i = 0; i < 8; i++) {
|
||||||
|
if (i == 5)
|
||||||
|
ASSERT_OK(segmenter_->FinalizeSegment(0, 5 * kDuration, !kSubsegment));
|
||||||
std::shared_ptr<MediaSample> sample =
|
std::shared_ptr<MediaSample> sample =
|
||||||
CreateSample(kKeyFrame, kDuration, kNoSideData);
|
CreateSample(kKeyFrame, kDuration, kNoSideData);
|
||||||
ASSERT_OK(segmenter_->AddSample(sample));
|
ASSERT_OK(segmenter_->AddSample(sample));
|
||||||
}
|
}
|
||||||
|
ASSERT_OK(
|
||||||
|
segmenter_->FinalizeSegment(5 * kDuration, 8 * kDuration, !kSubsegment));
|
||||||
ASSERT_OK(segmenter_->Finalize());
|
ASSERT_OK(segmenter_->Finalize());
|
||||||
|
|
||||||
// Verify the resulting data.
|
// Verify the resulting data.
|
||||||
|
@ -185,17 +190,19 @@ TEST_F(SingleSegmentSegmenterTest, SplitsClustersOnSegmentDuration) {
|
||||||
EXPECT_EQ(3, parser.GetFrameCountForCluster(1));
|
EXPECT_EQ(3, parser.GetFrameCountForCluster(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(SingleSegmentSegmenterTest, IgnoresFragmentDuration) {
|
TEST_F(SingleSegmentSegmenterTest, IgnoresSubsegment) {
|
||||||
MuxerOptions options = CreateMuxerOptions();
|
MuxerOptions options = CreateMuxerOptions();
|
||||||
options.fragment_duration = 5; // seconds
|
|
||||||
ASSERT_NO_FATAL_FAILURE(InitializeSegmenter(options));
|
ASSERT_NO_FATAL_FAILURE(InitializeSegmenter(options));
|
||||||
|
|
||||||
// Write the samples to the Segmenter.
|
// Write the samples to the Segmenter.
|
||||||
for (int i = 0; i < 8; i++) {
|
for (int i = 0; i < 8; i++) {
|
||||||
|
if (i == 5)
|
||||||
|
ASSERT_OK(segmenter_->FinalizeSegment(0, 5 * kDuration, kSubsegment));
|
||||||
std::shared_ptr<MediaSample> sample =
|
std::shared_ptr<MediaSample> sample =
|
||||||
CreateSample(kKeyFrame, kDuration, kNoSideData);
|
CreateSample(kKeyFrame, kDuration, kNoSideData);
|
||||||
ASSERT_OK(segmenter_->AddSample(sample));
|
ASSERT_OK(segmenter_->AddSample(sample));
|
||||||
}
|
}
|
||||||
|
ASSERT_OK(segmenter_->FinalizeSegment(0, 8 * kDuration, !kSubsegment));
|
||||||
ASSERT_OK(segmenter_->Finalize());
|
ASSERT_OK(segmenter_->Finalize());
|
||||||
|
|
||||||
// Verify the resulting data.
|
// Verify the resulting data.
|
||||||
|
@ -205,31 +212,5 @@ TEST_F(SingleSegmentSegmenterTest, IgnoresFragmentDuration) {
|
||||||
EXPECT_EQ(8, parser.GetFrameCountForCluster(0));
|
EXPECT_EQ(8, parser.GetFrameCountForCluster(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(SingleSegmentSegmenterTest, RespectsSAPAlign) {
|
|
||||||
MuxerOptions options = CreateMuxerOptions();
|
|
||||||
options.segment_duration = 3; // seconds
|
|
||||||
options.segment_sap_aligned = true;
|
|
||||||
ASSERT_NO_FATAL_FAILURE(InitializeSegmenter(options));
|
|
||||||
|
|
||||||
// Write the samples to the Segmenter.
|
|
||||||
for (int i = 0; i < 10; i++) {
|
|
||||||
const KeyFrameFlag key_frame_flag = i == 6 ? kKeyFrame : kNotKeyFrame;
|
|
||||||
std::shared_ptr<MediaSample> sample =
|
|
||||||
CreateSample(key_frame_flag, kDuration, kNoSideData);
|
|
||||||
ASSERT_OK(segmenter_->AddSample(sample));
|
|
||||||
}
|
|
||||||
ASSERT_OK(segmenter_->Finalize());
|
|
||||||
|
|
||||||
// Verify the resulting data.
|
|
||||||
ClusterParser parser;
|
|
||||||
ASSERT_NO_FATAL_FAILURE(parser.PopulateFromSegment(OutputFileName()));
|
|
||||||
// Segments are 1 second, so there would normally be 3 frames per cluster,
|
|
||||||
// but since it's SAP aligned and only frame 7 is a key-frame, there are
|
|
||||||
// two clusters with 6 and 4 frames respectively.
|
|
||||||
ASSERT_EQ(2u, parser.cluster_count());
|
|
||||||
EXPECT_EQ(6, parser.GetFrameCountForCluster(0));
|
|
||||||
EXPECT_EQ(4, parser.GetFrameCountForCluster(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace media
|
} // namespace media
|
||||||
} // namespace shaka
|
} // namespace shaka
|
||||||
|
|
|
@ -87,9 +87,6 @@ Status TwoPassSingleSegmentSegmenter::DoInitialize(
|
||||||
}
|
}
|
||||||
|
|
||||||
Status TwoPassSingleSegmentSegmenter::DoFinalize() {
|
Status TwoPassSingleSegmentSegmenter::DoFinalize() {
|
||||||
if (!cluster()->Finalize())
|
|
||||||
return Status(error::FILE_FAILURE, "Error finalizing cluster.");
|
|
||||||
|
|
||||||
const uint64_t header_size = init_end() + 1;
|
const uint64_t header_size = init_end() + 1;
|
||||||
const uint64_t cues_pos = header_size - segment_payload_pos();
|
const uint64_t cues_pos = header_size - segment_payload_pos();
|
||||||
const uint64_t cues_size = UpdateCues(cues());
|
const uint64_t cues_size = UpdateCues(cues());
|
||||||
|
|
|
@ -72,11 +72,22 @@ Status WebMMuxer::Finalize() {
|
||||||
return Status::OK;
|
return Status::OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
Status WebMMuxer::DoAddSample(std::shared_ptr<MediaSample> sample) {
|
Status WebMMuxer::AddSample(int stream_id,
|
||||||
|
std::shared_ptr<MediaSample> sample) {
|
||||||
DCHECK(segmenter_);
|
DCHECK(segmenter_);
|
||||||
|
DCHECK_EQ(stream_id, 0);
|
||||||
return segmenter_->AddSample(sample);
|
return segmenter_->AddSample(sample);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Status WebMMuxer::FinalizeSegment(int stream_id,
|
||||||
|
std::shared_ptr<SegmentInfo> segment_info) {
|
||||||
|
DCHECK(segmenter_);
|
||||||
|
DCHECK_EQ(stream_id, 0);
|
||||||
|
return segmenter_->FinalizeSegment(segment_info->start_timestamp,
|
||||||
|
segment_info->duration,
|
||||||
|
segment_info->is_subsegment);
|
||||||
|
}
|
||||||
|
|
||||||
void WebMMuxer::FireOnMediaStartEvent() {
|
void WebMMuxer::FireOnMediaStartEvent() {
|
||||||
if (!muxer_listener())
|
if (!muxer_listener())
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -26,7 +26,9 @@ class WebMMuxer : public Muxer {
|
||||||
// Muxer implementation overrides.
|
// Muxer implementation overrides.
|
||||||
Status InitializeMuxer() override;
|
Status InitializeMuxer() override;
|
||||||
Status Finalize() override;
|
Status Finalize() override;
|
||||||
Status DoAddSample(std::shared_ptr<MediaSample> sample) override;
|
Status AddSample(int stream_id, std::shared_ptr<MediaSample> sample) override;
|
||||||
|
Status FinalizeSegment(int stream_id,
|
||||||
|
std::shared_ptr<SegmentInfo> segment_info) override;
|
||||||
|
|
||||||
void FireOnMediaStartEvent();
|
void FireOnMediaStartEvent();
|
||||||
void FireOnMediaEndEvent();
|
void FireOnMediaEndEvent();
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
#include "packager/media/base/muxer_util.h"
|
#include "packager/media/base/muxer_util.h"
|
||||||
#include "packager/media/base/stream_info.h"
|
#include "packager/media/base/stream_info.h"
|
||||||
#include "packager/media/base/test/status_test_util.h"
|
#include "packager/media/base/test/status_test_util.h"
|
||||||
|
#include "packager/media/chunking/chunking_handler.h"
|
||||||
#include "packager/media/formats/mp4/mp4_muxer.h"
|
#include "packager/media/formats/mp4/mp4_muxer.h"
|
||||||
#include "packager/media/test/test_data_util.h"
|
#include "packager/media/test/test_data_util.h"
|
||||||
|
|
||||||
|
@ -89,7 +90,9 @@ class PackagerTestBasic : public ::testing::TestWithParam<const char*> {
|
||||||
// Check if |file1| and |file2| are the same.
|
// Check if |file1| and |file2| are the same.
|
||||||
bool ContentsEqual(const std::string& file1, const std::string file2);
|
bool ContentsEqual(const std::string& file1, const std::string file2);
|
||||||
|
|
||||||
MuxerOptions SetupOptions(const std::string& output, bool single_segment);
|
ChunkingOptions SetupChunkingOptions();
|
||||||
|
MuxerOptions SetupMuxerOptions(const std::string& output,
|
||||||
|
bool single_segment);
|
||||||
void Remux(const std::string& input,
|
void Remux(const std::string& input,
|
||||||
const std::string& video_output,
|
const std::string& video_output,
|
||||||
const std::string& audio_output,
|
const std::string& audio_output,
|
||||||
|
@ -116,15 +119,10 @@ bool PackagerTestBasic::ContentsEqual(const std::string& file1,
|
||||||
test_directory_.AppendASCII(file2));
|
test_directory_.AppendASCII(file2));
|
||||||
}
|
}
|
||||||
|
|
||||||
MuxerOptions PackagerTestBasic::SetupOptions(const std::string& output,
|
MuxerOptions PackagerTestBasic::SetupMuxerOptions(const std::string& output,
|
||||||
bool single_segment) {
|
bool single_segment) {
|
||||||
MuxerOptions options;
|
MuxerOptions options;
|
||||||
options.segment_duration = kSegmentDurationInSeconds;
|
|
||||||
options.fragment_duration = kFragmentDurationInSecodns;
|
|
||||||
options.segment_sap_aligned = kSegmentSapAligned;
|
|
||||||
options.fragment_sap_aligned = kFragmentSapAligned;
|
|
||||||
options.num_subsegments_per_sidx = kNumSubsegmentsPerSidx;
|
options.num_subsegments_per_sidx = kNumSubsegmentsPerSidx;
|
||||||
|
|
||||||
options.output_file_name = GetFullPath(output);
|
options.output_file_name = GetFullPath(output);
|
||||||
if (!single_segment)
|
if (!single_segment)
|
||||||
options.segment_template = GetFullPath(kSegmentTemplate);
|
options.segment_template = GetFullPath(kSegmentTemplate);
|
||||||
|
@ -132,6 +130,15 @@ MuxerOptions PackagerTestBasic::SetupOptions(const std::string& output,
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ChunkingOptions PackagerTestBasic::SetupChunkingOptions() {
|
||||||
|
ChunkingOptions options;
|
||||||
|
options.segment_duration_in_seconds = kSegmentDurationInSeconds;
|
||||||
|
options.subsegment_duration_in_seconds = kFragmentDurationInSecodns;
|
||||||
|
options.segment_sap_aligned = kSegmentSapAligned;
|
||||||
|
options.subsegment_sap_aligned = kFragmentSapAligned;
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
void PackagerTestBasic::Remux(const std::string& input,
|
void PackagerTestBasic::Remux(const std::string& input,
|
||||||
const std::string& video_output,
|
const std::string& video_output,
|
||||||
const std::string& audio_output,
|
const std::string& audio_output,
|
||||||
|
@ -140,7 +147,6 @@ void PackagerTestBasic::Remux(const std::string& input,
|
||||||
CHECK(!video_output.empty() || !audio_output.empty());
|
CHECK(!video_output.empty() || !audio_output.empty());
|
||||||
|
|
||||||
Demuxer demuxer(GetFullPath(input));
|
Demuxer demuxer(GetFullPath(input));
|
||||||
|
|
||||||
std::unique_ptr<KeySource> encryption_key_source(
|
std::unique_ptr<KeySource> encryption_key_source(
|
||||||
FixedKeySource::CreateFromHexStrings(kKeyIdHex, kKeyHex, "", ""));
|
FixedKeySource::CreateFromHexStrings(kKeyIdHex, kKeyHex, "", ""));
|
||||||
DCHECK(encryption_key_source);
|
DCHECK(encryption_key_source);
|
||||||
|
@ -148,7 +154,7 @@ void PackagerTestBasic::Remux(const std::string& input,
|
||||||
std::shared_ptr<Muxer> muxer_video;
|
std::shared_ptr<Muxer> muxer_video;
|
||||||
if (!video_output.empty()) {
|
if (!video_output.empty()) {
|
||||||
muxer_video.reset(
|
muxer_video.reset(
|
||||||
new mp4::MP4Muxer(SetupOptions(video_output, single_segment)));
|
new mp4::MP4Muxer(SetupMuxerOptions(video_output, single_segment)));
|
||||||
muxer_video->set_clock(&fake_clock_);
|
muxer_video->set_clock(&fake_clock_);
|
||||||
|
|
||||||
if (enable_encryption) {
|
if (enable_encryption) {
|
||||||
|
@ -157,13 +163,17 @@ void PackagerTestBasic::Remux(const std::string& input,
|
||||||
kMaxUHD1Pixels, kClearLeadInSeconds,
|
kMaxUHD1Pixels, kClearLeadInSeconds,
|
||||||
kCryptoDurationInSeconds, FOURCC_cenc);
|
kCryptoDurationInSeconds, FOURCC_cenc);
|
||||||
}
|
}
|
||||||
ASSERT_OK(demuxer.SetHandler("video", muxer_video));
|
|
||||||
|
auto chunking_handler =
|
||||||
|
std::make_shared<ChunkingHandler>(SetupChunkingOptions());
|
||||||
|
ASSERT_OK(demuxer.SetHandler("video", chunking_handler));
|
||||||
|
ASSERT_OK(chunking_handler->SetHandler(0, muxer_video));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<Muxer> muxer_audio;
|
std::shared_ptr<Muxer> muxer_audio;
|
||||||
if (!audio_output.empty()) {
|
if (!audio_output.empty()) {
|
||||||
muxer_audio.reset(
|
muxer_audio.reset(
|
||||||
new mp4::MP4Muxer(SetupOptions(audio_output, single_segment)));
|
new mp4::MP4Muxer(SetupMuxerOptions(audio_output, single_segment)));
|
||||||
muxer_audio->set_clock(&fake_clock_);
|
muxer_audio->set_clock(&fake_clock_);
|
||||||
|
|
||||||
if (enable_encryption) {
|
if (enable_encryption) {
|
||||||
|
@ -172,7 +182,11 @@ void PackagerTestBasic::Remux(const std::string& input,
|
||||||
kMaxUHD1Pixels, kClearLeadInSeconds,
|
kMaxUHD1Pixels, kClearLeadInSeconds,
|
||||||
kCryptoDurationInSeconds, FOURCC_cenc);
|
kCryptoDurationInSeconds, FOURCC_cenc);
|
||||||
}
|
}
|
||||||
ASSERT_OK(demuxer.SetHandler("audio", muxer_audio));
|
|
||||||
|
auto chunking_handler =
|
||||||
|
std::make_shared<ChunkingHandler>(SetupChunkingOptions());
|
||||||
|
ASSERT_OK(demuxer.SetHandler("audio", chunking_handler));
|
||||||
|
ASSERT_OK(chunking_handler->SetHandler(0, muxer_audio));
|
||||||
}
|
}
|
||||||
|
|
||||||
ASSERT_OK(demuxer.Initialize());
|
ASSERT_OK(demuxer.Initialize());
|
||||||
|
@ -193,18 +207,20 @@ void PackagerTestBasic::Decrypt(const std::string& input,
|
||||||
|
|
||||||
std::shared_ptr<Muxer> muxer;
|
std::shared_ptr<Muxer> muxer;
|
||||||
if (!video_output.empty()) {
|
if (!video_output.empty()) {
|
||||||
muxer.reset(
|
muxer.reset(new mp4::MP4Muxer(SetupMuxerOptions(video_output, true)));
|
||||||
new mp4::MP4Muxer(SetupOptions(video_output, true)));
|
|
||||||
}
|
}
|
||||||
if (!audio_output.empty()) {
|
if (!audio_output.empty()) {
|
||||||
muxer.reset(
|
muxer.reset(new mp4::MP4Muxer(SetupMuxerOptions(audio_output, true)));
|
||||||
new mp4::MP4Muxer(SetupOptions(audio_output, true)));
|
|
||||||
}
|
}
|
||||||
ASSERT_TRUE(muxer);
|
ASSERT_TRUE(muxer);
|
||||||
muxer->set_clock(&fake_clock_);
|
muxer->set_clock(&fake_clock_);
|
||||||
ASSERT_OK(demuxer.SetHandler("0", muxer));
|
|
||||||
ASSERT_OK(demuxer.Initialize());
|
|
||||||
|
|
||||||
|
auto chunking_handler =
|
||||||
|
std::make_shared<ChunkingHandler>(SetupChunkingOptions());
|
||||||
|
ASSERT_OK(demuxer.SetHandler("0", chunking_handler));
|
||||||
|
ASSERT_OK(chunking_handler->SetHandler(0, muxer));
|
||||||
|
|
||||||
|
ASSERT_OK(demuxer.Initialize());
|
||||||
ASSERT_OK(demuxer.Run());
|
ASSERT_OK(demuxer.Run());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -42,6 +42,7 @@
|
||||||
'dependencies': [
|
'dependencies': [
|
||||||
'hls/hls.gyp:hls_builder',
|
'hls/hls.gyp:hls_builder',
|
||||||
'media/codecs/codecs.gyp:codecs',
|
'media/codecs/codecs.gyp:codecs',
|
||||||
|
'media/chunking/chunking.gyp:chunking',
|
||||||
'media/event/media_event.gyp:media_event',
|
'media/event/media_event.gyp:media_event',
|
||||||
'media/file/file.gyp:file',
|
'media/file/file.gyp:file',
|
||||||
'media/formats/mp2t/mp2t.gyp:mp2t',
|
'media/formats/mp2t/mp2t.gyp:mp2t',
|
||||||
|
@ -85,6 +86,7 @@
|
||||||
],
|
],
|
||||||
'dependencies': [
|
'dependencies': [
|
||||||
'media/codecs/codecs.gyp:codecs',
|
'media/codecs/codecs.gyp:codecs',
|
||||||
|
'media/chunking/chunking.gyp:chunking',
|
||||||
'media/file/file.gyp:file',
|
'media/file/file.gyp:file',
|
||||||
'media/formats/mp2t/mp2t.gyp:mp2t',
|
'media/formats/mp2t/mp2t.gyp:mp2t',
|
||||||
'media/formats/mp4/mp4.gyp:mp4',
|
'media/formats/mp4/mp4.gyp:mp4',
|
||||||
|
|
Loading…
Reference in New Issue