Implements Demuxer and Muxer media handlers

- Also sets up the packaging and verify it works.

Some of the changes are temporary to get the integration going.

Change-Id: I0cf6c379d185e157808acabb9ef58ff93d4a39ae
This commit is contained in:
KongQun Yang 2017-02-21 10:36:50 -08:00
parent 12c61d6792
commit 8f2cd6da91
32 changed files with 439 additions and 728 deletions

View File

@ -168,10 +168,6 @@ class RemuxJob : public base::SimpleThread {
~RemuxJob() override {} ~RemuxJob() override {}
void AddMuxer(std::unique_ptr<Muxer> mux) {
muxers_.push_back(std::move(mux));
}
Demuxer* demuxer() { return demuxer_.get(); } Demuxer* demuxer() { return demuxer_.get(); }
Status status() { return status_; } Status status() { return status_; }
@ -182,7 +178,6 @@ class RemuxJob : public base::SimpleThread {
} }
std::unique_ptr<Demuxer> demuxer_; std::unique_ptr<Demuxer> demuxer_;
std::vector<std::unique_ptr<Muxer>> muxers_;
Status status_; Status status_;
DISALLOW_COPY_AND_ASSIGN(RemuxJob); DISALLOW_COPY_AND_ASSIGN(RemuxJob);
@ -227,15 +222,15 @@ bool StreamInfoToTextMediaInfo(const StreamDescriptor& stream_descriptor,
return true; return true;
} }
std::unique_ptr<Muxer> CreateOutputMuxer(const MuxerOptions& options, std::shared_ptr<Muxer> CreateOutputMuxer(const MuxerOptions& options,
MediaContainerName container) { MediaContainerName container) {
if (container == CONTAINER_WEBM) { if (container == CONTAINER_WEBM) {
return std::unique_ptr<Muxer>(new webm::WebMMuxer(options)); return std::shared_ptr<Muxer>(new webm::WebMMuxer(options));
} else if (container == CONTAINER_MPEG2TS) { } else if (container == CONTAINER_MPEG2TS) {
return std::unique_ptr<Muxer>(new mp2t::TsMuxer(options)); return std::shared_ptr<Muxer>(new mp2t::TsMuxer(options));
} else { } else {
DCHECK_EQ(container, CONTAINER_MOV); DCHECK_EQ(container, CONTAINER_MOV);
return std::unique_ptr<Muxer>(new mp4::MP4Muxer(options)); return std::shared_ptr<Muxer>(new mp4::MP4Muxer(options));
} }
} }
@ -300,6 +295,7 @@ bool CreateRemuxJobs(const StreamDescriptorList& stream_descriptors,
if (stream_iter->input != previous_input) { if (stream_iter->input != previous_input) {
// New remux job needed. Create demux and job thread. // New remux job needed. Create demux and job thread.
std::unique_ptr<Demuxer> demuxer(new Demuxer(stream_iter->input)); std::unique_ptr<Demuxer> demuxer(new Demuxer(stream_iter->input));
demuxer->set_dump_stream_info(FLAGS_dump_stream_info);
if (FLAGS_enable_widevine_decryption || if (FLAGS_enable_widevine_decryption ||
FLAGS_enable_fixed_key_decryption) { FLAGS_enable_fixed_key_decryption) {
std::unique_ptr<KeySource> key_source(CreateDecryptionKeySource()); std::unique_ptr<KeySource> key_source(CreateDecryptionKeySource());
@ -307,23 +303,15 @@ bool CreateRemuxJobs(const StreamDescriptorList& stream_descriptors,
return false; return false;
demuxer->SetKeySource(std::move(key_source)); demuxer->SetKeySource(std::move(key_source));
} }
Status status = demuxer->Initialize();
if (!status.ok()) {
LOG(ERROR) << "Demuxer failed to initialize: " << status.ToString();
return false;
}
if (FLAGS_dump_stream_info) {
printf("\nFile \"%s\":\n", stream_iter->input.c_str());
DumpStreamInfo(demuxer->streams());
if (stream_iter->output.empty())
continue; // just need stream info.
}
remux_jobs->emplace_back(new RemuxJob(std::move(demuxer))); remux_jobs->emplace_back(new RemuxJob(std::move(demuxer)));
previous_input = stream_iter->input; previous_input = stream_iter->input;
// Skip setting up muxers if output is not needed.
if (stream_iter->output.empty())
continue;
} }
DCHECK(!remux_jobs->empty()); DCHECK(!remux_jobs->empty());
std::unique_ptr<Muxer> muxer( std::shared_ptr<Muxer> muxer(
CreateOutputMuxer(stream_muxer_options, stream_iter->output_format)); CreateOutputMuxer(stream_muxer_options, stream_iter->output_format));
if (FLAGS_use_fake_clock_for_muxer) muxer->set_clock(fake_clock); if (FLAGS_use_fake_clock_for_muxer) muxer->set_clock(fake_clock);
@ -373,15 +361,25 @@ bool CreateRemuxJobs(const StreamDescriptorList& stream_descriptors,
if (muxer_listener) if (muxer_listener)
muxer->SetMuxerListener(std::move(muxer_listener)); muxer->SetMuxerListener(std::move(muxer_listener));
if (!AddStreamToMuxer(remux_jobs->back()->demuxer()->streams(), auto* demuxer = remux_jobs->back()->demuxer();
stream_iter->stream_selector, const std::string& stream_selector = stream_iter->stream_selector;
stream_iter->language, Status status = demuxer->SetHandler(stream_selector, std::move(muxer));
muxer.get())) { if (!status.ok()) {
LOG(ERROR) << "Demuxer::SetHandler failed " << status;
return false; return false;
} }
remux_jobs->back()->AddMuxer(std::move(muxer)); if (!stream_iter->language.empty())
demuxer->SetLanguageOverride(stream_selector, stream_iter->language);
} }
// Initialize processing graph.
for (const std::unique_ptr<RemuxJob>& job : *remux_jobs) {
Status status = job->demuxer()->Initialize();
if (!status.ok()) {
LOG(ERROR) << "Failed to initialize processing graph " << status;
return false;
}
}
return true; return true;
} }

View File

@ -17,15 +17,12 @@
#include "packager/base/logging.h" #include "packager/base/logging.h"
#include "packager/base/strings/string_number_conversions.h" #include "packager/base/strings/string_number_conversions.h"
#include "packager/media/base/fixed_key_source.h" #include "packager/media/base/fixed_key_source.h"
#include "packager/media/base/media_stream.h"
#include "packager/media/base/muxer.h"
#include "packager/media/base/muxer_options.h" #include "packager/media/base/muxer_options.h"
#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/stream_info.h"
#include "packager/media/base/widevine_key_source.h" #include "packager/media/base/widevine_key_source.h"
#include "packager/media/file/file.h" #include "packager/media/file/file.h"
#include "packager/mpd/base/mpd_builder.h" #include "packager/mpd/base/mpd_options.h"
DEFINE_bool(mp4_use_decoding_timestamp_in_timeline, DEFINE_bool(mp4_use_decoding_timestamp_in_timeline,
false, false,
@ -38,12 +35,6 @@ DEFINE_bool(dump_stream_info, false, "Dump demuxed stream info.");
namespace shaka { namespace shaka {
namespace media { namespace media {
void DumpStreamInfo(const std::vector<std::unique_ptr<MediaStream>>& streams) {
printf("Found %zu stream(s).\n", streams.size());
for (size_t i = 0; i < streams.size(); ++i)
printf("Stream [%zu] %s\n", i, streams[i]->info()->ToString().c_str());
}
std::unique_ptr<RequestSigner> CreateSigner() { std::unique_ptr<RequestSigner> CreateSigner() {
std::unique_ptr<RequestSigner> signer; std::unique_ptr<RequestSigner> signer;
@ -196,63 +187,5 @@ bool GetMpdOptions(bool on_demand_profile, MpdOptions* mpd_options) {
return true; return true;
} }
MediaStream* FindFirstStreamOfType(
const std::vector<std::unique_ptr<MediaStream>>& streams,
StreamType stream_type) {
for (const std::unique_ptr<MediaStream>& stream : streams) {
if (stream->info()->stream_type() == stream_type)
return stream.get();
}
return nullptr;
}
MediaStream* FindFirstVideoStream(
const std::vector<std::unique_ptr<MediaStream>>& streams) {
return FindFirstStreamOfType(streams, kStreamVideo);
}
MediaStream* FindFirstAudioStream(
const std::vector<std::unique_ptr<MediaStream>>& streams) {
return FindFirstStreamOfType(streams, kStreamAudio);
}
bool AddStreamToMuxer(const std::vector<std::unique_ptr<MediaStream>>& streams,
const std::string& stream_selector,
const std::string& language_override,
Muxer* muxer) {
DCHECK(muxer);
MediaStream* stream = nullptr;
if (stream_selector == "video") {
stream = FindFirstVideoStream(streams);
} else if (stream_selector == "audio") {
stream = FindFirstAudioStream(streams);
} else {
// Expect stream_selector to be a zero based stream id.
size_t stream_id;
if (!base::StringToSizeT(stream_selector, &stream_id) ||
stream_id >= streams.size()) {
LOG(ERROR) << "Invalid argument --stream=" << stream_selector << "; "
<< "should be 'audio', 'video', or a number within [0, "
<< streams.size() - 1 << "].";
return false;
}
stream = streams[stream_id].get();
DCHECK(stream);
}
// This could occur only if stream_selector=audio|video and the corresponding
// stream does not exist in the input.
if (!stream) {
LOG(ERROR) << "No " << stream_selector << " stream found in the input.";
return false;
}
if (!language_override.empty()) {
stream->info()->set_language(language_override);
}
muxer->AddStream(stream);
return true;
}
} // namespace media } // namespace media
} // namespace shaka } // namespace shaka

View File

@ -6,13 +6,12 @@
// //
// Packager utility functions. // Packager utility functions.
#ifndef APP_PACKAGER_UTIL_H_ #ifndef PACKAGER_APP_PACKAGER_UTIL_H_
#define APP_PACKAGER_UTIL_H_ #define PACKAGER_APP_PACKAGER_UTIL_H_
#include <gflags/gflags.h> #include <gflags/gflags.h>
#include <memory> #include <memory>
#include <string>
#include <vector>
DECLARE_bool(dump_stream_info); DECLARE_bool(dump_stream_info);
@ -23,13 +22,8 @@ struct MpdOptions;
namespace media { namespace media {
class KeySource; class KeySource;
class MediaStream;
class Muxer;
struct MuxerOptions; struct MuxerOptions;
/// Print all the stream info for the provided strings to standard output.
void DumpStreamInfo(const std::vector<std::unique_ptr<MediaStream>>& streams);
/// Create KeySource based on provided command line options for content /// Create KeySource based on provided command line options for content
/// encryption. Also fetches keys. /// encryption. Also fetches keys.
/// @return A std::unique_ptr containing a new KeySource, or nullptr if /// @return A std::unique_ptr containing a new KeySource, or nullptr if
@ -48,21 +42,7 @@ bool GetMuxerOptions(MuxerOptions* muxer_options);
/// Fill MpdOptions members using provided command line options. /// Fill MpdOptions members using provided command line options.
bool GetMpdOptions(bool on_demand_profile, MpdOptions* mpd_options); bool GetMpdOptions(bool on_demand_profile, MpdOptions* mpd_options);
/// Select and add a stream from a provided set to a muxer.
/// @param streams contains the set of MediaStreams from which to select.
/// @param stream_selector is a string containing one of the following values:
/// "audio" to select the first audio track, "video" to select the first
/// video track, or a decimal number indicating which track number to
/// select (start at "1").
/// @param language_override is a string which, if non-empty, overrides the
/// stream's language metadata.
/// @return true if successful, false otherwise.
bool AddStreamToMuxer(const std::vector<std::unique_ptr<MediaStream>>& streams,
const std::string& stream_selector,
const std::string& language_override,
Muxer* muxer);
} // namespace media } // namespace media
} // namespace shaka } // namespace shaka
#endif // APP_PACKAGER_UTIL_H_ #endif // PACKAGER_APP_PACKAGER_UTIL_H_

View File

@ -92,6 +92,22 @@ class PackagerAppTest(unittest.TestCase):
self._DiffGold(self.output[1], 'bear-640x360-v-golden.mp4') self._DiffGold(self.output[1], 'bear-640x360-v-golden.mp4')
self._DiffGold(self.mpd_output, 'bear-640x360-av-golden.mpd') self._DiffGold(self.mpd_output, 'bear-640x360-av-golden.mpd')
def testPackageAudioVideoWithLanguageOverride(self):
self.packager.Package(
self._GetStreams(['audio', 'video'], language_override='por-BR'),
self._GetFlags())
self._DiffGold(self.output[0], 'bear-640x360-a-por-golden.mp4')
self._DiffGold(self.output[1], 'bear-640x360-v-golden.mp4')
self._DiffGold(self.mpd_output, 'bear-640x360-av-por-golden.mpd')
def testPackageAudioVideoWithLanguageOverrideWithSubtag(self):
self.packager.Package(
self._GetStreams(['audio', 'video'], language_override='por-BR'),
self._GetFlags())
self._DiffGold(self.output[0], 'bear-640x360-a-por-BR-golden.mp4')
self._DiffGold(self.output[1], 'bear-640x360-v-golden.mp4')
self._DiffGold(self.mpd_output, 'bear-640x360-av-por-BR-golden.mpd')
# Package all video, audio, and text. # Package all video, audio, and text.
def testPackageVideoAudioText(self): def testPackageVideoAudioText(self):
audio_video_streams = self._GetStreams(['audio', 'video']) audio_video_streams = self._GetStreams(['audio', 'video'])
@ -438,6 +454,7 @@ class PackagerAppTest(unittest.TestCase):
def _GetStreams(self, def _GetStreams(self,
stream_descriptors, stream_descriptors,
language_override=None,
output_format=None, output_format=None,
live=False, live=False,
test_files=None): test_files=None):
@ -466,9 +483,6 @@ class PackagerAppTest(unittest.TestCase):
'input=%s,stream=%s,init_segment=%s-init.mp4,' 'input=%s,stream=%s,init_segment=%s-init.mp4,'
'segment_template=%s-$Number$.m4s' % 'segment_template=%s-$Number$.m4s' %
(test_file, stream_descriptor, output_prefix, output_prefix)) (test_file, stream_descriptor, output_prefix, output_prefix))
if output_format:
stream += ',format=%s' % output_format
streams.append(stream)
self.output.append(output_prefix) self.output.append(output_prefix)
else: else:
output = '%s.%s' % ( output = '%s.%s' % (
@ -476,10 +490,12 @@ class PackagerAppTest(unittest.TestCase):
self._GetExtension(stream_descriptor, output_format)) self._GetExtension(stream_descriptor, output_format))
stream = ('input=%s,stream=%s,output=%s' % stream = ('input=%s,stream=%s,output=%s' %
(test_file, stream_descriptor, output)) (test_file, stream_descriptor, output))
self.output.append(output)
if output_format: if output_format:
stream += ',format=%s' % output_format stream += ',format=%s' % output_format
if language_override:
stream += ',lang=%s' % language_override
streams.append(stream) streams.append(stream)
self.output.append(output)
return streams return streams
def _GetExtension(self, stream_descriptor, output_format): def _GetExtension(self, stream_descriptor, output_format):

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--Generated with https://github.com/google/shaka-packager version <tag>-<hash>-<test>-->
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xlink="http://www.w3.org/1999/xlink" xsi:schemaLocation="urn:mpeg:dash:schema:mpd:2011 DASH-MPD.xsd" xmlns:cenc="urn:mpeg:cenc:2013" profiles="urn:mpeg:dash:profile:isoff-on-demand:2011" minBufferTime="PT2S" type="static" mediaPresentationDuration="PT2.76317S">
<Period id="0">
<AdaptationSet id="0" contentType="video" width="640" height="360" frameRate="30000/1001" subsegmentAlignment="true" par="16:9">
<Representation id="0" bandwidth="882040" codecs="avc1.64001e" mimeType="video/mp4" sar="1:1">
<BaseURL>output_video.mp4</BaseURL>
<SegmentBase indexRange="815-882" timescale="30000">
<Initialization range="0-814"/>
</SegmentBase>
</Representation>
</AdaptationSet>
<AdaptationSet id="1" contentType="audio" lang="pt-BR" subsegmentAlignment="true">
<Representation id="1" bandwidth="126487" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
<BaseURL>output_audio.mp4</BaseURL>
<SegmentBase indexRange="749-816" timescale="44100">
<Initialization range="0-748"/>
</SegmentBase>
</Representation>
</AdaptationSet>
</Period>
</MPD>

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--Generated with https://github.com/google/shaka-packager version <tag>-<hash>-<test>-->
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xlink="http://www.w3.org/1999/xlink" xsi:schemaLocation="urn:mpeg:dash:schema:mpd:2011 DASH-MPD.xsd" xmlns:cenc="urn:mpeg:cenc:2013" profiles="urn:mpeg:dash:profile:isoff-on-demand:2011" minBufferTime="PT2S" type="static" mediaPresentationDuration="PT2.76317S">
<Period id="0">
<AdaptationSet id="0" contentType="video" width="640" height="360" frameRate="30000/1001" subsegmentAlignment="true" par="16:9">
<Representation id="0" bandwidth="882040" codecs="avc1.64001e" mimeType="video/mp4" sar="1:1">
<BaseURL>output_video.mp4</BaseURL>
<SegmentBase indexRange="815-882" timescale="30000">
<Initialization range="0-814"/>
</SegmentBase>
</Representation>
</AdaptationSet>
<AdaptationSet id="1" contentType="audio" lang="pt-BR" subsegmentAlignment="true">
<Representation id="1" bandwidth="126487" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
<BaseURL>output_audio.mp4</BaseURL>
<SegmentBase indexRange="749-816" timescale="44100">
<Initialization range="0-748"/>
</SegmentBase>
</Representation>
</AdaptationSet>
</Period>
</MPD>

View File

@ -6,12 +6,14 @@
#include "packager/media/base/demuxer.h" #include "packager/media/base/demuxer.h"
#include <algorithm>
#include "packager/base/bind.h" #include "packager/base/bind.h"
#include "packager/base/logging.h" #include "packager/base/logging.h"
#include "packager/base/strings/string_number_conversions.h"
#include "packager/media/base/decryptor_source.h" #include "packager/media/base/decryptor_source.h"
#include "packager/media/base/key_source.h" #include "packager/media/base/key_source.h"
#include "packager/media/base/media_sample.h" #include "packager/media/base/media_sample.h"
#include "packager/media/base/media_stream.h"
#include "packager/media/base/stream_info.h" #include "packager/media/base/stream_info.h"
#include "packager/media/file/file.h" #include "packager/media/file/file.h"
#include "packager/media/formats/mp2t/mp2t_media_parser.h" #include "packager/media/formats/mp2t/mp2t_media_parser.h"
@ -28,19 +30,45 @@ const size_t kBufSize = 0x200000; // 2MB
// samples before seeing init_event, something is not right. The number // samples before seeing init_event, something is not right. The number
// set here is arbitrary though. // set here is arbitrary though.
const size_t kQueuedSamplesLimit = 10000; const size_t kQueuedSamplesLimit = 10000;
const int kInvalidStreamIndex = -1;
const int kBaseVideoOutputStreamIndex = 0x100;
const int kBaseAudioOutputStreamIndex = 0x200;
std::string GetStreamLabel(int stream_index) {
switch (stream_index) {
case kBaseVideoOutputStreamIndex:
return "video";
case kBaseAudioOutputStreamIndex:
return "audio";
default:
return base::IntToString(stream_index);
}
}
bool GetStreamIndex(const std::string& stream_label, int* stream_index) {
DCHECK(stream_index);
if (stream_label == "video") {
*stream_index = kBaseVideoOutputStreamIndex;
} else if (stream_label == "audio") {
*stream_index = kBaseAudioOutputStreamIndex;
} else {
// Expect stream_label to be a zero based stream id.
if (!base::StringToInt(stream_label, stream_index)) {
LOG(ERROR) << "Invalid argument --stream=" << stream_label << "; "
<< "should be 'audio', 'video', or a number";
return false;
}
}
return true;
}
} }
namespace shaka { namespace shaka {
namespace media { namespace media {
Demuxer::Demuxer(const std::string& file_name) Demuxer::Demuxer(const std::string& file_name)
: file_name_(file_name), : file_name_(file_name), buffer_(new uint8_t[kBufSize]) {}
media_file_(NULL),
init_event_received_(false),
container_name_(CONTAINER_UNKNOWN),
buffer_(new uint8_t[kBufSize]),
cancelled_(false) {
}
Demuxer::~Demuxer() { Demuxer::~Demuxer() {
if (media_file_) if (media_file_)
@ -51,9 +79,74 @@ void Demuxer::SetKeySource(std::unique_ptr<KeySource> key_source) {
key_source_ = std::move(key_source); key_source_ = std::move(key_source);
} }
Status Demuxer::Initialize() { Status Demuxer::Run() {
LOG(INFO) << "Demuxer::Run() on file '" << file_name_ << "'.";
Status status = InitializeParser();
// ParserInitEvent callback is called after a few calls to Parse(), which sets
// up the streams. Only after that, we can verify the outputs below.
while (!all_streams_ready_ && status.ok())
status.Update(Parse());
// If no output is defined, then return success after receiving all stream
// info.
if (all_streams_ready_ && output_handlers().empty())
return Status::OK;
// Check if all specified outputs exists.
for (const auto& pair : output_handlers()) {
if (std::find(stream_indexes_.begin(), stream_indexes_.end(), pair.first) ==
stream_indexes_.end()) {
LOG(ERROR) << "Invalid argument, stream=" << GetStreamLabel(pair.first)
<< " not available.";
return Status(error::INVALID_ARGUMENT, "Stream not available");
}
}
while (!cancelled_ && status.ok())
status.Update(Parse());
if (cancelled_ && status.ok())
return Status(error::CANCELLED, "Demuxer run cancelled");
if (status.error_code() == error::END_OF_STREAM) {
for (int stream_index : stream_indexes_) {
status = FlushStream(stream_index);
if (!status.ok())
return status;
}
return Status::OK;
}
return status;
}
void Demuxer::Cancel() {
cancelled_ = true;
}
Status Demuxer::SetHandler(const std::string& stream_label,
std::shared_ptr<MediaHandler> handler) {
int stream_index = kInvalidStreamIndex;
if (!GetStreamIndex(stream_label, &stream_index)) {
return Status(error::INVALID_ARGUMENT,
"Invalid stream: " + stream_label);
}
return MediaHandler::SetHandler(stream_index, std::move(handler));
}
void Demuxer::SetLanguageOverride(const std::string& stream_label,
const std::string& language_override) {
int stream_index = kInvalidStreamIndex;
if (!GetStreamIndex(stream_label, &stream_index))
LOG(WARNING) << "Invalid stream for language override " << stream_label;
language_overrides_[stream_index] = language_override;
}
Demuxer::QueuedSample::QueuedSample(uint32_t local_track_id,
std::shared_ptr<MediaSample> local_sample)
: track_id(local_track_id), sample(local_sample) {}
Demuxer::QueuedSample::~QueuedSample() {}
Status Demuxer::InitializeParser() {
DCHECK(!media_file_); DCHECK(!media_file_);
DCHECK(!init_event_received_); DCHECK(!all_streams_ready_);
LOG(INFO) << "Initialize Demuxer for file '" << file_name_ << "'."; LOG(INFO) << "Initialize Demuxer for file '" << file_name_ << "'.";
@ -105,34 +198,65 @@ Status Demuxer::Initialize() {
// Handle trailing 'moov'. // Handle trailing 'moov'.
if (container_name_ == CONTAINER_MOV) if (container_name_ == CONTAINER_MOV)
static_cast<mp4::MP4MediaParser*>(parser_.get())->LoadMoov(file_name_); static_cast<mp4::MP4MediaParser*>(parser_.get())->LoadMoov(file_name_);
if (!parser_->Parse(buffer_.get(), bytes_read)) { if (!parser_->Parse(buffer_.get(), bytes_read)) {
init_parsing_status_ = return Status(error::PARSER_FAILURE,
Status(error::PARSER_FAILURE, "Cannot parse media file " + file_name_); "Cannot parse media file " + file_name_);
} }
return Status::OK;
// Parse until init event received or on error.
while (!init_event_received_ && init_parsing_status_.ok())
init_parsing_status_ = Parse();
// Defer error reporting if init completed successfully.
return init_event_received_ ? Status::OK : init_parsing_status_;
} }
void Demuxer::ParserInitEvent( void Demuxer::ParserInitEvent(
const std::vector<std::shared_ptr<StreamInfo>>& stream_infos) { const std::vector<std::shared_ptr<StreamInfo>>& stream_infos) {
init_event_received_ = true; if (dump_stream_info_) {
for (const std::shared_ptr<StreamInfo>& stream_info : stream_infos) printf("\nFile \"%s\":\n", file_name_.c_str());
streams_.emplace_back(new MediaStream(stream_info, this)); printf("Found %zu stream(s).\n", stream_infos.size());
for (size_t i = 0; i < stream_infos.size(); ++i)
printf("Stream [%zu] %s\n", i, stream_infos[i]->ToString().c_str());
} }
Demuxer::QueuedSample::QueuedSample(uint32_t local_track_id, int base_stream_index = 0;
std::shared_ptr<MediaSample> local_sample) bool video_handler_set =
: track_id(local_track_id), sample(local_sample) {} output_handlers().find(kBaseVideoOutputStreamIndex) !=
Demuxer::QueuedSample::~QueuedSample() {} output_handlers().end();
bool audio_handler_set =
output_handlers().find(kBaseAudioOutputStreamIndex) !=
output_handlers().end();
for (const std::shared_ptr<StreamInfo>& stream_info : stream_infos) {
int stream_index = base_stream_index;
if (video_handler_set && stream_info->stream_type() == kStreamVideo) {
stream_index = kBaseVideoOutputStreamIndex;
// Only for the first video stream.
video_handler_set = false;
}
if (audio_handler_set && stream_info->stream_type() == kStreamAudio) {
stream_index = kBaseAudioOutputStreamIndex;
// Only for the first audio stream.
audio_handler_set = false;
}
const bool handler_set =
output_handlers().find(stream_index) != output_handlers().end();
if (handler_set) {
track_id_to_stream_index_map_[stream_info->track_id()] = stream_index;
stream_indexes_.push_back(stream_index);
auto iter = language_overrides_.find(stream_index);
if (iter != language_overrides_.end() &&
stream_info->stream_type() != kStreamVideo) {
stream_info->set_language(iter->second);
}
DispatchStreamInfo(stream_index, stream_info);
} else {
track_id_to_stream_index_map_[stream_info->track_id()] =
kInvalidStreamIndex;
}
++base_stream_index;
}
all_streams_ready_ = true;
}
bool Demuxer::NewSampleEvent(uint32_t track_id, bool Demuxer::NewSampleEvent(uint32_t track_id,
const std::shared_ptr<MediaSample>& sample) { const std::shared_ptr<MediaSample>& sample) {
if (!init_event_received_) { if (!all_streams_ready_) {
if (queued_samples_.size() >= kQueuedSamplesLimit) { if (queued_samples_.size() >= kQueuedSamplesLimit) {
LOG(ERROR) << "Queued samples limit reached: " << kQueuedSamplesLimit; LOG(ERROR) << "Queued samples limit reached: " << kQueuedSamplesLimit;
return false; return false;
@ -152,46 +276,19 @@ bool Demuxer::NewSampleEvent(uint32_t track_id,
bool Demuxer::PushSample(uint32_t track_id, bool Demuxer::PushSample(uint32_t track_id,
const std::shared_ptr<MediaSample>& sample) { const std::shared_ptr<MediaSample>& sample) {
for (const std::unique_ptr<MediaStream>& stream : streams_) { auto stream_index_iter = track_id_to_stream_index_map_.find(track_id);
if (track_id == stream->info()->track_id()) { if (stream_index_iter == track_id_to_stream_index_map_.end()) {
Status status = stream->PushSample(sample);
if (!status.ok())
LOG(ERROR) << "Demuxer::PushSample failed with " << status;
return status.ok();
}
}
LOG(ERROR) << "Track " << track_id << " not found."; LOG(ERROR) << "Track " << track_id << " not found.";
return false; return false;
} }
if (stream_index_iter->second == kInvalidStreamIndex)
Status Demuxer::Run() { return true;
Status status; Status status = DispatchMediaSample(stream_index_iter->second, sample);
if (!status.ok()) {
LOG(INFO) << "Demuxer::Run() on file '" << file_name_ << "'."; LOG(ERROR) << "Failed to process sample " << stream_index_iter->second
<< " " << status;
// Start the streams.
for (const std::unique_ptr<MediaStream>& stream : streams_) {
status = stream->Start(MediaStream::kPush);
if (!status.ok())
return status;
} }
return status.ok();
while (!cancelled_ && (status = Parse()).ok())
continue;
if (cancelled_ && status.ok())
return Status(error::CANCELLED, "Demuxer run cancelled");
if (status.error_code() == error::END_OF_STREAM) {
// Push EOS sample to muxer to indicate end of stream.
const std::shared_ptr<MediaSample>& sample = MediaSample::CreateEOSBuffer();
for (const std::unique_ptr<MediaStream>& stream : streams_) {
status = stream->PushSample(sample);
if (!status.ok())
return status;
}
}
return status;
} }
Status Demuxer::Parse() { Status Demuxer::Parse() {
@ -199,11 +296,6 @@ Status Demuxer::Parse() {
DCHECK(parser_); DCHECK(parser_);
DCHECK(buffer_); DCHECK(buffer_);
// Return early and avoid call Parse(...) again if it has already failed at
// the initialization.
if (!init_parsing_status_.ok())
return init_parsing_status_;
int64_t bytes_read = media_file_->Read(buffer_.get(), kBufSize); int64_t bytes_read = media_file_->Read(buffer_.get(), kBufSize);
if (bytes_read == 0) { if (bytes_read == 0) {
if (!parser_->Flush()) if (!parser_->Flush())
@ -219,9 +311,5 @@ Status Demuxer::Parse() {
"Cannot parse media file " + file_name_); "Cannot parse media file " + file_name_);
} }
void Demuxer::Cancel() {
cancelled_ = true;
}
} // namespace media } // namespace media
} // namespace shaka } // namespace shaka

View File

@ -13,6 +13,7 @@
#include "packager/base/compiler_specific.h" #include "packager/base/compiler_specific.h"
#include "packager/media/base/container_names.h" #include "packager/media/base/container_names.h"
#include "packager/media/base/media_handler.h"
#include "packager/media/base/status.h" #include "packager/media/base/status.h"
namespace shaka { namespace shaka {
@ -28,7 +29,7 @@ class StreamInfo;
/// Demuxer is responsible for extracting elementary stream samples from a /// Demuxer is responsible for extracting elementary stream samples from a
/// media file, e.g. an ISO BMFF file. /// media file, e.g. an ISO BMFF file.
class Demuxer { class Demuxer : public MediaHandler {
public: public:
/// @param file_name specifies the input source. It uses prefix matching to /// @param file_name specifies the input source. It uses prefix matching to
/// create a proper File object. The user can extend File to support /// create a proper File object. The user can extend File to support
@ -42,36 +43,52 @@ class Demuxer {
/// demuxed. /// demuxed.
void SetKeySource(std::unique_ptr<KeySource> key_source); void SetKeySource(std::unique_ptr<KeySource> key_source);
/// Initialize the Demuxer. Calling other public methods of this class
/// without this method returning OK, results in an undefined behavior.
/// This method primes the demuxer by parsing portions of the media file to
/// extract stream information.
/// @return OK on success.
Status Initialize();
/// Drive the remuxing from demuxer side (push). Read the file and push /// Drive the remuxing from demuxer side (push). Read the file and push
/// the Data to Muxer until Eof. /// the Data to Muxer until Eof.
Status Run(); Status Run();
/// Read from the source and send it to the parser.
Status Parse();
/// Cancel a demuxing job in progress. Will cause @a Run to exit with an error /// Cancel a demuxing job in progress. Will cause @a Run to exit with an error
/// status of type CANCELLED. /// status of type CANCELLED.
void Cancel(); void Cancel();
/// @return Streams in the media container being demuxed. The caller cannot
/// add or remove streams from the returned vector, but the caller is
/// allowed to change the internal state of the streams in the vector
/// through MediaStream APIs.
const std::vector<std::unique_ptr<MediaStream>>& streams() {
return streams_;
}
/// @return Container name (type). Value is CONTAINER_UNKNOWN if the demuxer /// @return Container name (type). Value is CONTAINER_UNKNOWN if the demuxer
/// is not initialized. /// is not initialized.
MediaContainerName container_name() { return container_name_; } MediaContainerName container_name() { return container_name_; }
/// Set the handler for the specified stream.
/// @param stream_label can be 'audio', 'video', or stream number (zero
/// based).
/// @param handler is the handler for the specified stream.
Status SetHandler(const std::string& stream_label,
std::shared_ptr<MediaHandler> handler);
/// Override the language in the specified stream. If the specified stream is
/// a video stream or invalid, this function is a no-op.
/// @param stream_label can be 'audio', 'video', or stream number (zero
/// based).
/// @param language_override is the new language.
void SetLanguageOverride(const std::string& stream_label,
const std::string& language_override);
void set_dump_stream_info(bool dump_stream_info) {
dump_stream_info_ = dump_stream_info;
}
protected:
/// @name MediaHandler implementation overrides.
/// @{
Status InitializeInternal() override { return Status::OK; }
Status Process(std::unique_ptr<StreamData> stream_data) override {
return Status(error::INTERNAL_ERROR,
"Demuxer should not be the downstream handler.");
}
bool ValidateOutputStreamIndex(int stream_index) const override {
// We don't know if the stream is valid or not when setting up the graph.
// Will validate the stream index later when stream info is available.
return true;
}
/// @}
private: private:
Demuxer(const Demuxer&) = delete; Demuxer(const Demuxer&) = delete;
Demuxer& operator=(const Demuxer&) = delete; Demuxer& operator=(const Demuxer&) = delete;
@ -84,6 +101,11 @@ class Demuxer {
std::shared_ptr<MediaSample> sample; std::shared_ptr<MediaSample> sample;
}; };
// Initialize the parser. This method primes the demuxer by parsing portions
// of the media file to extract stream information.
// @return OK on success.
Status InitializeParser();
// Parser init event. // Parser init event.
void ParserInitEvent(const std::vector<std::shared_ptr<StreamInfo>>& streams); void ParserInitEvent(const std::vector<std::shared_ptr<StreamInfo>>& streams);
// Parser new sample event handler. Queues the samples if init event has not // Parser new sample event handler. Queues the samples if init event has not
@ -95,18 +117,29 @@ class Demuxer {
bool PushSample(uint32_t track_id, bool PushSample(uint32_t track_id,
const std::shared_ptr<MediaSample>& sample); const std::shared_ptr<MediaSample>& sample);
// Read from the source and send it to the parser.
Status Parse();
std::string file_name_; std::string file_name_;
File* media_file_; File* media_file_ = nullptr;
bool init_event_received_; // A stream is considered ready after receiving the stream info.
Status init_parsing_status_; bool all_streams_ready_ = false;
// Queued samples received in NewSampleEvent() before ParserInitEvent(). // Queued samples received in NewSampleEvent() before ParserInitEvent().
std::deque<QueuedSample> queued_samples_; std::deque<QueuedSample> queued_samples_;
std::unique_ptr<MediaParser> parser_; std::unique_ptr<MediaParser> parser_;
std::vector<std::unique_ptr<MediaStream>> streams_; // TrackId -> StreamIndex map.
MediaContainerName container_name_; std::map<uint32_t, int> track_id_to_stream_index_map_;
// The list of stream indexes in the above map (in the same order as the input
// stream info vector).
std::vector<int> stream_indexes_;
// StreamIndex -> language_override map.
std::map<int, std::string> language_overrides_;
MediaContainerName container_name_ = CONTAINER_UNKNOWN;
std::unique_ptr<uint8_t[]> buffer_; std::unique_ptr<uint8_t[]> buffer_;
std::unique_ptr<KeySource> key_source_; std::unique_ptr<KeySource> key_source_;
bool cancelled_; bool cancelled_ = false;
// Whether to dump stream info when it is received.
bool dump_stream_info_ = false;
}; };
} // namespace media } // namespace media

View File

@ -59,8 +59,6 @@
'media_parser.h', 'media_parser.h',
'media_sample.cc', 'media_sample.cc',
'media_sample.h', 'media_sample.h',
'media_stream.cc',
'media_stream.h',
'muxer.cc', 'muxer.cc',
'muxer.h', 'muxer.h',
'muxer_options.cc', 'muxer_options.cc',

View File

@ -172,6 +172,10 @@ class MediaHandler {
int num_input_streams() const { return num_input_streams_; } int num_input_streams() const { return num_input_streams_; }
int next_output_stream_index() const { return next_output_stream_index_; } int next_output_stream_index() const { return next_output_stream_index_; }
const std::map<int, std::pair<std::shared_ptr<MediaHandler>, int>>&
output_handlers() {
return output_handlers_;
}
private: private:
MediaHandler(const MediaHandler&) = delete; MediaHandler(const MediaHandler&) = delete;

View File

@ -1,111 +0,0 @@
// Copyright 2014 Google Inc. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
#include "packager/media/base/media_stream.h"
#include "packager/base/logging.h"
#include "packager/base/strings/stringprintf.h"
#include "packager/media/base/demuxer.h"
#include "packager/media/base/media_sample.h"
#include "packager/media/base/muxer.h"
#include "packager/media/base/stream_info.h"
namespace shaka {
namespace media {
MediaStream::MediaStream(std::shared_ptr<StreamInfo> info, Demuxer* demuxer)
: info_(info), demuxer_(demuxer), muxer_(NULL), state_(kIdle) {}
MediaStream::~MediaStream() {}
Status MediaStream::PullSample(std::shared_ptr<MediaSample>* sample) {
DCHECK(state_ == kPulling || state_ == kIdle);
// Trigger a new parse in demuxer if no more samples.
while (samples_.empty()) {
Status status = demuxer_->Parse();
if (!status.ok())
return status;
}
*sample = samples_.front();
samples_.pop_front();
return Status::OK;
}
Status MediaStream::PushSample(const std::shared_ptr<MediaSample>& sample) {
switch (state_) {
case kIdle:
case kPulling:
samples_.push_back(sample);
return Status::OK;
case kDisconnected:
return Status::OK;
case kPushing:
return muxer_->AddSample(this, sample);
default:
NOTREACHED() << "Unexpected State " << state_;
return Status::UNKNOWN;
}
}
void MediaStream::Connect(Muxer* muxer) {
DCHECK(muxer);
DCHECK(!muxer_);
state_ = kConnected;
muxer_ = muxer;
}
Status MediaStream::Start(MediaStreamOperation operation) {
DCHECK(demuxer_);
DCHECK(operation == kPush || operation == kPull);
switch (state_) {
case kIdle:
// Disconnect the stream if it is not connected to a muxer.
state_ = kDisconnected;
samples_.clear();
return Status::OK;
case kConnected:
state_ = (operation == kPush) ? kPushing : kPulling;
if (operation == kPush) {
// Push samples in the queue to muxer if there is any.
while (!samples_.empty()) {
Status status = muxer_->AddSample(this, samples_.front());
if (!status.ok())
return status;
samples_.pop_front();
}
} else {
// We need to disconnect all its peer streams which are not connected
// to a muxer.
for (size_t i = 0; i < demuxer_->streams().size(); ++i) {
Status status = demuxer_->streams()[i]->Start(operation);
if (!status.ok())
return status;
}
}
return Status::OK;
case kPulling:
DCHECK(operation == kPull);
return Status::OK;
default:
NOTREACHED() << "Unexpected State " << state_;
return Status::UNKNOWN;
}
}
const std::shared_ptr<StreamInfo> MediaStream::info() const {
return info_;
}
std::string MediaStream::ToString() const {
return base::StringPrintf("state: %d\n samples in the queue: %zu\n %s",
state_, samples_.size(), info_->ToString().c_str());
}
} // namespace media
} // namespace shaka

View File

@ -1,80 +0,0 @@
// Copyright 2014 Google Inc. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
#ifndef MEDIA_BASE_MEDIA_STREAM_H_
#define MEDIA_BASE_MEDIA_STREAM_H_
#include <deque>
#include <memory>
#include "packager/media/base/status.h"
namespace shaka {
namespace media {
class Demuxer;
class Muxer;
class MediaSample;
class StreamInfo;
/// MediaStream connects Demuxer to Muxer. It is an abstraction for a media
/// elementary stream.
class MediaStream {
public:
enum MediaStreamOperation {
kPush,
kPull,
};
/// Create MediaStream from StreamInfo and Demuxer.
/// @param demuxer cannot be NULL.
MediaStream(std::shared_ptr<StreamInfo> info, Demuxer* demuxer);
~MediaStream();
/// Connect the stream to Muxer.
/// @param muxer cannot be NULL.
void Connect(Muxer* muxer);
/// Start the stream for pushing or pulling.
Status Start(MediaStreamOperation operation);
/// Push sample to Muxer (triggered by Demuxer).
Status PushSample(const std::shared_ptr<MediaSample>& sample);
/// Pull sample from Demuxer (triggered by Muxer).
Status PullSample(std::shared_ptr<MediaSample>* sample);
Demuxer* demuxer() { return demuxer_; }
Muxer* muxer() { return muxer_; }
const std::shared_ptr<StreamInfo> info() const;
/// @return a human-readable string describing |*this|.
std::string ToString() const;
private:
MediaStream(const MediaStream&) = delete;
MediaStream& operator=(const MediaStream&) = delete;
// State transition diagram available @ http://goo.gl/ThJQbl.
enum State {
kIdle,
kConnected,
kDisconnected,
kPushing,
kPulling,
};
std::shared_ptr<StreamInfo> info_;
Demuxer* demuxer_;
Muxer* muxer_;
State state_;
// An internal buffer to store samples temporarily.
std::deque<std::shared_ptr<MediaSample>> samples_;
};
} // namespace media
} // namespace shaka
#endif // MEDIA_BASE_MEDIA_STREAM_H_

View File

@ -10,14 +10,12 @@
#include "packager/media/base/fourccs.h" #include "packager/media/base/fourccs.h"
#include "packager/media/base/media_sample.h" #include "packager/media/base/media_sample.h"
#include "packager/media/base/media_stream.h"
namespace shaka { namespace shaka {
namespace media { namespace media {
Muxer::Muxer(const MuxerOptions& options) Muxer::Muxer(const MuxerOptions& options)
: options_(options), : options_(options),
initialized_(false),
encryption_key_source_(NULL), encryption_key_source_(NULL),
max_sd_pixels_(0), max_sd_pixels_(0),
max_hd_pixels_(0), max_hd_pixels_(0),
@ -47,46 +45,6 @@ void Muxer::SetKeySource(KeySource* encryption_key_source,
protection_scheme_ = protection_scheme; protection_scheme_ = protection_scheme;
} }
void Muxer::AddStream(MediaStream* stream) {
DCHECK(stream);
stream->Connect(this);
streams_.push_back(stream);
}
Status Muxer::Run() {
DCHECK(!streams_.empty());
Status status;
// Start the streams.
for (std::vector<MediaStream*>::iterator it = streams_.begin();
it != streams_.end();
++it) {
status = (*it)->Start(MediaStream::kPull);
if (!status.ok())
return status;
}
uint32_t current_stream_id = 0;
while (status.ok()) {
if (cancelled_)
return Status(error::CANCELLED, "muxer run cancelled");
std::shared_ptr<MediaSample> sample;
status = streams_[current_stream_id]->PullSample(&sample);
if (!status.ok())
break;
status = AddSample(streams_[current_stream_id], sample);
// Switch to next stream if the current stream is ready for fragmentation.
if (status.error_code() == error::FRAGMENT_FINALIZED) {
current_stream_id = (current_stream_id + 1) % streams_.size();
status.Clear();
}
}
// Finalize the muxer after reaching end of stream.
return status.error_code() == error::END_OF_STREAM ? Finalize() : status;
}
void Muxer::Cancel() { void Muxer::Cancel() {
cancelled_ = true; cancelled_ = true;
} }
@ -100,26 +58,21 @@ void Muxer::SetProgressListener(
progress_listener_ = std::move(progress_listener); progress_listener_ = std::move(progress_listener);
} }
Status Muxer::AddSample(const MediaStream* stream, Status Muxer::Process(std::unique_ptr<StreamData> stream_data) {
std::shared_ptr<MediaSample> sample) { Status status;
DCHECK(std::find(streams_.begin(), streams_.end(), stream) != streams_.end()); switch (stream_data->stream_data_type) {
case StreamDataType::kStreamInfo:
if (!initialized_) { streams_.push_back(std::move(stream_data->stream_info));
Status status = Initialize(); return InitializeMuxer();
if (!status.ok()) case StreamDataType::kMediaSample:
return status; return DoAddSample(stream_data->media_sample);
initialized_ = true; default:
VLOG(3) << "Stream data type "
<< static_cast<int>(stream_data->stream_data_type) << " ignored.";
break;
} }
if (sample->end_of_stream()) { // No dispatch for muxer.
// EOS sample should be sent only when the sample was pushed from Demuxer return Status::OK;
// to Muxer. In this case, there should be only one stream in Muxer.
DCHECK_EQ(1u, streams_.size());
return Finalize();
} else if (sample->is_encrypted()) {
LOG(ERROR) << "Unable to multiplex encrypted media sample";
return Status(error::INTERNAL_ERROR, "Encrypted media sample.");
}
return DoAddSample(stream, sample);
} }
} // namespace media } // namespace media

View File

@ -14,6 +14,7 @@
#include "packager/base/time/clock.h" #include "packager/base/time/clock.h"
#include "packager/media/base/fourccs.h" #include "packager/media/base/fourccs.h"
#include "packager/media/base/media_handler.h"
#include "packager/media/base/muxer_options.h" #include "packager/media/base/muxer_options.h"
#include "packager/media/base/status.h" #include "packager/media/base/status.h"
#include "packager/media/event/muxer_listener.h" #include "packager/media/event/muxer_listener.h"
@ -29,7 +30,7 @@ class MediaStream;
/// Muxer is responsible for taking elementary stream samples and producing /// Muxer is responsible for taking elementary stream samples and producing
/// media containers. An optional KeySource can be provided to Muxer /// media containers. An optional KeySource can be provided to Muxer
/// to generate encrypted outputs. /// to generate encrypted outputs.
class Muxer { class Muxer : public MediaHandler {
public: public:
explicit Muxer(const MuxerOptions& options); explicit Muxer(const MuxerOptions& options);
virtual ~Muxer(); virtual ~Muxer();
@ -65,12 +66,6 @@ class Muxer {
double crypto_period_duration_in_seconds, double crypto_period_duration_in_seconds,
FourCC protection_scheme); FourCC protection_scheme);
/// Add video/audio stream.
void AddStream(MediaStream* stream);
/// Drive the remuxing from muxer side (pull).
Status Run();
/// Cancel a muxing job in progress. Will cause @a Run to exit with an error /// Cancel a muxing job in progress. Will cause @a Run to exit with an error
/// status of type CANCELLED. /// status of type CANCELLED.
void Cancel(); void Cancel();
@ -83,7 +78,9 @@ class Muxer {
/// @param progress_listener should not be NULL. /// @param progress_listener should not be NULL.
void SetProgressListener(std::unique_ptr<ProgressListener> progress_listener); void SetProgressListener(std::unique_ptr<ProgressListener> progress_listener);
const std::vector<MediaStream*>& streams() const { return streams_; } const std::vector<std::shared_ptr<StreamInfo>>& streams() const {
return streams_;
}
/// Inject clock, mainly used for testing. /// Inject clock, mainly used for testing.
/// The injected clock will be used to generate the creation time-stamp and /// The injected clock will be used to generate the creation time-stamp and
@ -96,6 +93,13 @@ class Muxer {
} }
protected: protected:
/// @name MediaHandler implementation overrides.
/// @{
Status InitializeInternal() override { return Status::OK; }
Status Process(std::unique_ptr<StreamData> stream_data) override;
Status FlushStream(int input_stream_index) override { return Finalize(); }
/// @}
const MuxerOptions& options() const { return options_; } const MuxerOptions& options() const { return options_; }
KeySource* encryption_key_source() { KeySource* encryption_key_source() {
return encryption_key_source_; return encryption_key_source_;
@ -113,25 +117,17 @@ class Muxer {
FourCC protection_scheme() const { return protection_scheme_; } FourCC protection_scheme() const { return protection_scheme_; }
private: private:
friend class MediaStream; // Needed to access AddSample.
// Add new media sample.
Status AddSample(const MediaStream* stream,
std::shared_ptr<MediaSample> sample);
// Initialize the muxer. // Initialize the muxer.
virtual Status Initialize() = 0; virtual Status InitializeMuxer() = 0;
// Final clean up. // Final clean up.
virtual Status Finalize() = 0; virtual Status Finalize() = 0;
// AddSample implementation. // AddSample implementation.
virtual Status DoAddSample(const MediaStream* stream, virtual Status DoAddSample(std::shared_ptr<MediaSample> sample) = 0;
std::shared_ptr<MediaSample> sample) = 0;
MuxerOptions options_; MuxerOptions options_;
bool initialized_; std::vector<std::shared_ptr<StreamInfo>> streams_;
std::vector<MediaStream*> streams_;
KeySource* encryption_key_source_; KeySource* encryption_key_source_;
uint32_t max_sd_pixels_; uint32_t max_sd_pixels_;
uint32_t max_hd_pixels_; uint32_t max_hd_pixels_;

View File

@ -17,14 +17,13 @@ const uint32_t kTsTimescale = 90000;
TsMuxer::TsMuxer(const MuxerOptions& muxer_options) : Muxer(muxer_options) {} TsMuxer::TsMuxer(const MuxerOptions& muxer_options) : Muxer(muxer_options) {}
TsMuxer::~TsMuxer() {} TsMuxer::~TsMuxer() {}
Status TsMuxer::Initialize() { Status TsMuxer::InitializeMuxer() {
if (streams().size() > 1u) if (streams().size() > 1u)
return Status(error::MUXER_FAILURE, "Cannot handle more than one streams."); return Status(error::MUXER_FAILURE, "Cannot handle more than one streams.");
segmenter_.reset(new TsSegmenter(options(), muxer_listener())); segmenter_.reset(new TsSegmenter(options(), muxer_listener()));
Status status = Status status = segmenter_->Initialize(
segmenter_->Initialize(*streams()[0]->info(), encryption_key_source(), *streams()[0], encryption_key_source(), max_sd_pixels(), max_hd_pixels(),
max_sd_pixels(), max_hd_pixels(),
max_uhd1_pixels(), clear_lead_in_seconds()); max_uhd1_pixels(), clear_lead_in_seconds());
FireOnMediaStartEvent(); FireOnMediaStartEvent();
return status; return status;
@ -35,16 +34,15 @@ Status TsMuxer::Finalize() {
return segmenter_->Finalize(); return segmenter_->Finalize();
} }
Status TsMuxer::DoAddSample(const MediaStream* stream, Status TsMuxer::DoAddSample(std::shared_ptr<MediaSample> sample) {
std::shared_ptr<MediaSample> sample) {
return segmenter_->AddSample(sample); return segmenter_->AddSample(sample);
} }
void TsMuxer::FireOnMediaStartEvent() { void TsMuxer::FireOnMediaStartEvent() {
if (!muxer_listener()) if (!muxer_listener())
return; return;
muxer_listener()->OnMediaStart(options(), *streams().front()->info(), muxer_listener()->OnMediaStart(options(), *streams().front(), kTsTimescale,
kTsTimescale, MuxerListener::kContainerWebM); MuxerListener::kContainerWebM);
} }
void TsMuxer::FireOnMediaEndEvent() { void TsMuxer::FireOnMediaEndEvent() {

View File

@ -24,10 +24,9 @@ class TsMuxer : public Muxer {
private: private:
// Muxer implementation. // Muxer implementation.
Status Initialize() override; Status InitializeMuxer() override;
Status Finalize() override; Status Finalize() override;
Status DoAddSample(const MediaStream* stream, Status DoAddSample(std::shared_ptr<MediaSample> sample) override;
std::shared_ptr<MediaSample> sample) override;
void FireOnMediaStartEvent(); void FireOnMediaStartEvent();
void FireOnMediaEndEvent(); void FireOnMediaEndEvent();

View File

@ -8,7 +8,6 @@
#define PACKAGER_MEDIA_FORMATS_MP2T_TS_SEGMENTER_H_ #define PACKAGER_MEDIA_FORMATS_MP2T_TS_SEGMENTER_H_
#include <memory> #include <memory>
#include "packager/media/base/media_stream.h"
#include "packager/media/base/muxer_options.h" #include "packager/media/base/muxer_options.h"
#include "packager/media/base/status.h" #include "packager/media/base/status.h"
#include "packager/media/file/file.h" #include "packager/media/file/file.h"

View File

@ -12,7 +12,6 @@
#include <memory> #include <memory>
#include <vector> #include <vector>
#include "packager/media/base/media_stream.h"
#include "packager/media/file/file.h" #include "packager/media/file/file.h"
#include "packager/media/file/file_closer.h" #include "packager/media/file/file_closer.h"
#include "packager/media/formats/mp2t/continuity_counter.h" #include "packager/media/formats/mp2t/continuity_counter.h"
@ -21,6 +20,9 @@
namespace shaka { namespace shaka {
namespace media { namespace media {
class StreamInfo;
namespace mp2t { namespace mp2t {
/// This class takes PesPackets, encapsulates them into TS packets, and write /// This class takes PesPackets, encapsulates them into TS packets, and write

View File

@ -13,7 +13,6 @@
#include "packager/media/base/fourccs.h" #include "packager/media/base/fourccs.h"
#include "packager/media/base/key_source.h" #include "packager/media/base/key_source.h"
#include "packager/media/base/media_sample.h" #include "packager/media/base/media_sample.h"
#include "packager/media/base/media_stream.h"
#include "packager/media/base/video_stream_info.h" #include "packager/media/base/video_stream_info.h"
#include "packager/media/codecs/es_descriptor.h" #include "packager/media/codecs/es_descriptor.h"
#include "packager/media/event/muxer_listener.h" #include "packager/media/event/muxer_listener.h"
@ -82,7 +81,7 @@ FourCC CodecToFourCC(Codec codec) {
MP4Muxer::MP4Muxer(const MuxerOptions& options) : Muxer(options) {} MP4Muxer::MP4Muxer(const MuxerOptions& options) : Muxer(options) {}
MP4Muxer::~MP4Muxer() {} MP4Muxer::~MP4Muxer() {}
Status MP4Muxer::Initialize() { Status MP4Muxer::InitializeMuxer() {
DCHECK(!streams().empty()); DCHECK(!streams().empty());
std::unique_ptr<FileType> ftyp(new FileType); std::unique_ptr<FileType> ftyp(new FileType);
@ -91,10 +90,9 @@ Status MP4Muxer::Initialize() {
ftyp->major_brand = FOURCC_dash; ftyp->major_brand = FOURCC_dash;
ftyp->compatible_brands.push_back(FOURCC_iso6); ftyp->compatible_brands.push_back(FOURCC_iso6);
ftyp->compatible_brands.push_back(FOURCC_mp41); ftyp->compatible_brands.push_back(FOURCC_mp41);
if (streams().size() == 1 && if (streams().size() == 1 && streams()[0]->stream_type() == kStreamVideo) {
streams()[0]->info()->stream_type() == kStreamVideo) {
const FourCC codec_fourcc = CodecToFourCC( const FourCC codec_fourcc = CodecToFourCC(
static_cast<VideoStreamInfo*>(streams()[0]->info().get())->codec()); static_cast<VideoStreamInfo*>(streams()[0].get())->codec());
if (codec_fourcc != FOURCC_NULL) if (codec_fourcc != FOURCC_NULL)
ftyp->compatible_brands.push_back(codec_fourcc); ftyp->compatible_brands.push_back(codec_fourcc);
} }
@ -115,22 +113,18 @@ Status MP4Muxer::Initialize() {
trex.track_id = trak.header.track_id; trex.track_id = trak.header.track_id;
trex.default_sample_description_index = 1; trex.default_sample_description_index = 1;
switch (streams()[i]->info()->stream_type()) { switch (streams()[i]->stream_type()) {
case kStreamVideo: case kStreamVideo:
GenerateVideoTrak( GenerateVideoTrak(static_cast<VideoStreamInfo*>(streams()[i].get()),
static_cast<VideoStreamInfo*>(streams()[i]->info().get()), &trak, i + 1);
&trak,
i + 1);
break; break;
case kStreamAudio: case kStreamAudio:
GenerateAudioTrak( GenerateAudioTrak(static_cast<AudioStreamInfo*>(streams()[i].get()),
static_cast<AudioStreamInfo*>(streams()[i]->info().get()), &trak, i + 1);
&trak,
i + 1);
break; break;
default: default:
NOTIMPLEMENTED() << "Not implemented for stream type: " NOTIMPLEMENTED() << "Not implemented for stream type: "
<< streams()[i]->info()->stream_type(); << streams()[i]->stream_type();
} }
} }
@ -167,10 +161,9 @@ Status MP4Muxer::Finalize() {
return Status::OK; return Status::OK;
} }
Status MP4Muxer::DoAddSample(const MediaStream* stream, Status MP4Muxer::DoAddSample(std::shared_ptr<MediaSample> sample) {
std::shared_ptr<MediaSample> sample) {
DCHECK(segmenter_); DCHECK(segmenter_);
return segmenter_->AddSample(stream, sample); return segmenter_->AddSample(*streams()[0], sample);
} }
void MP4Muxer::InitializeTrak(const StreamInfo* info, Track* trak) { void MP4Muxer::InitializeTrak(const StreamInfo* info, Track* trak) {
@ -355,9 +348,7 @@ void MP4Muxer::FireOnMediaStartEvent() {
DCHECK(!streams().empty()) << "Media started without a stream."; DCHECK(!streams().empty()) << "Media started without a stream.";
const uint32_t timescale = segmenter_->GetReferenceTimeScale(); const uint32_t timescale = segmenter_->GetReferenceTimeScale();
muxer_listener()->OnMediaStart(options(), muxer_listener()->OnMediaStart(options(), *streams().front(), timescale,
*streams().front()->info(),
timescale,
MuxerListener::kContainerMp4); MuxerListener::kContainerMp4);
} }

View File

@ -35,10 +35,9 @@ class MP4Muxer : public Muxer {
private: private:
// Muxer implementation overrides. // Muxer implementation overrides.
Status Initialize() override; Status InitializeMuxer() override;
Status Finalize() override; Status Finalize() override;
Status DoAddSample(const MediaStream* stream, Status DoAddSample(std::shared_ptr<MediaSample> sample) override;
std::shared_ptr<MediaSample> sample) 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);

View File

@ -11,7 +11,6 @@
#include "packager/base/strings/string_number_conversions.h" #include "packager/base/strings/string_number_conversions.h"
#include "packager/base/strings/string_util.h" #include "packager/base/strings/string_util.h"
#include "packager/media/base/buffer_writer.h" #include "packager/media/base/buffer_writer.h"
#include "packager/media/base/media_stream.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/event/muxer_listener.h" #include "packager/media/event/muxer_listener.h"

View File

@ -13,7 +13,6 @@
#include "packager/media/base/buffer_writer.h" #include "packager/media/base/buffer_writer.h"
#include "packager/media/base/key_source.h" #include "packager/media/base/key_source.h"
#include "packager/media/base/media_sample.h" #include "packager/media/base/media_sample.h"
#include "packager/media/base/media_stream.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/base/video_stream_info.h" #include "packager/media/base/video_stream_info.h"
@ -162,7 +161,8 @@ Segmenter::Segmenter(const MuxerOptions& options,
Segmenter::~Segmenter() {} Segmenter::~Segmenter() {}
Status Segmenter::Initialize(const std::vector<MediaStream*>& streams, Status Segmenter::Initialize(
const std::vector<std::shared_ptr<StreamInfo>>& streams,
MuxerListener* muxer_listener, MuxerListener* muxer_listener,
ProgressListener* progress_listener, ProgressListener* progress_listener,
KeySource* encryption_key_source, KeySource* encryption_key_source,
@ -184,22 +184,19 @@ Status Segmenter::Initialize(const std::vector<MediaStream*>& streams,
const bool kInitialEncryptionInfo = true; const bool kInitialEncryptionInfo = true;
for (uint32_t i = 0; i < streams.size(); ++i) { for (uint32_t i = 0; i < streams.size(); ++i) {
stream_map_[streams[i]] = i;
moof_->tracks[i].header.track_id = i + 1; moof_->tracks[i].header.track_id = i + 1;
if (streams[i]->info()->stream_type() == kStreamVideo) { if (streams[i]->stream_type() == kStreamVideo) {
// Use the first video stream as the reference stream (which is 1-based). // Use the first video stream as the reference stream (which is 1-based).
if (sidx_->reference_id == 0) if (sidx_->reference_id == 0)
sidx_->reference_id = i + 1; sidx_->reference_id = i + 1;
} }
if (!encryption_key_source) { if (!encryption_key_source) {
fragmenters_[i].reset( fragmenters_[i].reset(new Fragmenter(streams[i], &moof_->tracks[i]));
new Fragmenter(streams[i]->info(), &moof_->tracks[i]));
continue; continue;
} }
KeySource::TrackType track_type = KeySource::TrackType track_type = GetTrackTypeForEncryption(
GetTrackTypeForEncryption(*streams[i]->info(), max_sd_pixels, *streams[i], max_sd_pixels, max_hd_pixels, max_uhd1_pixels);
max_hd_pixels, max_uhd1_pixels);
SampleDescription& description = SampleDescription& description =
moov_->tracks[i].media.information.sample_table.description; moov_->tracks[i].media.information.sample_table.description;
ProtectionPattern pattern = ProtectionPattern pattern =
@ -224,12 +221,11 @@ Status Segmenter::Initialize(const std::vector<MediaStream*>& streams,
} }
fragmenters_[i].reset(new KeyRotationFragmenter( fragmenters_[i].reset(new KeyRotationFragmenter(
moof_.get(), streams[i]->info(), &moof_->tracks[i], moof_.get(), streams[i], &moof_->tracks[i], encryption_key_source,
encryption_key_source, track_type, track_type,
crypto_period_duration_in_seconds * streams[i]->info()->time_scale(), crypto_period_duration_in_seconds * streams[i]->time_scale(),
clear_lead_in_seconds * streams[i]->info()->time_scale(), clear_lead_in_seconds * streams[i]->time_scale(), protection_scheme,
protection_scheme, pattern.crypt_byte_block, pattern.skip_byte_block, pattern.crypt_byte_block, pattern.skip_byte_block, muxer_listener_));
muxer_listener_));
continue; continue;
} }
@ -262,10 +258,9 @@ Status Segmenter::Initialize(const std::vector<MediaStream*>& streams,
} }
fragmenters_[i].reset(new EncryptingFragmenter( fragmenters_[i].reset(new EncryptingFragmenter(
streams[i]->info(), &moof_->tracks[i], std::move(encryption_key), streams[i], &moof_->tracks[i], std::move(encryption_key),
clear_lead_in_seconds * streams[i]->info()->time_scale(), clear_lead_in_seconds * streams[i]->time_scale(), protection_scheme,
protection_scheme, pattern.crypt_byte_block, pattern.skip_byte_block, pattern.crypt_byte_block, pattern.skip_byte_block, muxer_listener_));
muxer_listener_));
} }
if (options_.mp4_use_decoding_timestamp_in_timeline) { if (options_.mp4_use_decoding_timestamp_in_timeline) {
@ -276,10 +271,10 @@ Status Segmenter::Initialize(const std::vector<MediaStream*>& streams,
// Choose the first stream if there is no VIDEO. // Choose the first stream if there is no VIDEO.
if (sidx_->reference_id == 0) if (sidx_->reference_id == 0)
sidx_->reference_id = 1; sidx_->reference_id = 1;
sidx_->timescale = streams[GetReferenceStreamId()]->info()->time_scale(); sidx_->timescale = streams[GetReferenceStreamId()]->time_scale();
// Use media duration as progress target. // Use media duration as progress target.
progress_target_ = streams[GetReferenceStreamId()]->info()->duration(); progress_target_ = streams[GetReferenceStreamId()]->duration();
// Use the reference stream's time scale as movie time scale. // Use the reference stream's time scale as movie time scale.
moov_->header.timescale = sidx_->timescale; moov_->header.timescale = sidx_->timescale;
@ -320,12 +315,10 @@ Status Segmenter::Finalize() {
return DoFinalize(); return DoFinalize();
} }
Status Segmenter::AddSample(const MediaStream* stream, Status Segmenter::AddSample(const StreamInfo& stream_info,
std::shared_ptr<MediaSample> sample) { std::shared_ptr<MediaSample> sample) {
// Find the fragmenter for this stream. // TODO(kqyang): Stream id should be passed in.
DCHECK(stream); const uint32_t stream_id = 0;
DCHECK(stream_map_.find(stream) != stream_map_.end());
uint32_t stream_id = stream_map_[stream];
Fragmenter* fragmenter = fragmenters_[stream_id].get(); 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.
@ -341,14 +334,14 @@ Status Segmenter::AddSample(const MediaStream* stream,
bool finalize_fragment = false; bool finalize_fragment = false;
if (fragmenter->fragment_duration() >= if (fragmenter->fragment_duration() >=
options_.fragment_duration * stream->info()->time_scale()) { options_.fragment_duration * stream_info.time_scale()) {
if (sample->is_key_frame() || !options_.fragment_sap_aligned) { if (sample->is_key_frame() || !options_.fragment_sap_aligned) {
finalize_fragment = true; finalize_fragment = true;
} }
} }
bool finalize_segment = false; bool finalize_segment = false;
if (segment_durations_[stream_id] >= if (segment_durations_[stream_id] >=
options_.segment_duration * stream->info()->time_scale()) { options_.segment_duration * stream_info.time_scale()) {
if (sample->is_key_frame() || !options_.segment_sap_aligned) { if (sample->is_key_frame() || !options_.segment_sap_aligned) {
finalize_segment = true; finalize_segment = true;
finalize_fragment = true; finalize_fragment = true;

View File

@ -23,9 +23,9 @@ struct MuxerOptions;
class BufferWriter; class BufferWriter;
class KeySource; class KeySource;
class MediaSample; class MediaSample;
class MediaStream;
class MuxerListener; class MuxerListener;
class ProgressListener; class ProgressListener;
class StreamInfo;
namespace mp4 { namespace mp4 {
@ -69,7 +69,7 @@ class Segmenter {
/// @param protection_scheme specifies the protection scheme: 'cenc', 'cens', /// @param protection_scheme specifies the protection scheme: 'cenc', 'cens',
/// 'cbc1', 'cbcs'. /// 'cbc1', 'cbcs'.
/// @return OK on success, an error status otherwise. /// @return OK on success, an error status otherwise.
Status Initialize(const std::vector<MediaStream*>& streams, Status Initialize(const std::vector<std::shared_ptr<StreamInfo>>& streams,
MuxerListener* muxer_listener, MuxerListener* muxer_listener,
ProgressListener* progress_listener, ProgressListener* progress_listener,
KeySource* encryption_key_source, KeySource* encryption_key_source,
@ -85,11 +85,9 @@ class Segmenter {
Status Finalize(); Status Finalize();
/// Add sample to the indicated stream. /// Add sample to the indicated stream.
/// @param stream points to the stream to which the sample belongs. It cannot
/// be NULL.
/// @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 MediaStream* stream, Status AddSample(const StreamInfo& stream_Info,
std::shared_ptr<MediaSample> sample); std::shared_ptr<MediaSample> sample);
/// @return true if there is an initialization range, while setting @a offset /// @return true if there is an initialization range, while setting @a offset
@ -145,7 +143,6 @@ class Segmenter {
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_; std::vector<uint64_t> segment_durations_;
std::map<const MediaStream*, uint32_t> stream_map_;
MuxerListener* muxer_listener_; MuxerListener* muxer_listener_;
ProgressListener* progress_listener_; ProgressListener* progress_listener_;
uint64_t progress_target_; uint64_t progress_target_;

View File

@ -9,7 +9,6 @@
#include <algorithm> #include <algorithm>
#include "packager/media/base/buffer_writer.h" #include "packager/media/base/buffer_writer.h"
#include "packager/media/base/media_stream.h"
#include "packager/media/base/muxer_options.h" #include "packager/media/base/muxer_options.h"
#include "packager/media/event/muxer_listener.h" #include "packager/media/event/muxer_listener.h"
#include "packager/media/event/progress_listener.h" #include "packager/media/event/progress_listener.h"

View File

@ -6,7 +6,6 @@
#include "packager/media/formats/webm/multi_segment_segmenter.h" #include "packager/media/formats/webm/multi_segment_segmenter.h"
#include "packager/media/base/media_stream.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/base/stream_info.h" #include "packager/media/base/stream_info.h"

View File

@ -9,7 +9,6 @@
#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_sample.h" #include "packager/media/base/media_sample.h"
#include "packager/media/base/media_stream.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/base/stream_info.h" #include "packager/media/base/stream_info.h"

View File

@ -9,7 +9,6 @@
#include <algorithm> #include <algorithm>
#include "packager/media/base/media_sample.h" #include "packager/media/base/media_sample.h"
#include "packager/media/base/media_stream.h"
#include "packager/media/base/muxer_options.h" #include "packager/media/base/muxer_options.h"
#include "packager/media/base/stream_info.h" #include "packager/media/base/stream_info.h"
#include "packager/media/file/file_util.h" #include "packager/media/file/file_util.h"

View File

@ -8,7 +8,6 @@
#include "packager/media/base/fourccs.h" #include "packager/media/base/fourccs.h"
#include "packager/media/base/media_sample.h" #include "packager/media/base/media_sample.h"
#include "packager/media/base/media_stream.h"
#include "packager/media/base/stream_info.h" #include "packager/media/base/stream_info.h"
#include "packager/media/formats/webm/mkv_writer.h" #include "packager/media/formats/webm/mkv_writer.h"
#include "packager/media/formats/webm/multi_segment_segmenter.h" #include "packager/media/formats/webm/multi_segment_segmenter.h"
@ -22,7 +21,7 @@ namespace webm {
WebMMuxer::WebMMuxer(const MuxerOptions& options) : Muxer(options) {} WebMMuxer::WebMMuxer(const MuxerOptions& options) : Muxer(options) {}
WebMMuxer::~WebMMuxer() {} WebMMuxer::~WebMMuxer() {}
Status WebMMuxer::Initialize() { Status WebMMuxer::InitializeMuxer() {
CHECK_EQ(streams().size(), 1U); CHECK_EQ(streams().size(), 1U);
if (crypto_period_duration_in_seconds() > 0) { if (crypto_period_duration_in_seconds() > 0) {
@ -50,7 +49,7 @@ Status WebMMuxer::Initialize() {
} }
Status initialized = segmenter_->Initialize( Status initialized = segmenter_->Initialize(
std::move(writer), streams()[0]->info().get(), progress_listener(), std::move(writer), streams()[0].get(), progress_listener(),
muxer_listener(), encryption_key_source(), max_sd_pixels(), muxer_listener(), encryption_key_source(), max_sd_pixels(),
max_hd_pixels(), max_uhd1_pixels(), clear_lead_in_seconds()); max_hd_pixels(), max_uhd1_pixels(), clear_lead_in_seconds());
@ -73,10 +72,8 @@ Status WebMMuxer::Finalize() {
return Status::OK; return Status::OK;
} }
Status WebMMuxer::DoAddSample(const MediaStream* stream, Status WebMMuxer::DoAddSample(std::shared_ptr<MediaSample> sample) {
std::shared_ptr<MediaSample> sample) {
DCHECK(segmenter_); DCHECK(segmenter_);
DCHECK(stream == streams()[0]);
return segmenter_->AddSample(sample); return segmenter_->AddSample(sample);
} }
@ -86,9 +83,9 @@ void WebMMuxer::FireOnMediaStartEvent() {
DCHECK(!streams().empty()) << "Media started without a stream."; DCHECK(!streams().empty()) << "Media started without a stream.";
const uint32_t timescale = streams().front()->info()->time_scale(); const uint32_t timescale = streams().front()->time_scale();
muxer_listener()->OnMediaStart(options(), *streams().front()->info(), muxer_listener()->OnMediaStart(options(), *streams().front(), timescale,
timescale, MuxerListener::kContainerWebM); MuxerListener::kContainerWebM);
} }
void WebMMuxer::FireOnMediaEndEvent() { void WebMMuxer::FireOnMediaEndEvent() {

View File

@ -24,10 +24,9 @@ class WebMMuxer : public Muxer {
private: private:
// Muxer implementation overrides. // Muxer implementation overrides.
Status Initialize() override; Status InitializeMuxer() override;
Status Finalize() override; Status Finalize() override;
Status DoAddSample(const MediaStream* stream, Status DoAddSample(std::shared_ptr<MediaSample> sample) override;
std::shared_ptr<MediaSample> sample) override;
void FireOnMediaStartEvent(); void FireOnMediaStartEvent();
void FireOnMediaEndEvent(); void FireOnMediaEndEvent();

View File

@ -13,7 +13,6 @@
#include "packager/media/base/demuxer.h" #include "packager/media/base/demuxer.h"
#include "packager/media/base/fixed_key_source.h" #include "packager/media/base/fixed_key_source.h"
#include "packager/media/base/fourccs.h" #include "packager/media/base/fourccs.h"
#include "packager/media/base/media_stream.h"
#include "packager/media/base/muxer.h" #include "packager/media/base/muxer.h"
#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"
@ -50,7 +49,6 @@ const bool kSingleSegment = true;
const bool kMultipleSegments = false; const bool kMultipleSegments = false;
const bool kEnableEncryption = true; const bool kEnableEncryption = true;
const bool kDisableEncryption = false; const bool kDisableEncryption = false;
const char kNoLanguageOverride[] = "";
// Encryption constants. // Encryption constants.
const char kKeyIdHex[] = "e5007e6e9dcd5ac095202ed3758382cd"; const char kKeyIdHex[] = "e5007e6e9dcd5ac095202ed3758382cd";
@ -63,24 +61,6 @@ const uint32_t kMaxSDPixels = 640 * 480;
const uint32_t kMaxHDPixels = 1920 * 1080; const uint32_t kMaxHDPixels = 1920 * 1080;
const uint32_t kMaxUHD1Pixels = 4096 * 2160; const uint32_t kMaxUHD1Pixels = 4096 * 2160;
MediaStream* FindFirstStreamOfType(
const std::vector<std::unique_ptr<MediaStream>>& streams,
StreamType stream_type) {
for (const std::unique_ptr<MediaStream>& stream : streams) {
if (stream->info()->stream_type() == stream_type)
return stream.get();
}
return nullptr;
}
MediaStream* FindFirstVideoStream(
const std::vector<std::unique_ptr<MediaStream>>& streams) {
return FindFirstStreamOfType(streams, kStreamVideo);
}
MediaStream* FindFirstAudioStream(
const std::vector<std::unique_ptr<MediaStream>>& streams) {
return FindFirstStreamOfType(streams, kStreamAudio);
}
} // namespace } // namespace
class FakeClock : public base::Clock { class FakeClock : public base::Clock {
@ -114,8 +94,7 @@ class PackagerTestBasic : public ::testing::TestWithParam<const char*> {
const std::string& video_output, const std::string& video_output,
const std::string& audio_output, const std::string& audio_output,
bool single_segment, bool single_segment,
bool enable_encryption, bool enable_encryption);
const std::string& override_language);
void Decrypt(const std::string& input, void Decrypt(const std::string& input,
const std::string& video_output, const std::string& video_output,
@ -157,59 +136,46 @@ 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,
bool single_segment, bool single_segment,
bool enable_encryption, bool enable_encryption) {
const std::string& language_override) {
CHECK(!video_output.empty() || !audio_output.empty()); CHECK(!video_output.empty() || !audio_output.empty());
Demuxer demuxer(GetFullPath(input)); Demuxer demuxer(GetFullPath(input));
ASSERT_OK(demuxer.Initialize());
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);
std::unique_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(SetupOptions(video_output, single_segment)));
muxer_video->set_clock(&fake_clock_); muxer_video->set_clock(&fake_clock_);
MediaStream* stream = FindFirstVideoStream(demuxer.streams());
if (!language_override.empty()) {
stream->info()->set_language(language_override);
ASSERT_EQ(language_override, stream->info()->language());
}
muxer_video->AddStream(stream);
if (enable_encryption) { if (enable_encryption) {
muxer_video->SetKeySource(encryption_key_source.get(), muxer_video->SetKeySource(encryption_key_source.get(),
kMaxSDPixels, kMaxHDPixels, kMaxSDPixels, kMaxHDPixels,
kMaxUHD1Pixels, kClearLeadInSeconds, kMaxUHD1Pixels, kClearLeadInSeconds,
kCryptoDurationInSeconds, FOURCC_cenc); kCryptoDurationInSeconds, FOURCC_cenc);
} }
ASSERT_OK(demuxer.SetHandler("video", muxer_video));
} }
std::unique_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(SetupOptions(audio_output, single_segment)));
muxer_audio->set_clock(&fake_clock_); muxer_audio->set_clock(&fake_clock_);
MediaStream* stream = FindFirstAudioStream(demuxer.streams());
if (!language_override.empty()) {
stream->info()->set_language(language_override);
ASSERT_EQ(language_override, stream->info()->language());
}
muxer_audio->AddStream(stream);
if (enable_encryption) { if (enable_encryption) {
muxer_audio->SetKeySource(encryption_key_source.get(), muxer_audio->SetKeySource(encryption_key_source.get(),
kMaxSDPixels, kMaxHDPixels, kMaxSDPixels, kMaxHDPixels,
kMaxUHD1Pixels, kClearLeadInSeconds, kMaxUHD1Pixels, kClearLeadInSeconds,
kCryptoDurationInSeconds, FOURCC_cenc); kCryptoDurationInSeconds, FOURCC_cenc);
} }
ASSERT_OK(demuxer.SetHandler("audio", muxer_audio));
} }
ASSERT_OK(demuxer.Initialize());
// Start remuxing process. // Start remuxing process.
ASSERT_OK(demuxer.Run()); ASSERT_OK(demuxer.Run());
} }
@ -224,25 +190,20 @@ void PackagerTestBasic::Decrypt(const std::string& input,
FixedKeySource::CreateFromHexStrings(kKeyIdHex, kKeyHex, "", "")); FixedKeySource::CreateFromHexStrings(kKeyIdHex, kKeyHex, "", ""));
ASSERT_TRUE(decryption_key_source); ASSERT_TRUE(decryption_key_source);
demuxer.SetKeySource(std::move(decryption_key_source)); demuxer.SetKeySource(std::move(decryption_key_source));
ASSERT_OK(demuxer.Initialize());
std::unique_ptr<Muxer> muxer; std::shared_ptr<Muxer> muxer;
MediaStream* stream(NULL);
if (!video_output.empty()) { if (!video_output.empty()) {
muxer.reset( muxer.reset(
new mp4::MP4Muxer(SetupOptions(video_output, true))); new mp4::MP4Muxer(SetupOptions(video_output, true)));
stream = FindFirstVideoStream(demuxer.streams());
} }
if (!audio_output.empty()) { if (!audio_output.empty()) {
muxer.reset( muxer.reset(
new mp4::MP4Muxer(SetupOptions(audio_output, true))); new mp4::MP4Muxer(SetupOptions(audio_output, true)));
stream = FindFirstAudioStream(demuxer.streams());
} }
ASSERT_TRUE(muxer); ASSERT_TRUE(muxer);
ASSERT_TRUE(stream != NULL);
ASSERT_TRUE(stream->info()->is_encrypted());
muxer->set_clock(&fake_clock_); muxer->set_clock(&fake_clock_);
muxer->AddStream(stream); ASSERT_OK(demuxer.SetHandler("0", muxer));
ASSERT_OK(demuxer.Initialize());
ASSERT_OK(demuxer.Run()); ASSERT_OK(demuxer.Run());
} }
@ -252,8 +213,7 @@ TEST_P(PackagerTestBasic, MP4MuxerSingleSegmentUnencryptedVideo) {
kOutputVideo, kOutputVideo,
kOutputNone, kOutputNone,
kSingleSegment, kSingleSegment,
kDisableEncryption, kDisableEncryption));
kNoLanguageOverride));
} }
TEST_P(PackagerTestBasic, MP4MuxerSingleSegmentUnencryptedAudio) { TEST_P(PackagerTestBasic, MP4MuxerSingleSegmentUnencryptedAudio) {
@ -261,8 +221,7 @@ TEST_P(PackagerTestBasic, MP4MuxerSingleSegmentUnencryptedAudio) {
kOutputNone, kOutputNone,
kOutputAudio, kOutputAudio,
kSingleSegment, kSingleSegment,
kDisableEncryption, kDisableEncryption));
kNoLanguageOverride));
} }
TEST_P(PackagerTestBasic, MP4MuxerSingleSegmentEncryptedVideo) { TEST_P(PackagerTestBasic, MP4MuxerSingleSegmentEncryptedVideo) {
@ -270,8 +229,7 @@ TEST_P(PackagerTestBasic, MP4MuxerSingleSegmentEncryptedVideo) {
kOutputVideo, kOutputVideo,
kOutputNone, kOutputNone,
kSingleSegment, kSingleSegment,
kEnableEncryption, kEnableEncryption));
kNoLanguageOverride));
ASSERT_NO_FATAL_FAILURE(Decrypt(kOutputVideo, ASSERT_NO_FATAL_FAILURE(Decrypt(kOutputVideo,
kOutputVideo2, kOutputVideo2,
@ -283,76 +241,13 @@ TEST_P(PackagerTestBasic, MP4MuxerSingleSegmentEncryptedAudio) {
kOutputNone, kOutputNone,
kOutputAudio, kOutputAudio,
kSingleSegment, kSingleSegment,
kEnableEncryption, kEnableEncryption));
kNoLanguageOverride));
ASSERT_NO_FATAL_FAILURE(Decrypt(kOutputAudio, ASSERT_NO_FATAL_FAILURE(Decrypt(kOutputAudio,
kOutputNone, kOutputNone,
kOutputAudio2)); kOutputAudio2));
} }
TEST_P(PackagerTestBasic, MP4MuxerLanguageWithoutSubtag) {
ASSERT_NO_FATAL_FAILURE(Remux(GetParam(),
kOutputNone,
kOutputAudio,
kSingleSegment,
kDisableEncryption,
"por"));
Demuxer demuxer(GetFullPath(kOutputAudio));
ASSERT_OK(demuxer.Initialize());
MediaStream* stream = FindFirstAudioStream(demuxer.streams());
ASSERT_EQ("por", stream->info()->language());
}
TEST_P(PackagerTestBasic, MP4MuxerLanguageWithSubtag) {
ASSERT_NO_FATAL_FAILURE(Remux(GetParam(),
kOutputNone,
kOutputAudio,
kSingleSegment,
kDisableEncryption,
"por-BR"));
Demuxer demuxer(GetFullPath(kOutputAudio));
ASSERT_OK(demuxer.Initialize());
MediaStream* stream = FindFirstAudioStream(demuxer.streams());
ASSERT_EQ("por", stream->info()->language());
}
TEST_P(PackagerTestBasic, GetTrackTypeForEncryption) {
Demuxer demuxer(GetFullPath(GetParam()));
ASSERT_OK(demuxer.Initialize());
MediaStream* video_stream = FindFirstVideoStream(demuxer.streams());
MediaStream* audio_stream = FindFirstAudioStream(demuxer.streams());
// Typical resolution constraints should set the resolution in the SD range
KeySource::TrackType track_type = GetTrackTypeForEncryption(
*video_stream->info(), kMaxSDPixels, kMaxHDPixels, kMaxUHD1Pixels);
ASSERT_EQ(FixedKeySource::GetTrackTypeFromString("SD"), track_type);
// Setting the max SD value to 1 should set the resolution in the HD range
track_type = GetTrackTypeForEncryption(
*video_stream->info(), 1, kMaxHDPixels, kMaxUHD1Pixels);
ASSERT_EQ(FixedKeySource::GetTrackTypeFromString("HD"), track_type);
// Setting the max HD value to 2 should set the resolution in the UHD1 range
track_type = GetTrackTypeForEncryption(
*video_stream->info(), 1, 2, kMaxUHD1Pixels);
ASSERT_EQ(FixedKeySource::GetTrackTypeFromString("UHD1"), track_type);
// Setting the max UHD1 value to 3 should set the resolution in the UHD2 range
track_type = GetTrackTypeForEncryption(
*video_stream->info(), 1, 2, 3);
ASSERT_EQ(FixedKeySource::GetTrackTypeFromString("UHD2"), track_type);
// Audio stream should always set the track_type to AUDIO
track_type = GetTrackTypeForEncryption(
*audio_stream->info(), kMaxSDPixels, kMaxHDPixels, kMaxUHD1Pixels);
ASSERT_EQ(FixedKeySource::GetTrackTypeFromString("AUDIO"), track_type);
}
class PackagerTest : public PackagerTestBasic { class PackagerTest : public PackagerTestBasic {
public: public:
@ -363,15 +258,13 @@ class PackagerTest : public PackagerTestBasic {
kOutputVideo, kOutputVideo,
kOutputNone, kOutputNone,
kSingleSegment, kSingleSegment,
kDisableEncryption, kDisableEncryption));
kNoLanguageOverride));
ASSERT_NO_FATAL_FAILURE(Remux(GetParam(), ASSERT_NO_FATAL_FAILURE(Remux(GetParam(),
kOutputNone, kOutputNone,
kOutputAudio, kOutputAudio,
kSingleSegment, kSingleSegment,
kDisableEncryption, kDisableEncryption));
kNoLanguageOverride));
} }
}; };
@ -382,8 +275,7 @@ TEST_P(PackagerTest, MP4MuxerSingleSegmentUnencryptedVideoAgain) {
kOutputVideo2, kOutputVideo2,
kOutputNone, kOutputNone,
kSingleSegment, kSingleSegment,
kDisableEncryption, kDisableEncryption));
kNoLanguageOverride));
EXPECT_TRUE(ContentsEqual(kOutputVideo, kOutputVideo2)); EXPECT_TRUE(ContentsEqual(kOutputVideo, kOutputVideo2));
} }
@ -394,8 +286,7 @@ TEST_P(PackagerTest, MP4MuxerSingleSegmentUnencryptedAudioAgain) {
kOutputNone, kOutputNone,
kOutputAudio2, kOutputAudio2,
kSingleSegment, kSingleSegment,
kDisableEncryption, kDisableEncryption));
kNoLanguageOverride));
EXPECT_TRUE(ContentsEqual(kOutputAudio, kOutputAudio2)); EXPECT_TRUE(ContentsEqual(kOutputAudio, kOutputAudio2));
} }
@ -404,8 +295,7 @@ TEST_P(PackagerTest, MP4MuxerSingleSegmentUnencryptedSeparateAudioVideo) {
kOutputVideo2, kOutputVideo2,
kOutputAudio2, kOutputAudio2,
kSingleSegment, kSingleSegment,
kDisableEncryption, kDisableEncryption));
kNoLanguageOverride));
// Compare the output with single muxer output. They should match. // Compare the output with single muxer output. They should match.
EXPECT_TRUE(ContentsEqual(kOutputVideo, kOutputVideo2)); EXPECT_TRUE(ContentsEqual(kOutputVideo, kOutputVideo2));
@ -417,8 +307,7 @@ TEST_P(PackagerTest, MP4MuxerMultiSegmentsUnencryptedVideo) {
kOutputVideo2, kOutputVideo2,
kOutputNone, kOutputNone,
kMultipleSegments, kMultipleSegments,
kDisableEncryption, kDisableEncryption));
kNoLanguageOverride));
// Find and concatenates the segments. // Find and concatenates the segments.
const std::string kOutputVideoSegmentsCombined = const std::string kOutputVideoSegmentsCombined =
@ -452,8 +341,7 @@ TEST_P(PackagerTest, MP4MuxerMultiSegmentsUnencryptedVideo) {
kOutputVideo2, kOutputVideo2,
kOutputNone, kOutputNone,
kSingleSegment, kSingleSegment,
kDisableEncryption, kDisableEncryption));
kNoLanguageOverride));
EXPECT_TRUE(ContentsEqual(kOutputVideo, kOutputVideo2)); EXPECT_TRUE(ContentsEqual(kOutputVideo, kOutputVideo2));
} }