Support for multiple audio/video tracks in a file
Remove --audio and --video command line options and replaced with --stream. User can use --stream=video (default) to pull the first video track from the source or --stream=audio to pull the first audio track. The user may also use --stream={stream_id}, 0-based stream id to pull the stream, e.g. --stream=0 for the first stream. Change-Id: Ie1f93c2cc80a160a496b1d43ae3a658263d30cfc
This commit is contained in:
parent
af0725a887
commit
fddeb1feb1
|
@ -11,8 +11,11 @@
|
|||
|
||||
#include <gflags/gflags.h>
|
||||
|
||||
DEFINE_bool(audio, false, "Add the first audio stream to muxer.");
|
||||
DEFINE_bool(video, false, "Add the first video stream to muxer.");
|
||||
DEFINE_string(stream,
|
||||
"video",
|
||||
"Add the specified stream to muxer. Allowed values, 'video' - "
|
||||
"the first video stream; or 'audio' - the first audio stream; or "
|
||||
"zero based stream id.");
|
||||
DEFINE_double(clear_lead,
|
||||
10.0,
|
||||
"Clear lead in seconds if encryption is enabled.");
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
#include "app/widevine_encryption_flags.h"
|
||||
#include "base/file_util.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/strings/string_number_conversions.h"
|
||||
#include "base/strings/stringprintf.h"
|
||||
#include "media/base/demuxer.h"
|
||||
#include "media/base/fixed_encryptor_source.h"
|
||||
|
@ -130,16 +131,29 @@ MediaStream* FindFirstAudioStream(const std::vector<MediaStream*>& streams) {
|
|||
bool AddStreamToMuxer(const std::vector<MediaStream*>& streams, Muxer* muxer) {
|
||||
DCHECK(muxer);
|
||||
|
||||
if (!FLAGS_video && !FLAGS_audio) {
|
||||
LOG(ERROR) << "Required: --audio or --video.";
|
||||
return false;
|
||||
MediaStream* stream = NULL;
|
||||
if (FLAGS_stream == "video") {
|
||||
stream = FindFirstVideoStream(streams);
|
||||
} else if (FLAGS_stream == "audio") {
|
||||
stream = FindFirstAudioStream(streams);
|
||||
} else {
|
||||
// Expect FLAGS_stream to be a zero based stream id.
|
||||
size_t stream_id;
|
||||
if (!base::StringToSizeT(FLAGS_stream, &stream_id) ||
|
||||
stream_id >= streams.size()) {
|
||||
LOG(ERROR) << "Invalid argument --stream=" << FLAGS_stream << "; "
|
||||
<< "should be 'audio', 'video', or a number within [0, "
|
||||
<< streams.size() - 1 << "].";
|
||||
return false;
|
||||
}
|
||||
stream = streams[stream_id];
|
||||
DCHECK(stream);
|
||||
}
|
||||
|
||||
MediaStream* stream = FLAGS_video ? FindFirstVideoStream(streams)
|
||||
: FindFirstAudioStream(streams);
|
||||
// This could occur only if FLAGS_stream=audio|video and the corresponding
|
||||
// stream does not exist in the input.
|
||||
if (!stream) {
|
||||
LOG(ERROR) << "Cannot find a " << (FLAGS_video ? "video" : "audio")
|
||||
<< " stream to mux.";
|
||||
LOG(ERROR) << "No " << FLAGS_stream << " stream found in the input.";
|
||||
return false;
|
||||
}
|
||||
Status status = muxer->AddStream(stream);
|
||||
|
|
|
@ -29,15 +29,7 @@ namespace media {
|
|||
namespace mp4 {
|
||||
|
||||
MP4MediaParser::MP4MediaParser()
|
||||
: state_(kWaitingForInit),
|
||||
moof_head_(0),
|
||||
mdat_tail_(0),
|
||||
has_audio_(false),
|
||||
has_video_(false),
|
||||
audio_track_id_(0),
|
||||
video_track_id_(0),
|
||||
is_audio_track_encrypted_(false),
|
||||
is_video_track_encrypted_(false) {}
|
||||
: state_(kWaitingForInit), moof_head_(0), mdat_tail_(0) {}
|
||||
|
||||
MP4MediaParser::~MP4MediaParser() {}
|
||||
|
||||
|
@ -147,9 +139,6 @@ bool MP4MediaParser::ParseMoov(BoxReader* reader) {
|
|||
RCHECK(moov_->Parse(reader));
|
||||
runs_.reset();
|
||||
|
||||
has_audio_ = false;
|
||||
has_video_ = false;
|
||||
|
||||
std::vector<scoped_refptr<StreamInfo> > streams;
|
||||
|
||||
for (std::vector<Track>::const_iterator track = moov_->tracks.begin();
|
||||
|
@ -200,8 +189,6 @@ bool MP4MediaParser::ParseMoov(BoxReader* reader) {
|
|||
desc_idx -= 1; // BMFF descriptor index is one-based
|
||||
|
||||
if (track->media.handler.type == kAudio) {
|
||||
RCHECK(!has_audio_);
|
||||
|
||||
RCHECK(!samp_descr.audio_entries.empty());
|
||||
|
||||
// It is not uncommon to find otherwise-valid files with incorrect sample
|
||||
|
@ -249,8 +236,8 @@ bool MP4MediaParser::ParseMoov(BoxReader* reader) {
|
|||
return false;
|
||||
}
|
||||
|
||||
is_audio_track_encrypted_ = entry.sinf.info.track_encryption.is_encrypted;
|
||||
DVLOG(1) << "is_audio_track_encrypted_: " << is_audio_track_encrypted_;
|
||||
bool is_encrypted = entry.sinf.info.track_encryption.is_encrypted;
|
||||
DVLOG(1) << "is_audio_track_encrypted_: " << is_encrypted;
|
||||
streams.push_back(new AudioStreamInfo(
|
||||
track->header.track_id,
|
||||
timescale,
|
||||
|
@ -263,14 +250,10 @@ bool MP4MediaParser::ParseMoov(BoxReader* reader) {
|
|||
sampling_frequency,
|
||||
extra_data.size() ? &extra_data[0] : NULL,
|
||||
extra_data.size(),
|
||||
is_audio_track_encrypted_));
|
||||
has_audio_ = true;
|
||||
audio_track_id_ = track->header.track_id;
|
||||
is_encrypted));
|
||||
}
|
||||
|
||||
if (track->media.handler.type == kVideo) {
|
||||
RCHECK(!has_video_);
|
||||
|
||||
RCHECK(!samp_descr.video_entries.empty());
|
||||
if (desc_idx >= samp_descr.video_entries.size())
|
||||
desc_idx = 0;
|
||||
|
@ -290,8 +273,8 @@ bool MP4MediaParser::ParseMoov(BoxReader* reader) {
|
|||
entry.avcc.profile_compatibility,
|
||||
entry.avcc.avc_level);
|
||||
|
||||
is_video_track_encrypted_ = entry.sinf.info.track_encryption.is_encrypted;
|
||||
DVLOG(1) << "is_video_track_encrypted_: " << is_video_track_encrypted_;
|
||||
bool is_encrypted = entry.sinf.info.track_encryption.is_encrypted;
|
||||
DVLOG(1) << "is_video_track_encrypted_: " << is_encrypted;
|
||||
streams.push_back(new VideoStreamInfo(track->header.track_id,
|
||||
timescale,
|
||||
duration,
|
||||
|
@ -303,9 +286,7 @@ bool MP4MediaParser::ParseMoov(BoxReader* reader) {
|
|||
entry.avcc.length_size,
|
||||
&entry.avcc.data[0],
|
||||
entry.avcc.data.size(),
|
||||
is_video_track_encrypted_));
|
||||
has_video_ = true;
|
||||
video_track_id_ = track->header.track_id;
|
||||
is_encrypted));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -372,11 +353,8 @@ bool MP4MediaParser::EnqueueSample(bool* err) {
|
|||
if (!buf_size)
|
||||
return false;
|
||||
|
||||
bool audio = has_audio_ && audio_track_id_ == runs_->track_id();
|
||||
bool video = has_video_ && video_track_id_ == runs_->track_id();
|
||||
|
||||
// Skip this entire track if it's not one we're interested in
|
||||
if (!audio && !video)
|
||||
// Skip this entire track if it is not audio nor video.
|
||||
if (!runs_->is_audio() && !runs_->is_video())
|
||||
runs_->AdvanceRun();
|
||||
|
||||
// Attempt to cache the auxiliary information first. Aux info is usually
|
||||
|
@ -431,7 +409,7 @@ bool MP4MediaParser::EnqueueSample(bool* err) {
|
|||
stream_sample->set_pts(runs_->cts());
|
||||
stream_sample->set_duration(runs_->duration());
|
||||
|
||||
DVLOG(3) << "Pushing frame: aud=" << audio
|
||||
DVLOG(3) << "Pushing frame: "
|
||||
<< ", key=" << runs_->is_keyframe()
|
||||
<< ", dur=" << runs_->duration()
|
||||
<< ", dts=" << runs_->dts()
|
||||
|
|
|
@ -89,13 +89,6 @@ class MP4MediaParser : public MediaParser {
|
|||
scoped_ptr<Movie> moov_;
|
||||
scoped_ptr<TrackRunIterator> runs_;
|
||||
|
||||
bool has_audio_;
|
||||
bool has_video_;
|
||||
uint32 audio_track_id_;
|
||||
uint32 video_track_id_;
|
||||
bool is_audio_track_encrypted_;
|
||||
bool is_video_track_encrypted_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(MP4MediaParser);
|
||||
};
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ struct TrackRunInfo {
|
|||
int64 start_dts;
|
||||
int64 sample_start_offset;
|
||||
|
||||
bool is_audio;
|
||||
TrackType track_type;
|
||||
const AudioSampleEntry* audio_description;
|
||||
const VideoSampleEntry* video_description;
|
||||
|
||||
|
@ -48,7 +48,9 @@ TrackRunInfo::TrackRunInfo()
|
|||
timescale(-1),
|
||||
start_dts(-1),
|
||||
sample_start_offset(-1),
|
||||
is_audio(false),
|
||||
track_type(kInvalid),
|
||||
audio_description(NULL),
|
||||
video_description(NULL),
|
||||
aux_info_start_offset(-1),
|
||||
aux_info_default_size(0),
|
||||
aux_info_total_size(0) {}
|
||||
|
@ -203,15 +205,15 @@ bool TrackRunIterator::Init() {
|
|||
RCHECK(desc_idx > 0); // Descriptions are one-indexed in the file.
|
||||
desc_idx -= 1;
|
||||
|
||||
tri.is_audio = (stsd.type == kAudio);
|
||||
if (tri.is_audio) {
|
||||
tri.track_type = stsd.type;
|
||||
if (tri.track_type == kAudio) {
|
||||
RCHECK(!stsd.audio_entries.empty());
|
||||
if (desc_idx > stsd.audio_entries.size())
|
||||
desc_idx = 0;
|
||||
tri.audio_description = &stsd.audio_entries[desc_idx];
|
||||
// We don't support encrypted non-fragmented mp4 for now.
|
||||
RCHECK(!tri.audio_description->sinf.info.track_encryption.is_encrypted);
|
||||
} else {
|
||||
} else if (tri.track_type == kVideo) {
|
||||
RCHECK(!stsd.video_entries.empty());
|
||||
if (desc_idx > stsd.video_entries.size())
|
||||
desc_idx = 0;
|
||||
|
@ -303,13 +305,13 @@ bool TrackRunIterator::Init(const MovieFragment& moof) {
|
|||
tri.start_dts = run_start_dts;
|
||||
tri.sample_start_offset = trun.data_offset;
|
||||
|
||||
tri.is_audio = (stsd.type == kAudio);
|
||||
if (tri.is_audio) {
|
||||
tri.track_type = stsd.type;
|
||||
if (tri.track_type == kAudio) {
|
||||
RCHECK(!stsd.audio_entries.empty());
|
||||
if (desc_idx > stsd.audio_entries.size())
|
||||
desc_idx = 0;
|
||||
tri.audio_description = &stsd.audio_entries[desc_idx];
|
||||
} else {
|
||||
} else if (tri.track_type == kVideo) {
|
||||
RCHECK(!stsd.video_entries.empty());
|
||||
if (desc_idx > stsd.video_entries.size())
|
||||
desc_idx = 0;
|
||||
|
@ -469,7 +471,12 @@ int TrackRunIterator::aux_info_size() const {
|
|||
|
||||
bool TrackRunIterator::is_audio() const {
|
||||
DCHECK(IsRunValid());
|
||||
return run_itr_->is_audio;
|
||||
return run_itr_->track_type == kAudio;
|
||||
}
|
||||
|
||||
bool TrackRunIterator::is_video() const {
|
||||
DCHECK(IsRunValid());
|
||||
return run_itr_->track_type == kVideo;
|
||||
}
|
||||
|
||||
const AudioSampleEntry& TrackRunIterator::audio_description() const {
|
||||
|
@ -479,7 +486,7 @@ const AudioSampleEntry& TrackRunIterator::audio_description() const {
|
|||
}
|
||||
|
||||
const VideoSampleEntry& TrackRunIterator::video_description() const {
|
||||
DCHECK(!is_audio());
|
||||
DCHECK(is_video());
|
||||
DCHECK(run_itr_->video_description);
|
||||
return *run_itr_->video_description;
|
||||
}
|
||||
|
@ -517,14 +524,16 @@ bool TrackRunIterator::is_keyframe() const {
|
|||
const TrackEncryption& TrackRunIterator::track_encryption() const {
|
||||
if (is_audio())
|
||||
return audio_description().sinf.info.track_encryption;
|
||||
DCHECK(is_video());
|
||||
return video_description().sinf.info.track_encryption;
|
||||
}
|
||||
|
||||
scoped_ptr<DecryptConfig> TrackRunIterator::GetDecryptConfig() {
|
||||
size_t sample_idx = sample_itr_ - run_itr_->samples.begin();
|
||||
DCHECK(sample_idx < cenc_info_.size());
|
||||
DCHECK_LT(sample_idx, cenc_info_.size());
|
||||
const FrameCENCInfo& cenc_info = cenc_info_[sample_idx];
|
||||
DCHECK(is_encrypted() && !AuxInfoNeedsToBeCached());
|
||||
DCHECK(is_encrypted());
|
||||
DCHECK(!AuxInfoNeedsToBeCached());
|
||||
|
||||
const size_t total_size_of_subsamples = cenc_info.GetTotalSizeOfSubsamples();
|
||||
if (total_size_of_subsamples != 0 &&
|
||||
|
|
|
@ -78,13 +78,13 @@ class TrackRunIterator {
|
|||
int aux_info_size() const;
|
||||
bool is_encrypted() const;
|
||||
bool is_audio() const;
|
||||
bool is_video() const;
|
||||
/// @}
|
||||
|
||||
/// @name Only one is valid, based on the value of is_audio().
|
||||
/// @{
|
||||
/// Only valid if is_audio() is true.
|
||||
const AudioSampleEntry& audio_description() const;
|
||||
/// Only valid if is_video() is true.
|
||||
const VideoSampleEntry& video_description() const;
|
||||
/// @}
|
||||
|
||||
/// @name Properties of the current sample. Only valid if IsSampleValid().
|
||||
/// @{
|
||||
|
|
Loading…
Reference in New Issue