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:
parent
12c61d6792
commit
8f2cd6da91
|
@ -168,10 +168,6 @@ class RemuxJob : public base::SimpleThread {
|
|||
|
||||
~RemuxJob() override {}
|
||||
|
||||
void AddMuxer(std::unique_ptr<Muxer> mux) {
|
||||
muxers_.push_back(std::move(mux));
|
||||
}
|
||||
|
||||
Demuxer* demuxer() { return demuxer_.get(); }
|
||||
Status status() { return status_; }
|
||||
|
||||
|
@ -182,7 +178,6 @@ class RemuxJob : public base::SimpleThread {
|
|||
}
|
||||
|
||||
std::unique_ptr<Demuxer> demuxer_;
|
||||
std::vector<std::unique_ptr<Muxer>> muxers_;
|
||||
Status status_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(RemuxJob);
|
||||
|
@ -227,15 +222,15 @@ bool StreamInfoToTextMediaInfo(const StreamDescriptor& stream_descriptor,
|
|||
return true;
|
||||
}
|
||||
|
||||
std::unique_ptr<Muxer> CreateOutputMuxer(const MuxerOptions& options,
|
||||
std::shared_ptr<Muxer> CreateOutputMuxer(const MuxerOptions& options,
|
||||
MediaContainerName container) {
|
||||
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) {
|
||||
return std::unique_ptr<Muxer>(new mp2t::TsMuxer(options));
|
||||
return std::shared_ptr<Muxer>(new mp2t::TsMuxer(options));
|
||||
} else {
|
||||
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) {
|
||||
// New remux job needed. Create demux and job thread.
|
||||
std::unique_ptr<Demuxer> demuxer(new Demuxer(stream_iter->input));
|
||||
demuxer->set_dump_stream_info(FLAGS_dump_stream_info);
|
||||
if (FLAGS_enable_widevine_decryption ||
|
||||
FLAGS_enable_fixed_key_decryption) {
|
||||
std::unique_ptr<KeySource> key_source(CreateDecryptionKeySource());
|
||||
|
@ -307,23 +303,15 @@ bool CreateRemuxJobs(const StreamDescriptorList& stream_descriptors,
|
|||
return false;
|
||||
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)));
|
||||
previous_input = stream_iter->input;
|
||||
// Skip setting up muxers if output is not needed.
|
||||
if (stream_iter->output.empty())
|
||||
continue;
|
||||
}
|
||||
DCHECK(!remux_jobs->empty());
|
||||
|
||||
std::unique_ptr<Muxer> muxer(
|
||||
std::shared_ptr<Muxer> muxer(
|
||||
CreateOutputMuxer(stream_muxer_options, stream_iter->output_format));
|
||||
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)
|
||||
muxer->SetMuxerListener(std::move(muxer_listener));
|
||||
|
||||
if (!AddStreamToMuxer(remux_jobs->back()->demuxer()->streams(),
|
||||
stream_iter->stream_selector,
|
||||
stream_iter->language,
|
||||
muxer.get())) {
|
||||
auto* demuxer = remux_jobs->back()->demuxer();
|
||||
const std::string& stream_selector = stream_iter->stream_selector;
|
||||
Status status = demuxer->SetHandler(stream_selector, std::move(muxer));
|
||||
if (!status.ok()) {
|
||||
LOG(ERROR) << "Demuxer::SetHandler failed " << status;
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -17,15 +17,12 @@
|
|||
#include "packager/base/logging.h"
|
||||
#include "packager/base/strings/string_number_conversions.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/playready_key_source.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/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,
|
||||
false,
|
||||
|
@ -38,12 +35,6 @@ DEFINE_bool(dump_stream_info, false, "Dump demuxed stream info.");
|
|||
namespace shaka {
|
||||
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> signer;
|
||||
|
||||
|
@ -196,63 +187,5 @@ bool GetMpdOptions(bool on_demand_profile, MpdOptions* mpd_options) {
|
|||
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 shaka
|
||||
|
|
|
@ -6,13 +6,12 @@
|
|||
//
|
||||
// Packager utility functions.
|
||||
|
||||
#ifndef APP_PACKAGER_UTIL_H_
|
||||
#define APP_PACKAGER_UTIL_H_
|
||||
#ifndef PACKAGER_APP_PACKAGER_UTIL_H_
|
||||
#define PACKAGER_APP_PACKAGER_UTIL_H_
|
||||
|
||||
#include <gflags/gflags.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
DECLARE_bool(dump_stream_info);
|
||||
|
||||
|
@ -23,13 +22,8 @@ struct MpdOptions;
|
|||
namespace media {
|
||||
|
||||
class KeySource;
|
||||
class MediaStream;
|
||||
class Muxer;
|
||||
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
|
||||
/// encryption. Also fetches keys.
|
||||
/// @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.
|
||||
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 shaka
|
||||
|
||||
#endif // APP_PACKAGER_UTIL_H_
|
||||
#endif // PACKAGER_APP_PACKAGER_UTIL_H_
|
||||
|
|
|
@ -92,6 +92,22 @@ class PackagerAppTest(unittest.TestCase):
|
|||
self._DiffGold(self.output[1], 'bear-640x360-v-golden.mp4')
|
||||
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.
|
||||
def testPackageVideoAudioText(self):
|
||||
audio_video_streams = self._GetStreams(['audio', 'video'])
|
||||
|
@ -438,6 +454,7 @@ class PackagerAppTest(unittest.TestCase):
|
|||
|
||||
def _GetStreams(self,
|
||||
stream_descriptors,
|
||||
language_override=None,
|
||||
output_format=None,
|
||||
live=False,
|
||||
test_files=None):
|
||||
|
@ -466,9 +483,6 @@ class PackagerAppTest(unittest.TestCase):
|
|||
'input=%s,stream=%s,init_segment=%s-init.mp4,'
|
||||
'segment_template=%s-$Number$.m4s' %
|
||||
(test_file, stream_descriptor, output_prefix, output_prefix))
|
||||
if output_format:
|
||||
stream += ',format=%s' % output_format
|
||||
streams.append(stream)
|
||||
self.output.append(output_prefix)
|
||||
else:
|
||||
output = '%s.%s' % (
|
||||
|
@ -476,10 +490,12 @@ class PackagerAppTest(unittest.TestCase):
|
|||
self._GetExtension(stream_descriptor, output_format))
|
||||
stream = ('input=%s,stream=%s,output=%s' %
|
||||
(test_file, stream_descriptor, output))
|
||||
if output_format:
|
||||
stream += ',format=%s' % output_format
|
||||
streams.append(stream)
|
||||
self.output.append(output)
|
||||
if output_format:
|
||||
stream += ',format=%s' % output_format
|
||||
if language_override:
|
||||
stream += ',lang=%s' % language_override
|
||||
streams.append(stream)
|
||||
return streams
|
||||
|
||||
def _GetExtension(self, stream_descriptor, output_format):
|
||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -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>
|
|
@ -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>
|
|
@ -6,12 +6,14 @@
|
|||
|
||||
#include "packager/media/base/demuxer.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "packager/base/bind.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/key_source.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/file/file.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
|
||||
// set here is arbitrary though.
|
||||
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 media {
|
||||
|
||||
Demuxer::Demuxer(const std::string& file_name)
|
||||
: file_name_(file_name),
|
||||
media_file_(NULL),
|
||||
init_event_received_(false),
|
||||
container_name_(CONTAINER_UNKNOWN),
|
||||
buffer_(new uint8_t[kBufSize]),
|
||||
cancelled_(false) {
|
||||
}
|
||||
: file_name_(file_name), buffer_(new uint8_t[kBufSize]) {}
|
||||
|
||||
Demuxer::~Demuxer() {
|
||||
if (media_file_)
|
||||
|
@ -51,9 +79,74 @@ void Demuxer::SetKeySource(std::unique_ptr<KeySource> 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(!init_event_received_);
|
||||
DCHECK(!all_streams_ready_);
|
||||
|
||||
LOG(INFO) << "Initialize Demuxer for file '" << file_name_ << "'.";
|
||||
|
||||
|
@ -105,34 +198,65 @@ Status Demuxer::Initialize() {
|
|||
// Handle trailing 'moov'.
|
||||
if (container_name_ == CONTAINER_MOV)
|
||||
static_cast<mp4::MP4MediaParser*>(parser_.get())->LoadMoov(file_name_);
|
||||
|
||||
if (!parser_->Parse(buffer_.get(), bytes_read)) {
|
||||
init_parsing_status_ =
|
||||
Status(error::PARSER_FAILURE, "Cannot parse media file " + file_name_);
|
||||
return Status(error::PARSER_FAILURE,
|
||||
"Cannot parse media file " + file_name_);
|
||||
}
|
||||
|
||||
// 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_;
|
||||
return Status::OK;
|
||||
}
|
||||
|
||||
void Demuxer::ParserInitEvent(
|
||||
const std::vector<std::shared_ptr<StreamInfo>>& stream_infos) {
|
||||
init_event_received_ = true;
|
||||
for (const std::shared_ptr<StreamInfo>& stream_info : stream_infos)
|
||||
streams_.emplace_back(new MediaStream(stream_info, this));
|
||||
}
|
||||
if (dump_stream_info_) {
|
||||
printf("\nFile \"%s\":\n", file_name_.c_str());
|
||||
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,
|
||||
std::shared_ptr<MediaSample> local_sample)
|
||||
: track_id(local_track_id), sample(local_sample) {}
|
||||
Demuxer::QueuedSample::~QueuedSample() {}
|
||||
int base_stream_index = 0;
|
||||
bool video_handler_set =
|
||||
output_handlers().find(kBaseVideoOutputStreamIndex) !=
|
||||
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,
|
||||
const std::shared_ptr<MediaSample>& sample) {
|
||||
if (!init_event_received_) {
|
||||
if (!all_streams_ready_) {
|
||||
if (queued_samples_.size() >= kQueuedSamplesLimit) {
|
||||
LOG(ERROR) << "Queued samples limit reached: " << kQueuedSamplesLimit;
|
||||
return false;
|
||||
|
@ -152,46 +276,19 @@ bool Demuxer::NewSampleEvent(uint32_t track_id,
|
|||
|
||||
bool Demuxer::PushSample(uint32_t track_id,
|
||||
const std::shared_ptr<MediaSample>& sample) {
|
||||
for (const std::unique_ptr<MediaStream>& stream : streams_) {
|
||||
if (track_id == stream->info()->track_id()) {
|
||||
Status status = stream->PushSample(sample);
|
||||
if (!status.ok())
|
||||
LOG(ERROR) << "Demuxer::PushSample failed with " << status;
|
||||
return status.ok();
|
||||
}
|
||||
auto stream_index_iter = track_id_to_stream_index_map_.find(track_id);
|
||||
if (stream_index_iter == track_id_to_stream_index_map_.end()) {
|
||||
LOG(ERROR) << "Track " << track_id << " not found.";
|
||||
return false;
|
||||
}
|
||||
LOG(ERROR) << "Track " << track_id << " not found.";
|
||||
return false;
|
||||
}
|
||||
|
||||
Status Demuxer::Run() {
|
||||
Status status;
|
||||
|
||||
LOG(INFO) << "Demuxer::Run() on file '" << file_name_ << "'.";
|
||||
|
||||
// Start the streams.
|
||||
for (const std::unique_ptr<MediaStream>& stream : streams_) {
|
||||
status = stream->Start(MediaStream::kPush);
|
||||
if (!status.ok())
|
||||
return status;
|
||||
if (stream_index_iter->second == kInvalidStreamIndex)
|
||||
return true;
|
||||
Status status = DispatchMediaSample(stream_index_iter->second, sample);
|
||||
if (!status.ok()) {
|
||||
LOG(ERROR) << "Failed to process sample " << stream_index_iter->second
|
||||
<< " " << status;
|
||||
}
|
||||
|
||||
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;
|
||||
return status.ok();
|
||||
}
|
||||
|
||||
Status Demuxer::Parse() {
|
||||
|
@ -199,11 +296,6 @@ Status Demuxer::Parse() {
|
|||
DCHECK(parser_);
|
||||
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);
|
||||
if (bytes_read == 0) {
|
||||
if (!parser_->Flush())
|
||||
|
@ -219,9 +311,5 @@ Status Demuxer::Parse() {
|
|||
"Cannot parse media file " + file_name_);
|
||||
}
|
||||
|
||||
void Demuxer::Cancel() {
|
||||
cancelled_ = true;
|
||||
}
|
||||
|
||||
} // namespace media
|
||||
} // namespace shaka
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
|
||||
#include "packager/base/compiler_specific.h"
|
||||
#include "packager/media/base/container_names.h"
|
||||
#include "packager/media/base/media_handler.h"
|
||||
#include "packager/media/base/status.h"
|
||||
|
||||
namespace shaka {
|
||||
|
@ -28,7 +29,7 @@ class StreamInfo;
|
|||
|
||||
/// Demuxer is responsible for extracting elementary stream samples from a
|
||||
/// media file, e.g. an ISO BMFF file.
|
||||
class Demuxer {
|
||||
class Demuxer : public MediaHandler {
|
||||
public:
|
||||
/// @param file_name specifies the input source. It uses prefix matching to
|
||||
/// create a proper File object. The user can extend File to support
|
||||
|
@ -42,36 +43,52 @@ class Demuxer {
|
|||
/// demuxed.
|
||||
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
|
||||
/// the Data to Muxer until Eof.
|
||||
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
|
||||
/// status of type CANCELLED.
|
||||
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
|
||||
/// is not initialized.
|
||||
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:
|
||||
Demuxer(const Demuxer&) = delete;
|
||||
Demuxer& operator=(const Demuxer&) = delete;
|
||||
|
@ -84,6 +101,11 @@ class Demuxer {
|
|||
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.
|
||||
void ParserInitEvent(const std::vector<std::shared_ptr<StreamInfo>>& streams);
|
||||
// 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,
|
||||
const std::shared_ptr<MediaSample>& sample);
|
||||
|
||||
// Read from the source and send it to the parser.
|
||||
Status Parse();
|
||||
|
||||
std::string file_name_;
|
||||
File* media_file_;
|
||||
bool init_event_received_;
|
||||
Status init_parsing_status_;
|
||||
File* media_file_ = nullptr;
|
||||
// A stream is considered ready after receiving the stream info.
|
||||
bool all_streams_ready_ = false;
|
||||
// Queued samples received in NewSampleEvent() before ParserInitEvent().
|
||||
std::deque<QueuedSample> queued_samples_;
|
||||
std::unique_ptr<MediaParser> parser_;
|
||||
std::vector<std::unique_ptr<MediaStream>> streams_;
|
||||
MediaContainerName container_name_;
|
||||
// TrackId -> StreamIndex map.
|
||||
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<KeySource> key_source_;
|
||||
bool cancelled_;
|
||||
bool cancelled_ = false;
|
||||
// Whether to dump stream info when it is received.
|
||||
bool dump_stream_info_ = false;
|
||||
};
|
||||
|
||||
} // namespace media
|
||||
|
|
|
@ -59,8 +59,6 @@
|
|||
'media_parser.h',
|
||||
'media_sample.cc',
|
||||
'media_sample.h',
|
||||
'media_stream.cc',
|
||||
'media_stream.h',
|
||||
'muxer.cc',
|
||||
'muxer.h',
|
||||
'muxer_options.cc',
|
||||
|
|
|
@ -172,6 +172,10 @@ class MediaHandler {
|
|||
|
||||
int num_input_streams() const { return num_input_streams_; }
|
||||
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:
|
||||
MediaHandler(const MediaHandler&) = delete;
|
||||
|
|
|
@ -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
|
|
@ -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_
|
|
@ -10,14 +10,12 @@
|
|||
|
||||
#include "packager/media/base/fourccs.h"
|
||||
#include "packager/media/base/media_sample.h"
|
||||
#include "packager/media/base/media_stream.h"
|
||||
|
||||
namespace shaka {
|
||||
namespace media {
|
||||
|
||||
Muxer::Muxer(const MuxerOptions& options)
|
||||
: options_(options),
|
||||
initialized_(false),
|
||||
encryption_key_source_(NULL),
|
||||
max_sd_pixels_(0),
|
||||
max_hd_pixels_(0),
|
||||
|
@ -47,46 +45,6 @@ void Muxer::SetKeySource(KeySource* encryption_key_source,
|
|||
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() {
|
||||
cancelled_ = true;
|
||||
}
|
||||
|
@ -100,26 +58,21 @@ void Muxer::SetProgressListener(
|
|||
progress_listener_ = std::move(progress_listener);
|
||||
}
|
||||
|
||||
Status Muxer::AddSample(const MediaStream* stream,
|
||||
std::shared_ptr<MediaSample> sample) {
|
||||
DCHECK(std::find(streams_.begin(), streams_.end(), stream) != streams_.end());
|
||||
|
||||
if (!initialized_) {
|
||||
Status status = Initialize();
|
||||
if (!status.ok())
|
||||
return status;
|
||||
initialized_ = true;
|
||||
Status Muxer::Process(std::unique_ptr<StreamData> stream_data) {
|
||||
Status status;
|
||||
switch (stream_data->stream_data_type) {
|
||||
case StreamDataType::kStreamInfo:
|
||||
streams_.push_back(std::move(stream_data->stream_info));
|
||||
return InitializeMuxer();
|
||||
case StreamDataType::kMediaSample:
|
||||
return DoAddSample(stream_data->media_sample);
|
||||
default:
|
||||
VLOG(3) << "Stream data type "
|
||||
<< static_cast<int>(stream_data->stream_data_type) << " ignored.";
|
||||
break;
|
||||
}
|
||||
if (sample->end_of_stream()) {
|
||||
// EOS sample should be sent only when the sample was pushed from Demuxer
|
||||
// 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);
|
||||
// No dispatch for muxer.
|
||||
return Status::OK;
|
||||
}
|
||||
|
||||
} // namespace media
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
#include "packager/base/time/clock.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/status.h"
|
||||
#include "packager/media/event/muxer_listener.h"
|
||||
|
@ -29,7 +30,7 @@ class MediaStream;
|
|||
/// Muxer is responsible for taking elementary stream samples and producing
|
||||
/// media containers. An optional KeySource can be provided to Muxer
|
||||
/// to generate encrypted outputs.
|
||||
class Muxer {
|
||||
class Muxer : public MediaHandler {
|
||||
public:
|
||||
explicit Muxer(const MuxerOptions& options);
|
||||
virtual ~Muxer();
|
||||
|
@ -65,12 +66,6 @@ class Muxer {
|
|||
double crypto_period_duration_in_seconds,
|
||||
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
|
||||
/// status of type CANCELLED.
|
||||
void Cancel();
|
||||
|
@ -83,7 +78,9 @@ class Muxer {
|
|||
/// @param progress_listener should not be NULL.
|
||||
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.
|
||||
/// The injected clock will be used to generate the creation time-stamp and
|
||||
|
@ -96,6 +93,13 @@ class Muxer {
|
|||
}
|
||||
|
||||
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_; }
|
||||
KeySource* encryption_key_source() {
|
||||
return encryption_key_source_;
|
||||
|
@ -113,25 +117,17 @@ class Muxer {
|
|||
FourCC protection_scheme() const { return protection_scheme_; }
|
||||
|
||||
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.
|
||||
virtual Status Initialize() = 0;
|
||||
virtual Status InitializeMuxer() = 0;
|
||||
|
||||
// Final clean up.
|
||||
virtual Status Finalize() = 0;
|
||||
|
||||
// AddSample implementation.
|
||||
virtual Status DoAddSample(const MediaStream* stream,
|
||||
std::shared_ptr<MediaSample> sample) = 0;
|
||||
virtual Status DoAddSample(std::shared_ptr<MediaSample> sample) = 0;
|
||||
|
||||
MuxerOptions options_;
|
||||
bool initialized_;
|
||||
std::vector<MediaStream*> streams_;
|
||||
std::vector<std::shared_ptr<StreamInfo>> streams_;
|
||||
KeySource* encryption_key_source_;
|
||||
uint32_t max_sd_pixels_;
|
||||
uint32_t max_hd_pixels_;
|
||||
|
|
|
@ -17,15 +17,14 @@ const uint32_t kTsTimescale = 90000;
|
|||
TsMuxer::TsMuxer(const MuxerOptions& muxer_options) : Muxer(muxer_options) {}
|
||||
TsMuxer::~TsMuxer() {}
|
||||
|
||||
Status TsMuxer::Initialize() {
|
||||
Status TsMuxer::InitializeMuxer() {
|
||||
if (streams().size() > 1u)
|
||||
return Status(error::MUXER_FAILURE, "Cannot handle more than one streams.");
|
||||
|
||||
segmenter_.reset(new TsSegmenter(options(), muxer_listener()));
|
||||
Status status =
|
||||
segmenter_->Initialize(*streams()[0]->info(), encryption_key_source(),
|
||||
max_sd_pixels(), max_hd_pixels(),
|
||||
max_uhd1_pixels(), clear_lead_in_seconds());
|
||||
Status status = segmenter_->Initialize(
|
||||
*streams()[0], encryption_key_source(), max_sd_pixels(), max_hd_pixels(),
|
||||
max_uhd1_pixels(), clear_lead_in_seconds());
|
||||
FireOnMediaStartEvent();
|
||||
return status;
|
||||
}
|
||||
|
@ -35,16 +34,15 @@ Status TsMuxer::Finalize() {
|
|||
return segmenter_->Finalize();
|
||||
}
|
||||
|
||||
Status TsMuxer::DoAddSample(const MediaStream* stream,
|
||||
std::shared_ptr<MediaSample> sample) {
|
||||
Status TsMuxer::DoAddSample(std::shared_ptr<MediaSample> sample) {
|
||||
return segmenter_->AddSample(sample);
|
||||
}
|
||||
|
||||
void TsMuxer::FireOnMediaStartEvent() {
|
||||
if (!muxer_listener())
|
||||
return;
|
||||
muxer_listener()->OnMediaStart(options(), *streams().front()->info(),
|
||||
kTsTimescale, MuxerListener::kContainerWebM);
|
||||
muxer_listener()->OnMediaStart(options(), *streams().front(), kTsTimescale,
|
||||
MuxerListener::kContainerWebM);
|
||||
}
|
||||
|
||||
void TsMuxer::FireOnMediaEndEvent() {
|
||||
|
|
|
@ -24,10 +24,9 @@ class TsMuxer : public Muxer {
|
|||
|
||||
private:
|
||||
// Muxer implementation.
|
||||
Status Initialize() override;
|
||||
Status InitializeMuxer() override;
|
||||
Status Finalize() override;
|
||||
Status DoAddSample(const MediaStream* stream,
|
||||
std::shared_ptr<MediaSample> sample) override;
|
||||
Status DoAddSample(std::shared_ptr<MediaSample> sample) override;
|
||||
|
||||
void FireOnMediaStartEvent();
|
||||
void FireOnMediaEndEvent();
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
#define PACKAGER_MEDIA_FORMATS_MP2T_TS_SEGMENTER_H_
|
||||
|
||||
#include <memory>
|
||||
#include "packager/media/base/media_stream.h"
|
||||
#include "packager/media/base/muxer_options.h"
|
||||
#include "packager/media/base/status.h"
|
||||
#include "packager/media/file/file.h"
|
||||
|
|
|
@ -12,7 +12,6 @@
|
|||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "packager/media/base/media_stream.h"
|
||||
#include "packager/media/file/file.h"
|
||||
#include "packager/media/file/file_closer.h"
|
||||
#include "packager/media/formats/mp2t/continuity_counter.h"
|
||||
|
@ -21,6 +20,9 @@
|
|||
|
||||
namespace shaka {
|
||||
namespace media {
|
||||
|
||||
class StreamInfo;
|
||||
|
||||
namespace mp2t {
|
||||
|
||||
/// This class takes PesPackets, encapsulates them into TS packets, and write
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
#include "packager/media/base/fourccs.h"
|
||||
#include "packager/media/base/key_source.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/codecs/es_descriptor.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() {}
|
||||
|
||||
Status MP4Muxer::Initialize() {
|
||||
Status MP4Muxer::InitializeMuxer() {
|
||||
DCHECK(!streams().empty());
|
||||
|
||||
std::unique_ptr<FileType> ftyp(new FileType);
|
||||
|
@ -91,10 +90,9 @@ Status MP4Muxer::Initialize() {
|
|||
ftyp->major_brand = FOURCC_dash;
|
||||
ftyp->compatible_brands.push_back(FOURCC_iso6);
|
||||
ftyp->compatible_brands.push_back(FOURCC_mp41);
|
||||
if (streams().size() == 1 &&
|
||||
streams()[0]->info()->stream_type() == kStreamVideo) {
|
||||
if (streams().size() == 1 && streams()[0]->stream_type() == kStreamVideo) {
|
||||
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)
|
||||
ftyp->compatible_brands.push_back(codec_fourcc);
|
||||
}
|
||||
|
@ -115,22 +113,18 @@ Status MP4Muxer::Initialize() {
|
|||
trex.track_id = trak.header.track_id;
|
||||
trex.default_sample_description_index = 1;
|
||||
|
||||
switch (streams()[i]->info()->stream_type()) {
|
||||
switch (streams()[i]->stream_type()) {
|
||||
case kStreamVideo:
|
||||
GenerateVideoTrak(
|
||||
static_cast<VideoStreamInfo*>(streams()[i]->info().get()),
|
||||
&trak,
|
||||
i + 1);
|
||||
GenerateVideoTrak(static_cast<VideoStreamInfo*>(streams()[i].get()),
|
||||
&trak, i + 1);
|
||||
break;
|
||||
case kStreamAudio:
|
||||
GenerateAudioTrak(
|
||||
static_cast<AudioStreamInfo*>(streams()[i]->info().get()),
|
||||
&trak,
|
||||
i + 1);
|
||||
GenerateAudioTrak(static_cast<AudioStreamInfo*>(streams()[i].get()),
|
||||
&trak, i + 1);
|
||||
break;
|
||||
default:
|
||||
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;
|
||||
}
|
||||
|
||||
Status MP4Muxer::DoAddSample(const MediaStream* stream,
|
||||
std::shared_ptr<MediaSample> sample) {
|
||||
Status MP4Muxer::DoAddSample(std::shared_ptr<MediaSample> sample) {
|
||||
DCHECK(segmenter_);
|
||||
return segmenter_->AddSample(stream, sample);
|
||||
return segmenter_->AddSample(*streams()[0], sample);
|
||||
}
|
||||
|
||||
void MP4Muxer::InitializeTrak(const StreamInfo* info, Track* trak) {
|
||||
|
@ -355,9 +348,7 @@ void MP4Muxer::FireOnMediaStartEvent() {
|
|||
DCHECK(!streams().empty()) << "Media started without a stream.";
|
||||
|
||||
const uint32_t timescale = segmenter_->GetReferenceTimeScale();
|
||||
muxer_listener()->OnMediaStart(options(),
|
||||
*streams().front()->info(),
|
||||
timescale,
|
||||
muxer_listener()->OnMediaStart(options(), *streams().front(), timescale,
|
||||
MuxerListener::kContainerMp4);
|
||||
}
|
||||
|
||||
|
|
|
@ -35,10 +35,9 @@ class MP4Muxer : public Muxer {
|
|||
|
||||
private:
|
||||
// Muxer implementation overrides.
|
||||
Status Initialize() override;
|
||||
Status InitializeMuxer() override;
|
||||
Status Finalize() override;
|
||||
Status DoAddSample(const MediaStream* stream,
|
||||
std::shared_ptr<MediaSample> sample) override;
|
||||
Status DoAddSample(std::shared_ptr<MediaSample> sample) override;
|
||||
|
||||
// Generate Audio/Video Track box.
|
||||
void InitializeTrak(const StreamInfo* info, Track* trak);
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
#include "packager/base/strings/string_number_conversions.h"
|
||||
#include "packager/base/strings/string_util.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_util.h"
|
||||
#include "packager/media/event/muxer_listener.h"
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
#include "packager/media/base/buffer_writer.h"
|
||||
#include "packager/media/base/key_source.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_util.h"
|
||||
#include "packager/media/base/video_stream_info.h"
|
||||
|
@ -162,16 +161,17 @@ Segmenter::Segmenter(const MuxerOptions& options,
|
|||
|
||||
Segmenter::~Segmenter() {}
|
||||
|
||||
Status Segmenter::Initialize(const std::vector<MediaStream*>& streams,
|
||||
MuxerListener* muxer_listener,
|
||||
ProgressListener* progress_listener,
|
||||
KeySource* encryption_key_source,
|
||||
uint32_t max_sd_pixels,
|
||||
uint32_t max_hd_pixels,
|
||||
uint32_t max_uhd1_pixels,
|
||||
double clear_lead_in_seconds,
|
||||
double crypto_period_duration_in_seconds,
|
||||
FourCC protection_scheme) {
|
||||
Status Segmenter::Initialize(
|
||||
const std::vector<std::shared_ptr<StreamInfo>>& streams,
|
||||
MuxerListener* muxer_listener,
|
||||
ProgressListener* progress_listener,
|
||||
KeySource* encryption_key_source,
|
||||
uint32_t max_sd_pixels,
|
||||
uint32_t max_hd_pixels,
|
||||
uint32_t max_uhd1_pixels,
|
||||
double clear_lead_in_seconds,
|
||||
double crypto_period_duration_in_seconds,
|
||||
FourCC protection_scheme) {
|
||||
DCHECK_LT(0u, streams.size());
|
||||
muxer_listener_ = muxer_listener;
|
||||
progress_listener_ = progress_listener;
|
||||
|
@ -184,22 +184,19 @@ Status Segmenter::Initialize(const std::vector<MediaStream*>& streams,
|
|||
const bool kInitialEncryptionInfo = true;
|
||||
|
||||
for (uint32_t i = 0; i < streams.size(); ++i) {
|
||||
stream_map_[streams[i]] = i;
|
||||
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).
|
||||
if (sidx_->reference_id == 0)
|
||||
sidx_->reference_id = i + 1;
|
||||
}
|
||||
if (!encryption_key_source) {
|
||||
fragmenters_[i].reset(
|
||||
new Fragmenter(streams[i]->info(), &moof_->tracks[i]));
|
||||
fragmenters_[i].reset(new Fragmenter(streams[i], &moof_->tracks[i]));
|
||||
continue;
|
||||
}
|
||||
|
||||
KeySource::TrackType track_type =
|
||||
GetTrackTypeForEncryption(*streams[i]->info(), max_sd_pixels,
|
||||
max_hd_pixels, max_uhd1_pixels);
|
||||
KeySource::TrackType track_type = GetTrackTypeForEncryption(
|
||||
*streams[i], max_sd_pixels, max_hd_pixels, max_uhd1_pixels);
|
||||
SampleDescription& description =
|
||||
moov_->tracks[i].media.information.sample_table.description;
|
||||
ProtectionPattern pattern =
|
||||
|
@ -224,12 +221,11 @@ Status Segmenter::Initialize(const std::vector<MediaStream*>& streams,
|
|||
}
|
||||
|
||||
fragmenters_[i].reset(new KeyRotationFragmenter(
|
||||
moof_.get(), streams[i]->info(), &moof_->tracks[i],
|
||||
encryption_key_source, track_type,
|
||||
crypto_period_duration_in_seconds * streams[i]->info()->time_scale(),
|
||||
clear_lead_in_seconds * streams[i]->info()->time_scale(),
|
||||
protection_scheme, pattern.crypt_byte_block, pattern.skip_byte_block,
|
||||
muxer_listener_));
|
||||
moof_.get(), streams[i], &moof_->tracks[i], encryption_key_source,
|
||||
track_type,
|
||||
crypto_period_duration_in_seconds * streams[i]->time_scale(),
|
||||
clear_lead_in_seconds * streams[i]->time_scale(), protection_scheme,
|
||||
pattern.crypt_byte_block, pattern.skip_byte_block, muxer_listener_));
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -262,10 +258,9 @@ Status Segmenter::Initialize(const std::vector<MediaStream*>& streams,
|
|||
}
|
||||
|
||||
fragmenters_[i].reset(new EncryptingFragmenter(
|
||||
streams[i]->info(), &moof_->tracks[i], std::move(encryption_key),
|
||||
clear_lead_in_seconds * streams[i]->info()->time_scale(),
|
||||
protection_scheme, pattern.crypt_byte_block, pattern.skip_byte_block,
|
||||
muxer_listener_));
|
||||
streams[i], &moof_->tracks[i], std::move(encryption_key),
|
||||
clear_lead_in_seconds * streams[i]->time_scale(), protection_scheme,
|
||||
pattern.crypt_byte_block, pattern.skip_byte_block, muxer_listener_));
|
||||
}
|
||||
|
||||
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.
|
||||
if (sidx_->reference_id == 0)
|
||||
sidx_->reference_id = 1;
|
||||
sidx_->timescale = streams[GetReferenceStreamId()]->info()->time_scale();
|
||||
sidx_->timescale = streams[GetReferenceStreamId()]->time_scale();
|
||||
|
||||
// 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.
|
||||
moov_->header.timescale = sidx_->timescale;
|
||||
|
@ -320,12 +315,10 @@ Status Segmenter::Finalize() {
|
|||
return DoFinalize();
|
||||
}
|
||||
|
||||
Status Segmenter::AddSample(const MediaStream* stream,
|
||||
Status Segmenter::AddSample(const StreamInfo& stream_info,
|
||||
std::shared_ptr<MediaSample> sample) {
|
||||
// Find the fragmenter for this stream.
|
||||
DCHECK(stream);
|
||||
DCHECK(stream_map_.find(stream) != stream_map_.end());
|
||||
uint32_t stream_id = stream_map_[stream];
|
||||
// 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.
|
||||
|
@ -341,14 +334,14 @@ Status Segmenter::AddSample(const MediaStream* stream,
|
|||
|
||||
bool finalize_fragment = false;
|
||||
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) {
|
||||
finalize_fragment = true;
|
||||
}
|
||||
}
|
||||
bool finalize_segment = false;
|
||||
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) {
|
||||
finalize_segment = true;
|
||||
finalize_fragment = true;
|
||||
|
|
|
@ -23,9 +23,9 @@ struct MuxerOptions;
|
|||
class BufferWriter;
|
||||
class KeySource;
|
||||
class MediaSample;
|
||||
class MediaStream;
|
||||
class MuxerListener;
|
||||
class ProgressListener;
|
||||
class StreamInfo;
|
||||
|
||||
namespace mp4 {
|
||||
|
||||
|
@ -69,7 +69,7 @@ class Segmenter {
|
|||
/// @param protection_scheme specifies the protection scheme: 'cenc', 'cens',
|
||||
/// 'cbc1', 'cbcs'.
|
||||
/// @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,
|
||||
ProgressListener* progress_listener,
|
||||
KeySource* encryption_key_source,
|
||||
|
@ -85,11 +85,9 @@ class Segmenter {
|
|||
Status Finalize();
|
||||
|
||||
/// 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.
|
||||
/// @return OK on success, an error status otherwise.
|
||||
Status AddSample(const MediaStream* stream,
|
||||
Status AddSample(const StreamInfo& stream_Info,
|
||||
std::shared_ptr<MediaSample> sample);
|
||||
|
||||
/// @return true if there is an initialization range, while setting @a offset
|
||||
|
@ -145,7 +143,6 @@ class Segmenter {
|
|||
std::unique_ptr<SegmentIndex> sidx_;
|
||||
std::vector<std::unique_ptr<Fragmenter>> fragmenters_;
|
||||
std::vector<uint64_t> segment_durations_;
|
||||
std::map<const MediaStream*, uint32_t> stream_map_;
|
||||
MuxerListener* muxer_listener_;
|
||||
ProgressListener* progress_listener_;
|
||||
uint64_t progress_target_;
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
#include <algorithm>
|
||||
|
||||
#include "packager/media/base/buffer_writer.h"
|
||||
#include "packager/media/base/media_stream.h"
|
||||
#include "packager/media/base/muxer_options.h"
|
||||
#include "packager/media/event/muxer_listener.h"
|
||||
#include "packager/media/event/progress_listener.h"
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
|
||||
#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_util.h"
|
||||
#include "packager/media/base/stream_info.h"
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
#include "packager/base/time/time.h"
|
||||
#include "packager/media/base/audio_stream_info.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_util.h"
|
||||
#include "packager/media/base/stream_info.h"
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
#include <algorithm>
|
||||
|
||||
#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/stream_info.h"
|
||||
#include "packager/media/file/file_util.h"
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
|
||||
#include "packager/media/base/fourccs.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/formats/webm/mkv_writer.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() {}
|
||||
|
||||
Status WebMMuxer::Initialize() {
|
||||
Status WebMMuxer::InitializeMuxer() {
|
||||
CHECK_EQ(streams().size(), 1U);
|
||||
|
||||
if (crypto_period_duration_in_seconds() > 0) {
|
||||
|
@ -50,7 +49,7 @@ Status WebMMuxer::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(),
|
||||
max_hd_pixels(), max_uhd1_pixels(), clear_lead_in_seconds());
|
||||
|
||||
|
@ -73,10 +72,8 @@ Status WebMMuxer::Finalize() {
|
|||
return Status::OK;
|
||||
}
|
||||
|
||||
Status WebMMuxer::DoAddSample(const MediaStream* stream,
|
||||
std::shared_ptr<MediaSample> sample) {
|
||||
Status WebMMuxer::DoAddSample(std::shared_ptr<MediaSample> sample) {
|
||||
DCHECK(segmenter_);
|
||||
DCHECK(stream == streams()[0]);
|
||||
return segmenter_->AddSample(sample);
|
||||
}
|
||||
|
||||
|
@ -86,9 +83,9 @@ void WebMMuxer::FireOnMediaStartEvent() {
|
|||
|
||||
DCHECK(!streams().empty()) << "Media started without a stream.";
|
||||
|
||||
const uint32_t timescale = streams().front()->info()->time_scale();
|
||||
muxer_listener()->OnMediaStart(options(), *streams().front()->info(),
|
||||
timescale, MuxerListener::kContainerWebM);
|
||||
const uint32_t timescale = streams().front()->time_scale();
|
||||
muxer_listener()->OnMediaStart(options(), *streams().front(), timescale,
|
||||
MuxerListener::kContainerWebM);
|
||||
}
|
||||
|
||||
void WebMMuxer::FireOnMediaEndEvent() {
|
||||
|
|
|
@ -24,10 +24,9 @@ class WebMMuxer : public Muxer {
|
|||
|
||||
private:
|
||||
// Muxer implementation overrides.
|
||||
Status Initialize() override;
|
||||
Status InitializeMuxer() override;
|
||||
Status Finalize() override;
|
||||
Status DoAddSample(const MediaStream* stream,
|
||||
std::shared_ptr<MediaSample> sample) override;
|
||||
Status DoAddSample(std::shared_ptr<MediaSample> sample) override;
|
||||
|
||||
void FireOnMediaStartEvent();
|
||||
void FireOnMediaEndEvent();
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
#include "packager/media/base/demuxer.h"
|
||||
#include "packager/media/base/fixed_key_source.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_util.h"
|
||||
#include "packager/media/base/stream_info.h"
|
||||
|
@ -50,7 +49,6 @@ const bool kSingleSegment = true;
|
|||
const bool kMultipleSegments = false;
|
||||
const bool kEnableEncryption = true;
|
||||
const bool kDisableEncryption = false;
|
||||
const char kNoLanguageOverride[] = "";
|
||||
|
||||
// Encryption constants.
|
||||
const char kKeyIdHex[] = "e5007e6e9dcd5ac095202ed3758382cd";
|
||||
|
@ -63,24 +61,6 @@ const uint32_t kMaxSDPixels = 640 * 480;
|
|||
const uint32_t kMaxHDPixels = 1920 * 1080;
|
||||
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
|
||||
|
||||
class FakeClock : public base::Clock {
|
||||
|
@ -114,8 +94,7 @@ class PackagerTestBasic : public ::testing::TestWithParam<const char*> {
|
|||
const std::string& video_output,
|
||||
const std::string& audio_output,
|
||||
bool single_segment,
|
||||
bool enable_encryption,
|
||||
const std::string& override_language);
|
||||
bool enable_encryption);
|
||||
|
||||
void Decrypt(const std::string& input,
|
||||
const std::string& video_output,
|
||||
|
@ -157,59 +136,46 @@ void PackagerTestBasic::Remux(const std::string& input,
|
|||
const std::string& video_output,
|
||||
const std::string& audio_output,
|
||||
bool single_segment,
|
||||
bool enable_encryption,
|
||||
const std::string& language_override) {
|
||||
bool enable_encryption) {
|
||||
CHECK(!video_output.empty() || !audio_output.empty());
|
||||
|
||||
Demuxer demuxer(GetFullPath(input));
|
||||
ASSERT_OK(demuxer.Initialize());
|
||||
|
||||
std::unique_ptr<KeySource> encryption_key_source(
|
||||
FixedKeySource::CreateFromHexStrings(kKeyIdHex, kKeyHex, "", ""));
|
||||
DCHECK(encryption_key_source);
|
||||
|
||||
std::unique_ptr<Muxer> muxer_video;
|
||||
std::shared_ptr<Muxer> muxer_video;
|
||||
if (!video_output.empty()) {
|
||||
muxer_video.reset(
|
||||
new mp4::MP4Muxer(SetupOptions(video_output, single_segment)));
|
||||
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) {
|
||||
muxer_video->SetKeySource(encryption_key_source.get(),
|
||||
kMaxSDPixels, kMaxHDPixels,
|
||||
kMaxUHD1Pixels, kClearLeadInSeconds,
|
||||
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()) {
|
||||
muxer_audio.reset(
|
||||
new mp4::MP4Muxer(SetupOptions(audio_output, single_segment)));
|
||||
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) {
|
||||
muxer_audio->SetKeySource(encryption_key_source.get(),
|
||||
kMaxSDPixels, kMaxHDPixels,
|
||||
kMaxUHD1Pixels, kClearLeadInSeconds,
|
||||
kCryptoDurationInSeconds, FOURCC_cenc);
|
||||
}
|
||||
ASSERT_OK(demuxer.SetHandler("audio", muxer_audio));
|
||||
}
|
||||
|
||||
ASSERT_OK(demuxer.Initialize());
|
||||
// Start remuxing process.
|
||||
ASSERT_OK(demuxer.Run());
|
||||
}
|
||||
|
@ -224,25 +190,20 @@ void PackagerTestBasic::Decrypt(const std::string& input,
|
|||
FixedKeySource::CreateFromHexStrings(kKeyIdHex, kKeyHex, "", ""));
|
||||
ASSERT_TRUE(decryption_key_source);
|
||||
demuxer.SetKeySource(std::move(decryption_key_source));
|
||||
ASSERT_OK(demuxer.Initialize());
|
||||
|
||||
std::unique_ptr<Muxer> muxer;
|
||||
MediaStream* stream(NULL);
|
||||
std::shared_ptr<Muxer> muxer;
|
||||
if (!video_output.empty()) {
|
||||
muxer.reset(
|
||||
new mp4::MP4Muxer(SetupOptions(video_output, true)));
|
||||
stream = FindFirstVideoStream(demuxer.streams());
|
||||
}
|
||||
if (!audio_output.empty()) {
|
||||
muxer.reset(
|
||||
new mp4::MP4Muxer(SetupOptions(audio_output, true)));
|
||||
stream = FindFirstAudioStream(demuxer.streams());
|
||||
}
|
||||
ASSERT_TRUE(muxer);
|
||||
ASSERT_TRUE(stream != NULL);
|
||||
ASSERT_TRUE(stream->info()->is_encrypted());
|
||||
muxer->set_clock(&fake_clock_);
|
||||
muxer->AddStream(stream);
|
||||
ASSERT_OK(demuxer.SetHandler("0", muxer));
|
||||
ASSERT_OK(demuxer.Initialize());
|
||||
|
||||
ASSERT_OK(demuxer.Run());
|
||||
}
|
||||
|
@ -252,8 +213,7 @@ TEST_P(PackagerTestBasic, MP4MuxerSingleSegmentUnencryptedVideo) {
|
|||
kOutputVideo,
|
||||
kOutputNone,
|
||||
kSingleSegment,
|
||||
kDisableEncryption,
|
||||
kNoLanguageOverride));
|
||||
kDisableEncryption));
|
||||
}
|
||||
|
||||
TEST_P(PackagerTestBasic, MP4MuxerSingleSegmentUnencryptedAudio) {
|
||||
|
@ -261,8 +221,7 @@ TEST_P(PackagerTestBasic, MP4MuxerSingleSegmentUnencryptedAudio) {
|
|||
kOutputNone,
|
||||
kOutputAudio,
|
||||
kSingleSegment,
|
||||
kDisableEncryption,
|
||||
kNoLanguageOverride));
|
||||
kDisableEncryption));
|
||||
}
|
||||
|
||||
TEST_P(PackagerTestBasic, MP4MuxerSingleSegmentEncryptedVideo) {
|
||||
|
@ -270,8 +229,7 @@ TEST_P(PackagerTestBasic, MP4MuxerSingleSegmentEncryptedVideo) {
|
|||
kOutputVideo,
|
||||
kOutputNone,
|
||||
kSingleSegment,
|
||||
kEnableEncryption,
|
||||
kNoLanguageOverride));
|
||||
kEnableEncryption));
|
||||
|
||||
ASSERT_NO_FATAL_FAILURE(Decrypt(kOutputVideo,
|
||||
kOutputVideo2,
|
||||
|
@ -283,76 +241,13 @@ TEST_P(PackagerTestBasic, MP4MuxerSingleSegmentEncryptedAudio) {
|
|||
kOutputNone,
|
||||
kOutputAudio,
|
||||
kSingleSegment,
|
||||
kEnableEncryption,
|
||||
kNoLanguageOverride));
|
||||
kEnableEncryption));
|
||||
|
||||
ASSERT_NO_FATAL_FAILURE(Decrypt(kOutputAudio,
|
||||
kOutputNone,
|
||||
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 {
|
||||
public:
|
||||
|
@ -363,15 +258,13 @@ class PackagerTest : public PackagerTestBasic {
|
|||
kOutputVideo,
|
||||
kOutputNone,
|
||||
kSingleSegment,
|
||||
kDisableEncryption,
|
||||
kNoLanguageOverride));
|
||||
kDisableEncryption));
|
||||
|
||||
ASSERT_NO_FATAL_FAILURE(Remux(GetParam(),
|
||||
kOutputNone,
|
||||
kOutputAudio,
|
||||
kSingleSegment,
|
||||
kDisableEncryption,
|
||||
kNoLanguageOverride));
|
||||
kDisableEncryption));
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -382,8 +275,7 @@ TEST_P(PackagerTest, MP4MuxerSingleSegmentUnencryptedVideoAgain) {
|
|||
kOutputVideo2,
|
||||
kOutputNone,
|
||||
kSingleSegment,
|
||||
kDisableEncryption,
|
||||
kNoLanguageOverride));
|
||||
kDisableEncryption));
|
||||
EXPECT_TRUE(ContentsEqual(kOutputVideo, kOutputVideo2));
|
||||
}
|
||||
|
||||
|
@ -394,8 +286,7 @@ TEST_P(PackagerTest, MP4MuxerSingleSegmentUnencryptedAudioAgain) {
|
|||
kOutputNone,
|
||||
kOutputAudio2,
|
||||
kSingleSegment,
|
||||
kDisableEncryption,
|
||||
kNoLanguageOverride));
|
||||
kDisableEncryption));
|
||||
EXPECT_TRUE(ContentsEqual(kOutputAudio, kOutputAudio2));
|
||||
}
|
||||
|
||||
|
@ -404,8 +295,7 @@ TEST_P(PackagerTest, MP4MuxerSingleSegmentUnencryptedSeparateAudioVideo) {
|
|||
kOutputVideo2,
|
||||
kOutputAudio2,
|
||||
kSingleSegment,
|
||||
kDisableEncryption,
|
||||
kNoLanguageOverride));
|
||||
kDisableEncryption));
|
||||
|
||||
// Compare the output with single muxer output. They should match.
|
||||
EXPECT_TRUE(ContentsEqual(kOutputVideo, kOutputVideo2));
|
||||
|
@ -417,8 +307,7 @@ TEST_P(PackagerTest, MP4MuxerMultiSegmentsUnencryptedVideo) {
|
|||
kOutputVideo2,
|
||||
kOutputNone,
|
||||
kMultipleSegments,
|
||||
kDisableEncryption,
|
||||
kNoLanguageOverride));
|
||||
kDisableEncryption));
|
||||
|
||||
// Find and concatenates the segments.
|
||||
const std::string kOutputVideoSegmentsCombined =
|
||||
|
@ -452,8 +341,7 @@ TEST_P(PackagerTest, MP4MuxerMultiSegmentsUnencryptedVideo) {
|
|||
kOutputVideo2,
|
||||
kOutputNone,
|
||||
kSingleSegment,
|
||||
kDisableEncryption,
|
||||
kNoLanguageOverride));
|
||||
kDisableEncryption));
|
||||
EXPECT_TRUE(ContentsEqual(kOutputVideo, kOutputVideo2));
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue