diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 8791d085..ff8fda1a 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -26,3 +26,6 @@ target_link_libraries(create_sadm_from_scratch PRIVATE adm) add_executable(parse_sadm_xml parse_sadm_xml.cpp) target_link_libraries(parse_sadm_xml PRIVATE adm) + +add_executable(adm_segmenter adm_segmenter.cpp) +target_link_libraries(adm_segmenter PRIVATE adm) diff --git a/examples/adm_segmenter.cpp b/examples/adm_segmenter.cpp new file mode 100644 index 00000000..d6b548a4 --- /dev/null +++ b/examples/adm_segmenter.cpp @@ -0,0 +1,86 @@ +#include +#include +#include +#include "adm/common_definitions.hpp" +#include "adm/parse.hpp" +#include "adm/write.hpp" +#include "adm/segmenter.hpp" +#include "adm/utilities/id_assignment.hpp" +#include "adm/utilities/object_creation.hpp" + +using namespace adm; + +std::string genSadmFilename(std::string pref, uint32_t ind); +TrackUidList buildTrackList(std::shared_ptr document); + +int main(int argc, char const *argv[]) { + std::string fout_pre = "/tmp/segmenter_output"; + int size = 1000; + int max_fr = 10; + + auto document = parseXml("../tests/test_data/segmenter_input.xml"); + + // Get file length + std::chrono::nanoseconds filelength((uint64_t)(max_fr * size) * 1000000L); + for (auto programme : document->getElements()) { + if (programme->has() && programme->has()) { + filelength = programme->get().get().asNanoseconds() - programme->get().get().asNanoseconds(); + break; + } else if (programme->has()) { + filelength = programme->get().get().asNanoseconds(); + break; + } + } + + auto trackUidList = buildTrackList(document); + + Segmenter segmenter(document, Time(filelength)); + SegmentStart segment_start(std::chrono::milliseconds(0)); + SegmentDuration segment_size(std::chrono::milliseconds{size}); + int fr = 0; + while (segment_start < filelength && fr < max_fr) { + segmenter.buildFrame(segment_start, segment_size, fr); + auto transportTrackFormat = segmenter.generateTransportTrackFormat(trackUidList, + segment_start, segment_size); + auto frame = segmenter.getFrame(); + auto frameHeader = segmenter.getFrameHeader(); + + std::stringstream xmlStream; + writeXml(xmlStream, frame, *frameHeader); + + std::string fname = genSadmFilename(fout_pre, fr); + std::ofstream sfile(fname); + + sfile << xmlStream.str(); + + // Get ready for next frame + xmlStream.str(""); + segment_start = SegmentStart(segment_start.get() + segment_size.get()); + fr++; + } + + return 0; +} + + +std::string genSadmFilename(std::string pref, uint32_t ind) { + char c[256]; + sprintf(c, "%s_%04d.xml", pref.c_str(), ind); + std::string s(c); + return s; +} + +TrackUidList buildTrackList(std::shared_ptr document) { + TrackUidList trackUidList; + + auto atus = document->getElements(); + uint16_t track_idx = 1; + for (auto atu : atus) { + TrackUid track_uid; + track_uid.uid = formatId(atu->get()); + track_uid.trackIndex = track_idx; + trackUidList.trackUid.push_back(track_uid); + track_idx++; + } + return trackUidList; +} \ No newline at end of file diff --git a/include/adm/document.hpp b/include/adm/document.hpp index 3e605782..8dffa716 100644 --- a/include/adm/document.hpp +++ b/include/adm/document.hpp @@ -80,6 +80,10 @@ namespace adm { ADM_EXPORT bool add(std::shared_ptr trackFormat); /// @brief Add an AudioTrackUid ADM_EXPORT bool add(std::shared_ptr trackUid); + /// @brief Add a profileList + ADM_EXPORT bool add(std::shared_ptr profileList); + /// @brief Add a tagList + ADM_EXPORT bool add(std::shared_ptr tagList); ///@} /** @name Remove ADM elements @@ -104,6 +108,10 @@ namespace adm { ADM_EXPORT bool remove(std::shared_ptr trackFormat); /// @brief Remove an AudioTrackUid ADM_EXPORT bool remove(std::shared_ptr trackUid); + /// @brief Remove a profileList + ADM_EXPORT bool remove(std::shared_ptr profileList); + /// @brief Remove a tagList + ADM_EXPORT bool remove(std::shared_ptr tagList); ///@} /** @@ -126,6 +134,12 @@ namespace adm { template ElementRange getElements(); + template + std::shared_ptr getElement() const; + + template + std::shared_ptr getElement(); + /** @name Lookup ADM elements by its Id * * Lookup the first ADM element with the given Id. @@ -257,6 +271,10 @@ namespace adm { detail::ParameterTraits::tag) const; ADM_EXPORT ElementRange getElements( detail::ParameterTraits::tag) const; + ADM_EXPORT std::shared_ptr getElement( + detail::ParameterTraits::tag) const; + ADM_EXPORT std::shared_ptr getElement( + detail::ParameterTraits::tag) const; ADM_EXPORT ElementRange getElements( detail::ParameterTraits::tag); ADM_EXPORT ElementRange getElements( @@ -273,6 +291,10 @@ namespace adm { detail::ParameterTraits::tag); ADM_EXPORT ElementRange getElements( detail::ParameterTraits::tag); + ADM_EXPORT std::shared_ptr getElement( + detail::ParameterTraits::tag); + ADM_EXPORT std::shared_ptr getElement( + detail::ParameterTraits::tag); /// check the parent of an element /// @@ -300,6 +322,8 @@ namespace adm { std::vector> audioStreamFormats_; std::vector> audioTrackFormats_; std::vector> audioTrackUids_; + std::shared_ptr profileList_; + std::shared_ptr tagList_; detail::IdAssigner idAssigner_; }; @@ -317,4 +341,16 @@ namespace adm { return getElements(Tag()); } + template + std::shared_ptr Document::getElement() const { + typedef typename detail::ParameterTraits::tag Tag; + return getElement(Tag()); + } + + template + std::shared_ptr Document::getElement() { + typedef typename detail::ParameterTraits::tag Tag; + return getElement(Tag()); + } + } // namespace adm diff --git a/include/adm/elements.hpp b/include/adm/elements.hpp index 9fdf9366..70bdd16d 100644 --- a/include/adm/elements.hpp +++ b/include/adm/elements.hpp @@ -27,6 +27,7 @@ #include "adm/elements/audio_stream_format.hpp" #include "adm/elements/audio_track_uid.hpp" #include "adm/elements/profile_list.hpp" +#include "adm/elements/tag_list.hpp" #include "adm/elements/audio_block_format_direct_speakers.hpp" #include "adm/elements/audio_block_format_matrix.hpp" diff --git a/include/adm/elements/audio_channel_format.hpp b/include/adm/elements/audio_channel_format.hpp index 45df467e..aa474157 100644 --- a/include/adm/elements/audio_channel_format.hpp +++ b/include/adm/elements/audio_channel_format.hpp @@ -381,7 +381,7 @@ namespace adm { return previous == 0u || current == previous.get() + 1u; } } - + template void AudioChannelFormat::assignId(BlockFormat &blockFormat, BlockFormat *previousBlock) { diff --git a/include/adm/elements/profile_list.hpp b/include/adm/elements/profile_list.hpp index a49faf0d..ec19a79e 100644 --- a/include/adm/elements/profile_list.hpp +++ b/include/adm/elements/profile_list.hpp @@ -85,12 +85,12 @@ namespace adm { using ProfileListBase = HasParameters>; } // namespace detail - struct ProfileceListTag {}; + struct ProfileListTag {}; class ProfileList : private detail::ProfileListBase, private detail::AddWrapperMethods { public: - using tag = ProfileceListTag; + using tag = ProfileListTag; template explicit ProfileList(Parameters... namedArgs) { diff --git a/include/adm/elements/tag_list.hpp b/include/adm/elements/tag_list.hpp new file mode 100644 index 00000000..d3703c52 --- /dev/null +++ b/include/adm/elements/tag_list.hpp @@ -0,0 +1,258 @@ +#pragma once +#include +#include "adm/detail/auto_base.hpp" +#include "adm/elements/audio_programme.hpp" +#include "adm/elements_fwd.hpp" +#include "adm/detail/named_option_helper.hpp" +#include "adm/detail/optional_comparison.hpp" +#include "adm/errors.hpp" + +namespace adm { + struct TTagValueTag {}; + using TTagValue = detail::NamedType; + + struct TTagClassTag {}; + using TTagClass = detail::NamedType; + + struct TTagTag {}; + + namespace detail { + extern template class ADM_EXPORT_TEMPLATE_METHODS + RequiredParameter; + extern template class ADM_EXPORT_TEMPLATE_METHODS + OptionalParameter; + + using TTagBase = HasParameters, + OptionalParameter>; + } // namespace detail + + class TTag : private detail::TTagBase, + private detail::AddWrapperMethods { + public: + using tag = TTagTag; + + template + explicit TTag(Parameters... namedArgs) { + detail::setNamedOptionHelper(this, std::move(namedArgs)...); + } + + ADM_EXPORT explicit TTag(std::string str) + : TTag(TTagValue(std::move(str))) {} + ADM_EXPORT explicit TTag(const char *s); + + ADM_EXPORT void print(std::ostream &os) const; + + using detail::TTagBase::set; + using detail::TTagBase::unset; + using detail::AddWrapperMethods::get; + using detail::AddWrapperMethods::has; + using detail::AddWrapperMethods::isDefault; + using detail::AddWrapperMethods::unset; + + private: + using detail::TTagBase::get; + using detail::TTagBase::has; + + friend class detail::AddWrapperMethods; + }; + + struct TTagsTag {}; + + using TTags = std::vector; + ADD_TRAIT(TTags, TTagsTag); + + inline bool operator==(const TTag &a, const TTag &b) { + return detail::optionalsEqual(a, b); + } + + inline bool operator!=(const TTag &a, const TTag &b) { return !(a == b); } + + struct TagGroupTag {}; + + namespace detail { + extern template class ADM_EXPORT_TEMPLATE_METHODS VectorParameter; + + using TagGroupBase = HasParameters>; + } // namespace detail + + class TagGroup : private detail::TagGroupBase, + private detail::AddWrapperMethods { + public: + using tag = TagGroupTag; + + // DEBUG FUNCTIONS + int getTempId() { return temp_id_; }; + void setTempId(int n) { temp_id_ = n; }; + + template + explicit TagGroup(Parameters... namedArgs) { + detail::setNamedOptionHelper(this, std::move(namedArgs)...); + } + + /// @brief Add reference to an AudioProgramme + ADM_EXPORT bool addReference(std::shared_ptr programme); + + /// @brief Add reference to an AudioContent + ADM_EXPORT bool addReference(std::shared_ptr content); + + /// @brief Add reference to an AudioObject + ADM_EXPORT bool addReference(std::shared_ptr object); + + template + ElementRange getReferences(); + + template + ElementRange getReferences() const; + + /// @brief Remove reference to an AudioProgramme + ADM_EXPORT void removeReference(std::shared_ptr programme); + + /// @brief Remove reference to an AudioContent + ADM_EXPORT void removeReference(std::shared_ptr content); + + /// @brief Remove reference to an AudioObject + ADM_EXPORT void removeReference(std::shared_ptr object); + + template + void clearReferences(); + + using detail::TagGroupBase::set; + using detail::AddWrapperMethods::get; + using detail::AddWrapperMethods::has; + using detail::AddWrapperMethods::isDefault; + using detail::AddWrapperMethods::unset; + using detail::TagGroupBase::add; + using detail::TagGroupBase::remove; + + private: + int temp_id_; + + using detail::TagGroupBase::get; + using detail::TagGroupBase::has; + using detail::TagGroupBase::isDefault; + using detail::TagGroupBase::unset; + + friend class detail::AddWrapperMethods; + + ADM_EXPORT ElementRange getReferences( + detail::ParameterTraits::tag) const; + ADM_EXPORT ElementRange getReferences( + detail::ParameterTraits::tag); + ADM_EXPORT ElementRange getReferences( + detail::ParameterTraits::tag) const; + ADM_EXPORT ElementRange getReferences( + detail::ParameterTraits::tag); + ADM_EXPORT ElementRange getReferences( + detail::ParameterTraits::tag) const; + ADM_EXPORT ElementRange getReferences( + detail::ParameterTraits::tag); + + ADM_EXPORT void clearReferences( + detail::ParameterTraits::tag); + ADM_EXPORT void clearReferences(detail::ParameterTraits::tag); + ADM_EXPORT void clearReferences(detail::ParameterTraits::tag); + + ADM_EXPORT void disconnectReferences(); + + std::vector> audioProgrammes_; + std::vector> audioContents_; + std::vector> audioObjects_; + }; + + inline bool operator==(const TagGroup &a, const TagGroup &b) { + return detail::optionalsEqual(a, b); + } + + inline bool operator!=(const TagGroup &a, const TagGroup &b) { + return !(a == b); + } + + template + ElementRange TagGroup::getReferences() const { + typedef typename detail::ParameterTraits::tag Tag; + return getReferences(Tag()); + } + + template + ElementRange TagGroup::getReferences() { + typedef typename detail::ParameterTraits::tag Tag; + return getReferences(Tag()); + } + + inline ElementRange TagGroup::getReferences( + detail::ParameterTraits::tag) const { + return detail::makeElementRange(audioProgrammes_); + } + + inline ElementRange TagGroup::getReferences( + detail::ParameterTraits::tag) { + return detail::makeElementRange(audioProgrammes_); + } + + inline ElementRange TagGroup::getReferences( + detail::ParameterTraits::tag) const { + return detail::makeElementRange(audioContents_); + } + + inline ElementRange TagGroup::getReferences( + detail::ParameterTraits::tag) { + return detail::makeElementRange(audioContents_); + } + + inline ElementRange TagGroup::getReferences( + detail::ParameterTraits::tag) const { + return detail::makeElementRange(audioObjects_); + } + + inline ElementRange TagGroup::getReferences( + detail::ParameterTraits::tag) { + return detail::makeElementRange(audioObjects_); + } + + template + void TagGroup::clearReferences() { + typedef typename detail::ParameterTraits::tag Tag; + clearReferences(Tag()); + } + + struct TagGroupsTag {}; + + using TagGroups = std::vector; + ADD_TRAIT(TagGroups, TagGroupsTag); + + namespace detail { + extern template class ADM_EXPORT_TEMPLATE_METHODS + VectorParameter; + + using TagListBase = HasParameters>; + } // namespace detail + + struct TagListTag {}; + + class TagList : private detail::TagListBase, + private detail::AddWrapperMethods { + public: + using tag = TagListTag; + + template + explicit TagList(Parameters... namedArgs) { + detail::setNamedOptionHelper(this, std::move(namedArgs)...); + } + + using detail::TagListBase::set; + using detail::AddWrapperMethods::get; + using detail::AddWrapperMethods::has; + using detail::AddWrapperMethods::isDefault; + using detail::AddWrapperMethods::unset; + using detail::TagListBase::add; + using detail::TagListBase::remove; + + private: + using detail::TagListBase::get; + using detail::TagListBase::has; + using detail::TagListBase::isDefault; + using detail::TagListBase::unset; + + friend class detail::AddWrapperMethods; + }; +} // namespace adm diff --git a/include/adm/parse.hpp b/include/adm/parse.hpp index 0ccfae24..11f8c3cc 100644 --- a/include/adm/parse.hpp +++ b/include/adm/parse.hpp @@ -88,6 +88,21 @@ namespace adm { std::istream& stream, FrameHeader const& header, xml::ParserOptions options = xml::ParserOptions::none); + /** + * @brief Parse an xml frame containing an audioFormatExtended + * node into an adm::Document, using a SADM FrameHeader to check for consistency. + * Primarily intended for parsing sadm frames. + * + * Parse adm data from an `std::istream`. + * @param stream input stream to parse XML data + * @param header S-ADM Frame header + * @param options Options to influence the XML parser behaviour + * @param commonDefinitions Common Defintions document + */ + ADM_EXPORT std::shared_ptr parseFrame( + std::istream& stream, const FrameHeader& header, + xml::ParserOptions options, std::shared_ptr commonDefinitions); + /** * @brief Parse an XML representation of a serial ADM frame and return * the frameHeader element as an adm::FrameHeader object diff --git a/include/adm/private/document_parser.hpp b/include/adm/private/document_parser.hpp index ee2ac5c2..bcaa7118 100644 --- a/include/adm/private/document_parser.hpp +++ b/include/adm/private/document_parser.hpp @@ -70,6 +70,7 @@ namespace adm { NodePtr node, boost::optional timeReference); Profile parseProfile(NodePtr node); ProfileList parseProfileList(NodePtr node); + TTag parseTTag(NodePtr node); NodePtr findAudioFormatExtendedNodeEbuCore(NodePtr root); NodePtr findAudioFormatExtendedNodeFullRecursive(NodePtr root); @@ -105,6 +106,8 @@ namespace adm { std::shared_ptr parseAudioPackFormat(NodePtr node); std::shared_ptr parseAudioTrackUid(NodePtr node); std::shared_ptr parseAudioChannelFormat(NodePtr node); + std::shared_ptr parseTagGroup(NodePtr node); + TagList parseTagList(NodePtr node); rapidxml::file<> xmlFile_; ParserOptions options_; @@ -127,6 +130,9 @@ namespace adm { std::map, AudioChannelFormatId> streamFormatChannelFormatRef_; std::map, AudioPackFormatId> streamFormatPackFormatRef_; std::map, std::vector> streamFormatTrackFormatRefs_; + std::map, std::vector> tagGroupProgrammeRefs_; + std::map, std::vector> tagGroupContentRefs_; + std::map, std::vector> tagGroupObjectRefs_; // clang-format on /// used to keep track of element IDs ourselves to avoid having it diff --git a/include/adm/private/rapidxml_formatter.hpp b/include/adm/private/rapidxml_formatter.hpp index df06671e..97ea6c38 100644 --- a/include/adm/private/rapidxml_formatter.hpp +++ b/include/adm/private/rapidxml_formatter.hpp @@ -55,6 +55,9 @@ namespace adm { XmlNode &node, const std::shared_ptr trackUid); void formatProfileList(XmlNode &node, const ProfileList &profileList); void formatProfile(XmlNode &node, const Profile &profile); + void formatTagList(XmlNode &node, const TagList &tagList); + void formatTagGroup(XmlNode &node, const TagGroup &tagGroup); + void formatTag(XmlNode &node, const TTag &tag); void formatBlockFormatDirectSpeakers( XmlNode &node, const AudioBlockFormatDirectSpeakers &audioBlock, diff --git a/include/adm/private/rapidxml_wrapper.hpp b/include/adm/private/rapidxml_wrapper.hpp index 5d4ce89a..65a9af99 100644 --- a/include/adm/private/rapidxml_wrapper.hpp +++ b/include/adm/private/rapidxml_wrapper.hpp @@ -102,6 +102,10 @@ namespace adm { void addBaseElements(const Source &src, const std::string &name, Callable formatter); + template + void addBaseElement(const Source &src, const std::string &name, + Callable formatter); + template void addReference(const Source &src, const std::string &name); @@ -262,6 +266,16 @@ namespace adm { } } + template + void XmlNode::addBaseElement(const Source &src, const std::string &name, + Callable formatter) { + auto admElement = src->template getElement(); + if (admElement) { + auto node = addNode(name); + formatter(node, *admElement); + } + } + template void XmlNode::addReference(const Source &src, const std::string &name) { addElement(src->template getReference(), name); diff --git a/include/adm/segmenter.hpp b/include/adm/segmenter.hpp new file mode 100644 index 00000000..bec493df --- /dev/null +++ b/include/adm/segmenter.hpp @@ -0,0 +1,338 @@ +#pragma once + +#include "adm/document.hpp" +#include "adm/serial/frame_header.hpp" +#include "adm/route.hpp" +#include "adm/utilities/comparator.hpp" +#include +#include "adm/route.hpp" +#include "adm/route_tracer.hpp" + +#include +#include +#include + +namespace adm { + + namespace detail { + struct SegmenterItem { + SegmenterItem(std::shared_ptr programme, + std::shared_ptr content, + std::vector> objects, + std::vector> packFormats, + std::shared_ptr channelFormat, + std::shared_ptr streamFormat, + std::shared_ptr trackFormat, + std::shared_ptr trackUid, + std::chrono::nanoseconds start, + boost::optional end) + : programme(programme), + content(content), + objects(objects), + packFormats(packFormats), + channelFormat(channelFormat), + streamFormat(streamFormat), + trackFormat(trackFormat), + trackUid(trackUid), + start(start), end(end){}; + std::shared_ptr programme; + std::shared_ptr content; + std::vector> objects; + std::vector> packFormats; + std::shared_ptr channelFormat; + std::shared_ptr streamFormat; + std::shared_ptr trackFormat; + std::shared_ptr trackUid; + std::chrono::nanoseconds start; + boost::optional end; + bool use; + }; + } // namespace detail + + /// @brief Tag for NamedType ::SegmentStart + struct SegmentStartTag {}; + /// @brief NamedType for the audioContentName attribute + using SegmentStart = + detail::NamedType; + /// @brief Tag for NamedType ::SegmentDuration + struct SegmentDurationTag {}; + /// @brief NamedType for the audioContentName attribute + using SegmentDuration = + detail::NamedType; + + struct TrackUid { + uint16_t trackIndex; + std::string uid; + }; + + /// @brief This could be filled from the chna chunk. + struct TrackUidList { + std::vector trackUid; + }; + + + // This routing strategy ensures audioTrackUid is included. + struct CustomStrategy { + template + bool shouldRecurse(std::shared_ptr, std::shared_ptr) { + return true; + } + bool shouldRecurse(std::shared_ptr, + std::shared_ptr) { + return false; + } + + template + bool shouldAdd(std::shared_ptr) { + return true; + } + + template + bool isEndOfRoute(std::shared_ptr) { + return false; + } + + bool isEndOfRoute(std::shared_ptr) { + return true; + } + + bool isEndOfRoute( + const std::shared_ptr &track_uid) { + return track_uid->isSilent(); + } + }; + + /** + * @brief Segment Document into Frames + * + * For now this class can only create full frames. + * + * The segmenter adds all AudioBlockFormats which *could* be relevant to the + * requested segment. It does take into account the duration of the + * AudioObject and AudioProgramme, but ignores the duration of the + * AudioBlockFormat. Consider the following example: + * + * @code + 0s 1s 2s 3s 4s 5s 6s + |-----|-----|-----|-----|-----|-----| + + +-----+-----+-----+-----+ + | AO1 | + +-----+-----+-----+-----+ + | AB1 | AB2 | AB3 | AB4 | + +-----+-----+-----+-----+ + @endcode + * + * For the following segment ranges the behaviour will be as follows: + * + * - [0s; 0.5s]: no AudioBlockFormat + * - [1s; 2s]: AB1, AB2 + * - AB2 to calc the duration of AB1 + * - [2s; 2.5s]: AB1, AB2, AB3 + * - AB1 to calc the start values of an interpolation + * - AB2 to calc the end values of an interpolation + * - AB3 to calc the duration of AB2 + * - [2.25s; 2.75s]: AB1, AB2, AB3 + * - AB1 to calc the start values of an interpolation + * - AB2 to calc the end values of an interpolation + * - AB3 to calc the duration of AB2 + * - [2s; 3s]: AB1, AB2, AB3 + * - AB1 to calc the start values of an interpolation + * - AB2 to calc the end values of an interpolation + * - AB3 to calc the duration of AB2 + * - AB4 is not relevant yet! + * - [4.5s; 5s]: AB3, AB4 + * - AB3 to calc the start values of an interpolation + * - AB4 to calc the end values of an interpolation + */ + class Segmenter { + public: + /** + * @brief Segmenter Ctor + * + * @warning All the audioBlockFormats in the adm::document, which is passed + * to the ctor will be sorted by time! + */ + ADM_EXPORT Segmenter(std::shared_ptr document, boost::optional