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

View File

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

View File

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

View File

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

View File

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

View File

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