diff --git a/mpd/mpd.gyp b/mpd/mpd.gyp index 600388076c..533a21bd5f 100644 --- a/mpd/mpd.gyp +++ b/mpd/mpd.gyp @@ -64,5 +64,16 @@ 'mpd_builder', ], }, + { + 'target_name': 'mpd_util', + 'type': '<(component)', + 'sources': [ + 'util/mpd_writer.cc', + 'util/mpd_writer.h', + ], + 'dependencies': [ + 'mpd_builder', + ], + }, ], } diff --git a/mpd/util/mpd_writer.cc b/mpd/util/mpd_writer.cc new file mode 100644 index 0000000000..291d2678e9 --- /dev/null +++ b/mpd/util/mpd_writer.cc @@ -0,0 +1,220 @@ +#include "mpd/util/mpd_writer.h" + +#include "base/basictypes.h" +#include "media/file/file.h" +#include "mpd/base/mpd_builder.h" +#include "third_party/protobuf/src/google/protobuf/text_format.h" + +using media::File; + +namespace dash_packager { + +namespace { +bool HasVideo(const MediaInfo& media_info) { + return media_info.video_info().size() > 0; +} + +bool HasAudio(const MediaInfo& media_info) { + return media_info.audio_info().size() > 0; +} + +bool HasText(const MediaInfo& media_info) { + return media_info.text_info().size() > 0; +} + +bool MoreThanOneTrue(bool b1, bool b2, bool b3) { + return (b1 && b2) || (b2 && b3) || (b3 && b1); +} + +bool OnlyOneTrue(bool b1, bool b2, bool b3) { + return !MoreThanOneTrue(b1, b2, b3) && (b1 || b2 || b3); +} + +// On entry set |has_video|, |has_audio|, and |has_text| to false. +// On success, return true and set appropriate |has_*| variables. Otherwise +// return false. +bool HasVideoAudioText(const std::list& media_infos, + bool* has_video, + bool* has_audio, + bool* has_text) { + DCHECK(has_video); + DCHECK(has_audio); + DCHECK(has_text); + + *has_video = false; + *has_audio = false; + *has_text = false; + + for (std::list::const_iterator it = media_infos.begin(); + it != media_infos.end(); + ++it) { + const MediaInfo& media_info = *it; + const bool media_info_has_video = HasVideo(media_info); + const bool media_info_has_audio = HasAudio(media_info); + const bool media_info_has_text = HasText(media_info); + + if (MoreThanOneTrue( + media_info_has_video, media_info_has_audio, media_info_has_text)) { + LOG(ERROR) << "MpdWriter cannot handle MediaInfo with more than " + "one stream."; + return false; + } + + if (!OnlyOneTrue( + media_info_has_video, media_info_has_audio, media_info_has_text)) { + LOG(ERROR) << "MpdWriter requires that MediaInfo contain one " + "audio, video, or text stream."; + return false; + } + + *has_video = *has_video || media_info_has_video; + *has_audio = *has_audio || media_info_has_audio; + *has_text = *has_text || media_info_has_text; + } + + return true; +} + +bool SetMediaInfosToMpdBuilder(const std::list& media_infos, + MpdBuilder* mpd_builder) { + if (media_infos.empty()) { + LOG(ERROR) << "No MediaInfo to generate an MPD."; + return false; + } + + bool has_video = false; + bool has_audio = false; + bool has_text = false; + if (!HasVideoAudioText(media_infos, &has_video, &has_audio, &has_text)) + return false; + + DCHECK(mpd_builder); + AdaptationSet* video_adaptation_set = + has_video ? mpd_builder->AddAdaptationSet() : NULL; + AdaptationSet* audio_adaptation_set = + has_audio ? mpd_builder->AddAdaptationSet() : NULL; + AdaptationSet* text_adaptation_set = + has_text ? mpd_builder->AddAdaptationSet() : NULL; + + DCHECK(video_adaptation_set || audio_adaptation_set || text_adaptation_set); + for (std::list::const_iterator it = media_infos.begin(); + it != media_infos.end(); + ++it) { + const MediaInfo& media_info = *it; + DCHECK(OnlyOneTrue( + HasVideo(media_info), HasAudio(media_info), HasText(media_info))); + + Representation* representation = NULL; + if (HasVideo(media_info)) { + representation = video_adaptation_set->AddRepresentation(media_info); + } else if (HasAudio(media_info)) { + representation = audio_adaptation_set->AddRepresentation(media_info); + } else if (HasText(media_info)) { + representation = text_adaptation_set->AddRepresentation(media_info); + } + + if (!representation) { + LOG(ERROR) << "Failed to add representation."; + return false; + } + } + + return true; +} +} // namespace + +MpdWriter::MpdWriter() {} +MpdWriter::~MpdWriter() {} + +bool MpdWriter::AddFile(const char* file_name) { + CHECK(file_name); + + std::string file_content; + if (!media::File::ReadFileToString(file_name, &file_content)) { + LOG(ERROR) << "Failed to read " << file_name << " to string."; + return false; + } + + MediaInfo media_info; + if (!::google::protobuf::TextFormat::ParseFromString(file_content, + &media_info)) { + LOG(ERROR) << "Failed to parse " << file_content << " to MediaInfo."; + return false; + } + + media_infos_.push_back(media_info); + return true; +} + +void MpdWriter::AddBaseUrl(const std::string& base_url) { + base_urls_.push_back(base_url); +} + +// TODO(rkuroiwa): The only use case we have for this is static profile, i.e. +// VOD. But we might want to support dynamic profile for live. +bool MpdWriter::WriteMpdToString(std::string* output) { + CHECK(output); + + MpdBuilder mpd_builder(MpdBuilder::kStatic); + for (std::list::const_iterator it = base_urls_.begin(); + it != base_urls_.end(); + ++it) { + const std::string& base_url = *it; + mpd_builder.AddBaseUrl(base_url); + } + + if (!SetMediaInfosToMpdBuilder(media_infos_, &mpd_builder)) { + LOG(ERROR) << "Failed to set MediaInfos to MpdBuilder."; + return false; + } + + return mpd_builder.ToString(output); +} + +bool MpdWriter::WriteMpdToFile(const char* file_name) { + CHECK(file_name); + + // TODO(rkuroiwa): MpdBuilder doesn't take File pointer yet. Once it does, + // skip intermediate ToString(). + std::string mpd; + if (!WriteMpdToString(&mpd)) { + LOG(ERROR) << "Failed to write MPD to string."; + return false; + } + + File* file = File::Open(file_name, "w"); + if (!file) { + LOG(ERROR) << "Failed to write MPD to string."; + return false; + } + + // TODO(kqyang): If File::Write() changes to best effort write-all then remove + // this loop. + const char* mpd_char_ptr = mpd.data(); + size_t mpd_bytes_left = mpd.size(); + while (mpd_bytes_left > 0) { + int64 length = file->Write(mpd_char_ptr, mpd_bytes_left); + if (length < 0) { + LOG(ERROR) << "Write error " << length; + return false; + } + + if (static_cast(length) > mpd_bytes_left) { + LOG(ERROR) << "Wrote " << length << " bytes but there was only " + << mpd_bytes_left << " bytes to write."; + return false; + } + + mpd_char_ptr += length; + mpd_bytes_left -= length; + } + + if (!file->Flush()) { + LOG(ERROR) << "Failed to flush file."; + return false; + } + + return file->Close(); +} + +} // namespace dash_packager diff --git a/mpd/util/mpd_writer.h b/mpd/util/mpd_writer.h new file mode 100644 index 0000000000..b798c82fcf --- /dev/null +++ b/mpd/util/mpd_writer.h @@ -0,0 +1,68 @@ +// Class for reading in MediaInfo from files and writing out an MPD. +#ifndef MPD_UTIL_MPD_WRITER_H_ +#define MPD_UTIL_MPD_WRITER_H_ + +#include +#include + +#include "base/basictypes.h" + +namespace media { + +class File; + +} // namespace media + +namespace dash_packager { + +class MediaInfo; + +// An instance of this class takes a set of MediaInfo files and generates an +// MPD when one of WriteMpd* methods are called. This generates an MPD with one +// element and at most three elements, each for video, +// audio, and text. Information in MediaInfo will be put into one of the +// AdaptationSets by checking the video_info, audio_info, and text_info fields. +// Therefore, this cannot handle an instance of MediaInfo with video, audio, and +// text combination. +class MpdWriter { + public: + MpdWriter(); + ~MpdWriter(); + + // Add |file_name| for MPD generation. |file_name| should not be NULL. + // The content of |media_info_file| should be a string representation of + // MediaInfo, i.e. the content should be a result of using + // google::protobuf::TestFormat::Print*() methods. + // If necessary, this method can be called after WriteMpd*() methods. + bool AddFile(const char* file_name); + + // |base_url| will be used for element for the MPD. The BaseURL + // element will be a direct child element of the element. + void AddBaseUrl(const std::string& base_url); + + // Write the MPD to |output|. |output| should not be NULL. + // AddFile() should be called before calling this function to generate an MPD. + // On success, MPD is set to |output| and returns true, otherwise returns + // false. + // This method can be called multiple times, if necessary. + bool WriteMpdToString(std::string* output); + + // Write the MPD to |file_name|. |file_name| should not be NULL. + // This opens the file in write mode, IOW if the + // file exists this will over write whatever is in the file. + // AddFile() should be called before calling this function to generate an MPD. + // On success, the MPD gets written to |file| and returns true, otherwise + // returns false. + // This method can be called multiple times, if necessary. + bool WriteMpdToFile(const char* file_name); + + private: + std::list media_infos_; + std::list base_urls_; + + DISALLOW_COPY_AND_ASSIGN(MpdWriter); +}; + +} // namespace dash_packager + +#endif // MPD_UTIL_MPD_WRITER_H_