blob: 78f042b4de10b9a015ab2d664f446d39a492b591 [file] [log] [blame]
/*
* Copyright (c) 2011 The WebM project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "media.h"
#include <assert.h>
#include <iostream>
#include "indent.h"
#include "mkvreader.hpp"
using std::cout;
using std::endl;
using indent_webm::Indent;
namespace adaptive_manifest {
Media::Media(const string& id)
: cue_chunk_time_nano_(0x7FFFFFFFFFFFFFFF),
id_(id),
file_() {
}
Media::~Media() {
}
bool Media::Init() {
reader_.reset(new mkvparser::MkvReader());
if (reader_->Open(file_.c_str())) {
cout << "Error trying to open file:" << file_ << endl;
return false;
}
long long pos = 0;
ebml_header_.reset(new mkvparser::EBMLHeader());
ebml_header_->Parse(reader_.get(), pos);
if (!CheckDocType()) {
cout << "DocType != webm" << endl;
return false;
}
mkvparser::Segment* segment;
if (mkvparser::Segment::CreateInstance(reader_.get(), pos, segment)) {
cout << "Segment::CreateInstance() failed." << endl;
return false;
}
segment_.reset(segment);
if (segment_->Load() < 0) {
cout << "Segment::Load() failed." << endl;
return false;
}
if (!CheckCodecTypes()) {
cout << "Video codec_id != V_VP8 || Audio codec_id != A_VORBIS" << endl;
return false;
}
return true;
}
bool Media::CheckAlignement(const Media& media) {
const mkvparser::Cues* const cues = media.GetCues();
if (!cues)
return false;
const mkvparser::Cues* const cues_int = GetCues();
if (!cues_int)
return false;
const mkvparser::Track* const track = media.GetTrack(0);
const mkvparser::Track* const track_int = GetTrack(0);
assert(track);
assert(track_int);
if (cues->GetCount() != cues_int->GetCount())
return false;
const mkvparser::CuePoint* cp = cues->GetFirst();
const mkvparser::CuePoint* cp_int = cues_int->GetFirst();
do {
assert(cp);
assert(cp_int);
if (cp->GetTimeCode() != cp_int->GetTimeCode())
return false;
// Check Block number
const mkvparser::CuePoint::TrackPosition* const tp = cp->Find(track);
const mkvparser::CuePoint::TrackPosition* const tp_int =
cp_int->Find(track_int);
if (tp && tp_int) {
if (tp->m_block != tp_int->m_block)
return false;
}
cp = cues->GetNext(cp);
cp_int = cues_int->GetNext(cp_int);
} while(cp != NULL);
return true;
}
string Media::GetCodec() const {
string codec;
const mkvparser::Track* const track = GetTrack(0);
if (track) {
string vorbis_id("A_VORBIS");
string vp8_id("V_VP8");
string codec_id(track->GetCodecId());
if (codec_id == vorbis_id)
codec = "vorbis";
else if (codec_id == vp8_id)
codec = "vp8";
}
return codec;
}
const mkvparser::Cues* Media::GetCues() const {
assert(segment_.get()!=NULL);
const mkvparser::Cues* const cues = segment_->GetCues();
if (cues) {
// Load all the cue points
while (!cues->DoneParsing()) {
cues->LoadCuePoint();
}
}
return cues;
}
long long Media::GetDurationNanoseconds() const {
assert(segment_.get()!=NULL);
assert(segment_->GetInfo()!=NULL);
return segment_->GetInfo()->GetDuration();
}
void Media::OutputPrototypeManifest(std::ostream& o, Indent& indt) {
indt.Adjust(2);
o << indt << "<Media id=\"" << id_ << "\"";
o << " bandwidth=\"" << GetAverageBandwidth() << "\"";
o << " url=\"" << file_ << "\"";
// Video
const int width = GetVideoWidth();
if (width > 0) {
o << " width=\"" << width << "\"";
}
const int height = GetVideoHeight();
if (height > 0) {
o << " height=\"" << height << "\"";
}
const double rate = GetVideoFramerate();
if (rate > 0.0) {
o << " framerate=\"" << rate << "\"";
}
// Audio
const int channels = GetAudioChannels();
if (channels > 0) {
o << " channels=\"" << channels << "\"";
}
const int samplerate = GetAudioSampleRate();
if (samplerate > 0) {
o << " samplerate=\"" << samplerate << "\"";
}
o << " >" << endl;
OutputPrototypeManifestMediaHeader(o, indt);
OutputPrototypeManifestMediaIndex(o, indt);
o << indt << "</Media>" << endl;
indt.Adjust(-2);
}
bool Media::CheckDocType() const {
bool type_is_webm = false;
assert(ebml_header_.get()!=NULL);
string doc_type(ebml_header_->m_docType);
if (doc_type.compare(0,4,"webm") == 0)
type_is_webm = true;
return type_is_webm;
}
bool Media::CheckCodecTypes() const {
const mkvparser::AudioTrack* const aud_track = GetAudioTrack();
const mkvparser::VideoTrack* const vid_track = GetVideoTrack();
if (!aud_track && !vid_track) {
cout << "WebM file does not have an audio or video track." << endl;
return false;
}
if (aud_track && vid_track)
return false;
if (aud_track) {
string vorbis_id("A_VORBIS");
string codec_id(aud_track->GetCodecId());
if (codec_id != vorbis_id) {
cout << "Audio track does not match A_VORBIS. :" << codec_id << endl;
return false;
}
}
if (vid_track) {
string vp8_id("V_VP8");
string codec_id(vid_track->GetCodecId());
if (codec_id != vp8_id) {
cout << "Video track does not match V_VP8. :" << codec_id << endl;
return false;
}
}
return true;
}
bool Media::CheckForCues() const {
assert(segment_.get()!=NULL);
bool b = false;
const mkvparser::Cues* const cues = segment_->GetCues();
if (cues) {
// Load all the cue points
while (!cues->DoneParsing()) {
cues->LoadCuePoint();
}
const mkvparser::CuePoint* cue;
const mkvparser::CuePoint::TrackPosition* track_position;
// Get the first track. Shouldn't matter what track it is because the
// tracks will be in separate files.
const mkvparser::Track* const track = GetTrack(0);
// Check for the first cue.
b = cues->Find(0, track, cue, track_position);
}
return b;
}
void Media::FindCuesChunk(long long start_time_nano,
long long end_time_nano,
long long& start,
long long& end,
long long& cue_start_time,
long long& cue_end_time) {
start = 0;
end = 0;
cue_start_time = 0;
cue_end_time = 0;
assert(segment_.get()!=NULL);
const mkvparser::Cues* const cues = segment_->GetCues();
if (cues) {
// Load all the cue points
while (!cues->DoneParsing()) {
cues->LoadCuePoint();
}
const mkvparser::CuePoint* cue;
const mkvparser::CuePoint::TrackPosition* track_position;
// Get the first track. Shouldn't matter what track it is because the
// tracks will be in separate files.
const mkvparser::Track* const track = GetTrack(0);
bool b = cues->Find(start_time_nano, track, cue, track_position);
if (b) {
long long time_nano;
while ( (time_nano = cue->GetTime(segment_.get())) < start_time_nano) {
cue = cues->GetNext(cue);
if (!cue)
return; // reached eof
}
start = cue->m_element_start;
cue_start_time = cue->GetTime(segment_.get());
const mkvparser::CuePoint* cue_prev = cue;
while ( (time_nano = cue->GetTime(segment_.get())) < end_time_nano) {
cue_prev = cue;
cue = cues->GetNext(cue);
if (!cue) {
// We have reached eof. Set our current cue to our previous cue so
// our end time will not include the duration of the last cue.
cue = cue_prev;
break;
}
}
end = cue_prev->m_element_start + cue_prev->m_element_size;
cue_end_time = cue->GetTime(segment_.get());
}
}
}
const mkvparser::AudioTrack* Media::GetAudioTrack() const {
assert(segment_.get()!=NULL);
const mkvparser::Tracks* const tracks = segment_->GetTracks();
assert(tracks!=NULL);
unsigned long i = 0;
const unsigned long j = tracks->GetTracksCount();
// TODO: This should be an enum of mkvparser::Tracks
enum { VIDEO_TRACK = 1, AUDIO_TRACK = 2 };
while (i != j) {
const mkvparser::Track* const track = tracks->GetTrackByIndex(i++);
if (track == NULL)
continue;
if (track->GetType() == AUDIO_TRACK)
return static_cast<const mkvparser::AudioTrack*>(track);
}
return NULL;
}
int Media::GetAudioChannels() const {
int channels = 0;
const mkvparser::AudioTrack* const aud_track = GetAudioTrack();
if (aud_track) {
channels = static_cast<int>(aud_track->GetChannels());
}
return channels;
}
int Media::GetAudioSampleRate() const {
int sample_rate = 0;
const mkvparser::AudioTrack* const aud_track = GetAudioTrack();
if (aud_track) {
sample_rate = static_cast<int>(aud_track->GetSamplingRate());
}
return sample_rate;
}
long long Media::GetAverageBandwidth() const {
long long filesize = 0;
assert(segment_.get()!=NULL);
// Just estimate for now by parsing through some elements and getting the
// highest byte value.
// This needs to change later!!!!
// parse through the clusters
const mkvparser::Cluster* cluster = segment_->GetFirst();
while ((cluster != NULL) && !cluster->EOS()) {
if ((cluster->m_element_start + cluster->GetElementSize()) > filesize)
filesize = cluster->m_element_start + cluster->GetElementSize();
cluster = segment_->GetNext(cluster);
}
// check cues element
const mkvparser::Cues* const cues = segment_->GetCues();
if (cues && (cues->m_element_start + cues->m_element_size) > filesize)
filesize = cues->m_element_start + cues->m_element_size;
// TODO: Add assert that SegmentInfo* is not null.
long long bandwidth = (filesize * 8) /
(segment_->GetInfo()->GetDuration() / 1000000000) / 1000;
return bandwidth;
}
long long Media::GetClusterRangeStart() const {
assert(segment_.get()!=NULL);
const mkvparser::Cluster* cluster = segment_->GetFirst();
long long start = -1;
if (cluster) {
start = cluster->m_element_start;
}
return start;
}
void Media::GetSegmentInfoRange(long long& start, long long& end) const {
assert(segment_.get()!=NULL);
const mkvparser::SegmentInfo* const segment_info = segment_->GetInfo();
assert(segment_info!=NULL);
start = 0;
end = 0;
if (segment_info) {
start = segment_info->m_element_start;
end = segment_info->m_element_start + segment_info->m_element_size;
}
}
const mkvparser::Track* Media::GetTrack(unsigned int index) const {
assert(segment_.get()!=NULL);
const mkvparser::Tracks* const tracks = segment_->GetTracks();
assert(tracks!=NULL);
return tracks->GetTrackByIndex(index);
}
void Media::GetTracksRange(long long& start, long long& end) const {
assert(segment_.get()!=NULL);
const mkvparser::Tracks* const tracks = segment_->GetTracks();
assert(tracks!=NULL);
start = 0;
end = 0;
if (tracks) {
start = tracks->m_element_start;
end = tracks->m_element_start + tracks->m_element_size;
}
}
void Media::GetHeaderRange(long long& start, long long& end) const {
start = 0;
end = GetClusterRangeStart();
}
double Media::GetVideoFramerate() const {
double rate = 0.0;
const mkvparser::VideoTrack* const vid_track = GetVideoTrack();
if (vid_track) {
rate = vid_track->GetFrameRate();
}
return rate;
}
int Media::GetVideoHeight() const {
int height = 0;
const mkvparser::VideoTrack* const vid_track = GetVideoTrack();
if (vid_track) {
height = static_cast<int>(vid_track->GetHeight());
}
return height;
}
int Media::GetVideoWidth() const {
int width = 0;
const mkvparser::VideoTrack* const vid_track = GetVideoTrack();
if (vid_track) {
width = static_cast<int>(vid_track->GetWidth());
}
return width;
}
const mkvparser::VideoTrack* Media::GetVideoTrack() const {
assert(segment_.get()!=NULL);
const mkvparser::Tracks* const tracks = segment_->GetTracks();
assert(tracks!=NULL);
unsigned long i = 0;
const unsigned long j = tracks->GetTracksCount();
// TODO: This should be an enum of mkvparser::Tracks
enum { VIDEO_TRACK = 1, AUDIO_TRACK = 2 };
while (i != j) {
const mkvparser::Track* const track = tracks->GetTrackByIndex(i++);
if (track == NULL)
continue;
if (track->GetType() == VIDEO_TRACK)
return static_cast<const mkvparser::VideoTrack*>(track);
}
return NULL;
}
void Media::OutputPrototypeManifestMediaHeader(std::ostream& o, Indent& indt) {
assert(segment_.get()!=NULL);
long long start;
long long end;
GetHeaderRange(start, end);
indt.Adjust(2);
o << indt << "<MediaHeader";
o << " url=\"" << file_ << "\"";
o << " range=\"" << start << "-" << end << "\"";
o << " />" << endl;
indt.Adjust(-2);
}
void Media::OutputPrototypeManifestMediaIndex(std::ostream& o, Indent& indt) {
assert(segment_.get()!=NULL);
if (!CheckForCues())
return;
// Output only the cue elements within the Cues element.
/*
long long start;
long long end;
long long cue_start_nano;
long long cue_end_nano;
FindCuesChunk(0,
GetDurationNanoseconds(),
start,
end,
cue_start_nano,
cue_end_nano);
*/
// Output the entire Cues element.
const mkvparser::Cues* const cues = segment_->GetCues();
const long long start = cues->m_element_start;
const long long end = start + cues->m_element_size;
indt.Adjust(2);
o << indt << "<MediaIndex";
o << " url=\"" << file_ << "\"";
o << " range=\"" << start << "-" << end << "\"";
o << " base_seek_pos=\"" << segment_->m_start << "\"";
o << " />" << endl;
indt.Adjust(-2);
}
void Media::OutputPrototypeManifestCues(std::ostream& o, Indent& indt) {
assert(segment_.get()!=NULL);
if (!CheckForCues())
return;
indt.Adjust(2);
o << indt << "<chunkindexlist";
o << " base_seek_pos=\"" << segment_->m_start << "\" >" << endl;
const long long duration_nano = GetDurationNanoseconds();
const long long chunks = (duration_nano / cue_chunk_time_nano_) + 1;
indt.Adjust(2);
for (int i=0; i<chunks; ++i) {
const long long start_time_nano = i*cue_chunk_time_nano_;
const long long end_time_nano = (i+1)*cue_chunk_time_nano_;
long long start;
long long end;
long long cue_start_nano;
long long cue_end_nano;
FindCuesChunk(start_time_nano,
end_time_nano,
start,
end,
cue_start_nano,
cue_end_nano);
if ((start == 0) && (end == 0))
break; // eof
// Set the end time of the last chunk to the duration of the file
if (i == chunks-1)
cue_end_nano = duration_nano;
o << indt << "<idx start=\"" << cue_start_nano / 1000000000.0 << "\"";
o << " end=\"" << cue_end_nano / 1000000000.0 << "\"";
o << " range=\"" << start << "-" << end << "\" />" << endl;
}
indt.Adjust(-2);
o << indt << "</chunkindexlist>" << endl;
indt.Adjust(-2);
}
std::ostream& operator<< (std::ostream &o, const Media &m) {
o << " Media" << endl;
o << " id_:" << m.id_ << endl;
o << " file_:" << m.file_ << endl;
o << endl;
return o;
}
} // namespace adaptive_manifest