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:
Kongqun Yang 2014-03-17 11:36:41 -07:00
parent af0725a887
commit fddeb1feb1
6 changed files with 60 additions and 63 deletions

View File

@ -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.");

View File

@ -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);

View File

@ -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()

View File

@ -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);
};

View File

@ -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 &&

View File

@ -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().
/// @{