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