From 717b0484d8d6b731b51484c830ed6c72aa7ec6f2 Mon Sep 17 00:00:00 2001 From: Henry Hoegelow Date: Thu, 21 Dec 2017 17:43:38 +0100 Subject: [PATCH 01/28] adding support for mp3, m4a and wma files on linux by using gstreamer library this is the initial revision, tests are not yet green, to be continued --- audiostream/CMakeLists.txt | 29 +- .../audio/source/gstreamer_file_source.cpp | 295 ++++++++++++++++++ .../audio/source/gstreamer_file_source.h | 84 +++++ .../ni/media/audio/source/mp3_file_source.cpp | 4 + .../ni/media/audio/source/mp3_file_source.h | 2 + .../ni/media/audio/source/mp4_file_source.cpp | 4 + .../ni/media/audio/source/mp4_file_source.h | 2 + .../ni/media/audio/source/wma_file_source.cpp | 8 + .../ni/media/audio/source/wma_file_source.h | 5 + cmake/FindGObject.cmake | 5 + cmake/FindGStreamer.cmake | 23 ++ cmake/FindGStreamerApp.cmake | 23 ++ cmake/FindGlib.cmake | 5 + 13 files changed, 487 insertions(+), 2 deletions(-) create mode 100644 audiostream/src/ni/media/audio/source/gstreamer_file_source.cpp create mode 100644 audiostream/src/ni/media/audio/source/gstreamer_file_source.h create mode 100644 cmake/FindGObject.cmake create mode 100644 cmake/FindGStreamer.cmake create mode 100644 cmake/FindGStreamerApp.cmake create mode 100644 cmake/FindGlib.cmake diff --git a/audiostream/CMakeLists.txt b/audiostream/CMakeLists.txt index 8ff48535..6203fb12 100644 --- a/audiostream/CMakeLists.txt +++ b/audiostream/CMakeLists.txt @@ -6,7 +6,7 @@ option( NIMEDIA_ENABLE_AIFF_DECODING "Enable ni-media aiff decoding" ON ) option( NIMEDIA_ENABLE_FLAC_DECODING "Enable ni-media flac decoding" ON ) option( NIMEDIA_ENABLE_OGG_DECODING "Enable ni-media ogg decoding" ON ) -if ( APPLE OR WIN32 ) +if ( APPLE OR WIN32 OR LINUX ) option( NIMEDIA_ENABLE_MP3_DECODING "Enable ni-media mp3 decoding" ON ) option( NIMEDIA_ENABLE_MP4_DECODING "Enable ni-media mp4 decoding" ON ) else() @@ -20,7 +20,7 @@ else() option( NIMEDIA_ENABLE_ITUNES_DECODING "Enable ni-media iTunes decoding" OFF ) endif() -if( WIN32 ) +if( WIN32 OR LINUX ) option( NIMEDIA_ENABLE_WMA_DECODING "Enable ni-media wma decoding" ON ) else() option( NIMEDIA_ENABLE_WMA_DECODING "Enable ni-media wma decoding" OFF ) @@ -71,6 +71,7 @@ endif() set( COMPILE_WITH_COREAUDIO DONT_COMPILE) set( COMPILE_WITH_MEDIA_FOUNDATION DONT_COMPILE) +set( COMPILE_WITH_GSTREAMER DONT_COMPILE) #----------------------------------------------------------------------------------------------------------------------- # dependencies @@ -133,6 +134,29 @@ if( NIMEDIA_ENABLE_MP3_DECODING OR NIMEDIA_ENABLE_MP4_DECODING OR NIMEDIA_ENABLE list(APPEND codec_libraries mfplat.lib mfreadwrite.lib mfuuid.lib Propsys.lib) + elseif( LINUX ) + + set(COMPILE_WITH_GSTREAMER) + + find_package(Glib REQUIRED) + find_package(GStreamer REQUIRED) + find_package(GStreamerApp REQUIRED) + find_package(GObject REQUIRED) + + if ( NOT TARGET GSTREAMER::gstreamer ) + message(FATAL_ERROR + "You are building ni-media with GStreamer decoding support but the required gstreamer-1.0 library was not found\n" + "Make sure library can be found or disable GSTREAMER decoding by setting:\n" + " * NIMEDIA_ENABLE_MP3_DECODING = OFF\n" + " * NIMEDIA_ENABLE_MP4_DECODING = OFF\n" + " * NIMEDIA_ENABLE_WMA_DECODING = OFF\n") + endif() + + list(APPEND codec_libraries GSTREAMER::gstreamer) + list(APPEND codec_libraries GSTREAMERAPP::gstreamerapp) + list(APPEND codec_libraries glib-2.0) + list(APPEND codec_libraries gobject-2.0) + else() message(FATAL_ERROR @@ -228,6 +252,7 @@ add_src_file (FILES_media_audio_source "src/ni/media/audio/source/container_sou add_src_file (FILES_media_audio_source "src/ni/media/audio/source/core_audio_file_source.cpp" ${COMPILE_WITH_COREAUDIO} WITH_HEADER ) add_src_file (FILES_media_audio_source "src/ni/media/audio/source/media_foundation_helper.h" ${COMPILE_WITH_MEDIA_FOUNDATION} ) add_src_file (FILES_media_audio_source "src/ni/media/audio/source/media_foundation_file_source.cpp" ${COMPILE_WITH_MEDIA_FOUNDATION} WITH_HEADER ) +add_src_file (FILES_media_audio_source "src/ni/media/audio/source/gstreamer_file_source.cpp" ${COMPILE_WITH_GSTREAMER} WITH_HEADER ) add_src_file (FILES_media_audio_source "src/ni/media/audio/source/aiff_source.h" ${COMPILE_WITH_AIFF_DECODING} ) add_src_file (FILES_media_audio_source "src/ni/media/audio/source/aiff_file_source.h" ${COMPILE_WITH_AIFF_DECODING} ) add_src_file (FILES_media_audio_source "src/ni/media/audio/source/aiff_vector_source.h" ${COMPILE_WITH_AIFF_DECODING} ) diff --git a/audiostream/src/ni/media/audio/source/gstreamer_file_source.cpp b/audiostream/src/ni/media/audio/source/gstreamer_file_source.cpp new file mode 100644 index 00000000..170ccab5 --- /dev/null +++ b/audiostream/src/ni/media/audio/source/gstreamer_file_source.cpp @@ -0,0 +1,295 @@ +// +// Copyright (c) 2017 Native Instruments GmbH, Berlin +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +#include "gstreamer_file_source.h" + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +namespace detail +{ + template + struct RingBuffer + { + static constexpr size_t size = s; + static constexpr size_t mask = (s - 1); + std::vector m_buffer; + uint64_t m_write_head = 0; + uint64_t m_read_head = 0; + + void push(const T* data, size_t count) + { + if(count) + { + auto idx = m_write_head & mask; + auto space = size - idx; + auto for_now = std::min(space, count); + std::copy(data, data + for_now, m_buffer.begin() + idx); + m_write_head += for_now; + push(data + for_now, count - for_now); + } + } + + size_t pull(T* data, size_t count) + { + count = std::min(count, m_write_head - m_read_head); + + if(count) + { + auto idx = m_read_head & mask; + auto space = size - idx; + auto for_now = std::min(space, count); + std::copy(m_buffer.begin() + idx, m_buffer.begin() + idx + for_now, data); + m_read_head += for_now; + return for_now + pull(data + for_now, count - for_now); + } + return 0; + } + }; +} + +//---------------------------------------------------------------------------------------------------------------------- +gstreamer_file_source::gstreamer_file_source(const std::string& path, audio::ifstream_info::container_type container, size_t stream) : + m_pipeline(nullptr, gst_object_unref), + m_ring_buffer(new RingBuffer()) +{ + init_gstreamer(); + setup_source(path, container); +} + +//---------------------------------------------------------------------------------------------------------------------- + +gstreamer_file_source::~gstreamer_file_source() +{ +} + +//---------------------------------------------------------------------------------------------------------------------- + +void gstreamer_file_source::init_gstreamer() +{ + static bool gstreamer_initialized = false; + + if(!std::exchange(gstreamer_initialized, true)) + { + gst_init(0, nullptr); + } +} + +//---------------------------------------------------------------------------------------------------------------------- + +void gstreamer_file_source::setup_source(const std::string& path, audio::ifstream_info::container_type container) +{ + GstElement* sink = prepare_pipeline(path); + auto sinkpad = gst_element_get_static_pad(sink, "sink"); + auto caps = gst_pad_get_current_caps(sinkpad); + auto caps_struct = gst_caps_get_structure(caps, 0); + fill_format_info(caps_struct, container); +} + +//---------------------------------------------------------------------------------------------------------------------- + +GstElement* gstreamer_file_source::prepare_pipeline(const std::string& path) +{ + m_pipeline.reset(gst_pipeline_new("pipeline")); + + GstElement* source = gst_element_factory_make("filesrc", "source"); + GstElement* decodebin = gst_element_factory_make("decodebin", "decoder"); + GstElement* queue = gst_element_factory_make("queue", "queue"); + GstElement* sink = gst_element_factory_make("appsink", "sink"); + + gst_bin_add_many(GST_BIN(m_pipeline.get()), source, decodebin, queue, sink, nullptr); + gst_element_link_many(source, decodebin, NULL); + gst_element_link_many(queue, sink, NULL); + + g_object_set(source, "location", path.c_str(), NULL); + + g_signal_connect(decodebin, "pad-added", G_CALLBACK(&onPadAdded), queue); + + preroll_pipeline(); + return sink; +} + +//---------------------------------------------------------------------------------------------------------------------- + +void gstreamer_file_source::preroll_pipeline() +{ + gst_element_set_state(m_pipeline.get(), GST_STATE_PLAYING); + GstState current_state = GST_STATE_VOID_PENDING; + GstState pending_state = GST_STATE_VOID_PENDING; + + while(gst_element_get_state(m_pipeline.get(), ¤t_state, &pending_state, GST_MSECOND) == GST_STATE_CHANGE_ASYNC) + { + g_main_context_iteration(nullptr, FALSE); + } + + std::cerr << "Pipeline is in state: " << current_state << std::endl; + + if(current_state != GST_STATE_PLAYING) + { + auto res = gst_element_get_state(m_pipeline.get(), ¤t_state, &pending_state, GST_MSECOND); + std::cerr << "Pipeline does not preroll: " << res << " " << current_state << std::endl; + throw std::runtime_error( "gstreamer_file_source: pipeline doesn't preroll" ); + } +} + +//---------------------------------------------------------------------------------------------------------------------- + +void gstreamer_file_source::fill_format_info(GstStructure *caps_struct, audio::ifstream_info::container_type container) +{ + m_info.container(container); + m_info.codec(audio::ifstream_info::codec_type::mp3); + m_info.lossless(false); + + gint64 num_frames = 0; + gst_element_query_duration(m_pipeline.get(), GST_FORMAT_DEFAULT, &num_frames); + m_info.num_frames(num_frames); + + int sample_rate = 0; + gst_structure_get_int(caps_struct, "rate", &sample_rate); + m_info.sample_rate(sample_rate); + + int num_channels = 0; + gst_structure_get_int(caps_struct, "channels", &num_channels); + m_info.num_channels(num_channels); + + m_info.format(create_runtime_format(caps_struct)); +} + +//---------------------------------------------------------------------------------------------------------------------- + +pcm::runtime_format gstreamer_file_source::create_runtime_format(GstStructure* caps_struct) +{ + const gchar* format = gst_structure_get_string(caps_struct, "format"); + pcm::number_type number_type = gst_format_char_to_number_type(format[0]); + auto srcDepth = std::atol(format + 1); + auto endian = (strcmp(format, "BE") == 0) ? pcm::big_endian : pcm::little_endian; + return pcm::runtime_format(number_type, srcDepth, endian); +} + +//---------------------------------------------------------------------------------------------------------------------- + +pcm::number_type gstreamer_file_source::gst_format_char_to_number_type(const gchar format) +{ + if(format == 'U') + return pcm::unsigned_integer; + else if(format == 'F') + return pcm::floating_point; + + return pcm::signed_integer; +} + +//---------------------------------------------------------------------------------------------------------------------- + +void gstreamer_file_source::onPadAdded(GstElement* element, GstPad* pad, GstElement* sink) +{ + tGstPtr sinkpad(gst_element_get_static_pad(sink, "sink"), gst_object_unref); + gst_pad_link(pad, sinkpad.get()); +} + +//---------------------------------------------------------------------------------------------------------------------- + +std::streampos gstreamer_file_source::seek(offset_type off, BOOST_IOS::seekdir way ) +{ + int64_t newPosition = 0; + + if (way == BOOST_IOS::seekdir::_S_beg) + { + newPosition = off; + } + else if (way == BOOST_IOS::seekdir::_S_cur) + { + newPosition = m_position + off; + } + else if (way == BOOST_IOS::seekdir::_S_end) + { + int64_t end = 0; + if(!gst_element_query_duration (m_pipeline.get(), GST_FORMAT_DEFAULT, &end)) + { + throw std::runtime_error( "gstreamer_file_source: seeking from end of file impossible - could not query duration" ); + } + newPosition = end + off; + } + + if(newPosition != m_position) + { + m_position = newPosition; + + if(!gst_element_seek_simple(m_pipeline.get(), GST_FORMAT_DEFAULT, (GstSeekFlags)(GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE), m_position)) + { + throw std::runtime_error( "gstreamer_file_source: seeking failed" ); + } + } + + return m_position; +} + +//---------------------------------------------------------------------------------------------------------------------- + +std::streamsize gstreamer_file_source::read(char* dst, std::streamsize size) +{ + auto bytesPerFrame = m_info.bytes_per_frame(); + auto bufferedBytes = m_ring_buffer->pull(dst, size * bytesPerFrame); + auto bufferedFrames = bufferedBytes / bytesPerFrame; + dst += bufferedFrames; + size -= bufferedFrames; + + tGstPtr sink(gst_bin_get_by_name(GST_BIN(m_pipeline.get()), "sink"), gst_object_unref); + GstAppSink *app_sink = reinterpret_cast(sink.get()); + + tGstPtr sample(gst_app_sink_pull_sample(app_sink), (GUnref) gst_sample_unref); + + if(sample) + { + auto buffer = gst_sample_get_buffer(sample.get()); + + GstMapInfo mapped; + + if(gst_buffer_map(buffer, &mapped, GST_MAP_READ)) + { + auto numBytesRequested = size * bytesPerFrame; + auto for_now = std::min(mapped.size, numBytesRequested); + + std::copy(mapped.data, mapped.data + for_now, dst); + + m_ring_buffer->push((char*) mapped.data + for_now, mapped.size - for_now); + bufferedFrames += for_now / bytesPerFrame; + + gst_buffer_unmap(buffer, &mapped); + } + } + + m_position += bufferedFrames; + return bufferedFrames; +} + diff --git a/audiostream/src/ni/media/audio/source/gstreamer_file_source.h b/audiostream/src/ni/media/audio/source/gstreamer_file_source.h new file mode 100644 index 00000000..88bbd40c --- /dev/null +++ b/audiostream/src/ni/media/audio/source/gstreamer_file_source.h @@ -0,0 +1,84 @@ +// +// Copyright (c) 2017 Native Instruments GmbH, Berlin +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +#pragma once + +#include +#include +#include +#include + +#include +#include + +namespace detail +{ + template struct RingBuffer; +} + +class gstreamer_file_source +{ + public: + //---------------------------------------------------------------------------------------------------------------------- + + using offset_type = boost::iostreams::stream_offset; + + using FrameRange = boost::icl::right_open_interval; + using FrameRangeSet = boost::icl::interval_set; + + template + using MfTypePtr = std::unique_ptr>; + + //---------------------------------------------------------------------------------------------------------------------- + + explicit gstreamer_file_source(const std::string& path, audio::ifstream_info::container_type container, size_t stream = 0); + ~gstreamer_file_source(); + + audio::ifstream_info info() const + { + return m_info; + } + + std::streampos seek(offset_type, BOOST_IOS::seekdir ); + std::streamsize read( char*, std::streamsize ); + + private: + static void init_gstreamer(); + static pcm::runtime_format create_runtime_format(GstStructure* caps_struct); + static pcm::number_type gst_format_char_to_number_type(const gchar format); + + void setup_source(const std::string& path, audio::ifstream_info::container_type container); + GstElement* prepare_pipeline(const std::string& path); + void preroll_pipeline(); + void fill_format_info(GstStructure *caps_struct, audio::ifstream_info::container_type container); + + static void onPadAdded(GstElement* element, GstPad* pad, GstElement* sink); + + audio::ifstream_info m_info; + using GUnref = void(*)(gpointer); + template using tGstPtr = std::unique_ptr; + tGstPtr m_pipeline; + + using RingBuffer = detail::RingBuffer; + std::unique_ptr m_ring_buffer; + int64_t m_position = 0; + }; diff --git a/audiostream/src/ni/media/audio/source/mp3_file_source.cpp b/audiostream/src/ni/media/audio/source/mp3_file_source.cpp index 09cddf1c..e22f27ec 100644 --- a/audiostream/src/ni/media/audio/source/mp3_file_source.cpp +++ b/audiostream/src/ni/media/audio/source/mp3_file_source.cpp @@ -26,6 +26,8 @@ #include "core_audio_file_source.h" #elif BOOST_OS_WINDOWS #include "media_foundation_file_source.h" +#elif BOOST_OS_LINUX +#include "gstreamer_file_source.h" #endif //---------------------------------------------------------------------------------------------------------------------- @@ -55,6 +57,8 @@ void mp3_file_source::open( const std::string& path ) m_impl.reset( new core_audio_file_source( path, audio::ifstream_info::container_type::mp3 ) ); #elif BOOST_OS_WINDOWS m_impl.reset( new media_foundation_file_source( path, audio::ifstream_info::container_type::mp3 ) ); +#elif BOOST_OS_LINUX + m_impl.reset( new gstreamer_file_source( path, audio::ifstream_info::container_type::mp3 ) ); #endif } diff --git a/audiostream/src/ni/media/audio/source/mp3_file_source.h b/audiostream/src/ni/media/audio/source/mp3_file_source.h index e3340d20..39dc7eb3 100644 --- a/audiostream/src/ni/media/audio/source/mp3_file_source.h +++ b/audiostream/src/ni/media/audio/source/mp3_file_source.h @@ -61,5 +61,7 @@ class mp3_file_source std::unique_ptr m_impl; #elif BOOST_OS_WINDOWS std::unique_ptr m_impl; +#elif BOOST_OS_LINUX + std::unique_ptr m_impl; #endif }; diff --git a/audiostream/src/ni/media/audio/source/mp4_file_source.cpp b/audiostream/src/ni/media/audio/source/mp4_file_source.cpp index c68f9db0..259fae63 100644 --- a/audiostream/src/ni/media/audio/source/mp4_file_source.cpp +++ b/audiostream/src/ni/media/audio/source/mp4_file_source.cpp @@ -26,6 +26,8 @@ #include "core_audio_file_source.h" #elif BOOST_OS_WINDOWS #include "media_foundation_file_source.h" +#elif BOOST_OS_LINUX +#include "gstreamer_file_source.h" #endif //---------------------------------------------------------------------------------------------------------------------- @@ -55,6 +57,8 @@ void mp4_file_source::open( const std::string& path, size_t stream ) m_impl.reset( new core_audio_file_source( path, audio::ifstream_info::container_type::mp4, stream ) ); #elif BOOST_OS_WINDOWS m_impl.reset( new media_foundation_file_source( path, audio::ifstream_info::container_type::mp4, stream ) ); +#elif BOOST_OS_LINUX + m_impl.reset( new gstreamer_file_source( path, audio::ifstream_info::container_type::mp4 ) ); #endif } diff --git a/audiostream/src/ni/media/audio/source/mp4_file_source.h b/audiostream/src/ni/media/audio/source/mp4_file_source.h index e5fe4679..26ec3b91 100644 --- a/audiostream/src/ni/media/audio/source/mp4_file_source.h +++ b/audiostream/src/ni/media/audio/source/mp4_file_source.h @@ -61,5 +61,7 @@ class mp4_file_source std::unique_ptr m_impl; #elif BOOST_OS_WINDOWS std::unique_ptr m_impl; +#elif BOOST_OS_LINUX + std::unique_ptr m_impl; #endif }; diff --git a/audiostream/src/ni/media/audio/source/wma_file_source.cpp b/audiostream/src/ni/media/audio/source/wma_file_source.cpp index aa4ddec7..61821808 100644 --- a/audiostream/src/ni/media/audio/source/wma_file_source.cpp +++ b/audiostream/src/ni/media/audio/source/wma_file_source.cpp @@ -22,7 +22,11 @@ #include +#if BOOST_OS_WINDOWS #include "media_foundation_file_source.h" +#elif BOOST_OS_LINUX +#include "gstreamer_file_source.h" +#endif //---------------------------------------------------------------------------------------------------------------------- @@ -47,7 +51,11 @@ wma_file_source::wma_file_source( const std::string& path ) void wma_file_source::open( const std::string& path ) { +#if BOOST_OS_WINDOWS m_impl.reset( new media_foundation_file_source( path, audio::ifstream_info::container_type::wma ) ); +#elif BOOST_OS_LINUX + m_impl.reset( new gstreamer_file_source( path, audio::ifstream_info::container_type::wma ) ); +#endif } //---------------------------------------------------------------------------------------------------------------------- diff --git a/audiostream/src/ni/media/audio/source/wma_file_source.h b/audiostream/src/ni/media/audio/source/wma_file_source.h index 30c09a6b..050a9701 100644 --- a/audiostream/src/ni/media/audio/source/wma_file_source.h +++ b/audiostream/src/ni/media/audio/source/wma_file_source.h @@ -26,6 +26,7 @@ #include #include +#include #include @@ -57,5 +58,9 @@ class wma_file_source auto info() const -> info_type; private: +#if BOOST_OS_WINDOWS std::unique_ptr m_impl; +#elif BOOST_OS_LINUX + std::unique_ptr m_impl; +#endif }; diff --git a/cmake/FindGObject.cmake b/cmake/FindGObject.cmake new file mode 100644 index 00000000..afb8cc8d --- /dev/null +++ b/cmake/FindGObject.cmake @@ -0,0 +1,5 @@ +find_package (PkgConfig REQUIRED) +pkg_check_modules (GOBJECT2 REQUIRED gobject-2.0) + +include_directories (${GOBJECT2_INCLUDE_DIRS}) +link_directories (${GOBJECT2_LIBRARY_DIRS}) diff --git a/cmake/FindGStreamer.cmake b/cmake/FindGStreamer.cmake new file mode 100644 index 00000000..066152b1 --- /dev/null +++ b/cmake/FindGStreamer.cmake @@ -0,0 +1,23 @@ +include(FindPackageHandleStandardArgs) + +find_package(PkgConfig REQUIRED) + +pkg_check_modules (GSTREAMER REQUIRED gstreamer-1.0) +find_library(GSTREAMER_LIBRARY NAMES gstreamer-1.0) + +find_package_handle_standard_args(GSTREAMER DEFAULT_MSG GSTREAMER_INCLUDE_DIRS GSTREAMER_LIBRARY) +mark_as_advanced(GSTREAMER_INCLUDE_DIRS GSTREAMER_LIBRARY) + + +if( GSTREAMER_FOUND ) + if( NOT TARGET GSTREAMER::gstreamer ) + add_library(GSTREAMER::gstreamer SHARED IMPORTED) + set_target_properties(GSTREAMER::gstreamer PROPERTIES + IMPORTED_LINK_INTERFACE_LANGUAGES "C" + IMPORTED_LOCATION ${GSTREAMER_LIBRARY} + INTERFACE_INCLUDE_DIRECTORIES "${GSTREAMER_INCLUDE_DIRS}" + INTERFACE_LINK_LIBRARIES "${GSTREAMER_LIBRARY}" + ) + endif() +endif() + diff --git a/cmake/FindGStreamerApp.cmake b/cmake/FindGStreamerApp.cmake new file mode 100644 index 00000000..ecb9d014 --- /dev/null +++ b/cmake/FindGStreamerApp.cmake @@ -0,0 +1,23 @@ +include(FindPackageHandleStandardArgs) + +find_package(PkgConfig REQUIRED) + +pkg_check_modules (GSTREAMERAPP REQUIRED gstreamer-app-1.0) +find_library(GSTREAMERAPP_LIBRARY NAMES gstapp-1.0) + +find_package_handle_standard_args(GSTREAMERAPP DEFAULT_MSG GSTREAMERAPP_INCLUDE_DIRS GSTREAMERAPP_LIBRARY) +mark_as_advanced(GSTREAMERAPP_INCLUDE_DIRS GSTREAMERAPP_LIBRARY) + + +if( GSTREAMERAPP_FOUND ) + if( NOT TARGET GSTREAMERAPP::gstreamerapp ) + add_library(GSTREAMERAPP::gstreamerapp SHARED IMPORTED) + set_target_properties(GSTREAMERAPP::gstreamerapp PROPERTIES + IMPORTED_LINK_INTERFACE_LANGUAGES "C" + IMPORTED_LOCATION ${GSTREAMERAPP_LIBRARY} + INTERFACE_INCLUDE_DIRECTORIES "${GSTREAMERAPP_INCLUDE_DIRS}" + INTERFACE_LINK_LIBRARIES "${GSTREAMERAPP_LIBRARY}" + ) + endif() +endif() + diff --git a/cmake/FindGlib.cmake b/cmake/FindGlib.cmake new file mode 100644 index 00000000..383f0712 --- /dev/null +++ b/cmake/FindGlib.cmake @@ -0,0 +1,5 @@ +find_package (PkgConfig REQUIRED) +pkg_check_modules (GLIB2 REQUIRED glib-2.0) + +include_directories (${GLIB2_INCLUDE_DIRS}) +link_directories (${GLIB2_LIBRARY_DIRS}) From 5274d99799585de6a75e9318bd07d945a6094c76 Mon Sep 17 00:00:00 2001 From: Henry Hoegelow Date: Fri, 22 Dec 2017 17:14:27 +0100 Subject: [PATCH 02/28] fixed some tests, still some failing --- .../audio/source/gstreamer_file_source.cpp | 110 +++++++++++------- .../audio/source/gstreamer_file_source.h | 3 +- 2 files changed, 70 insertions(+), 43 deletions(-) diff --git a/audiostream/src/ni/media/audio/source/gstreamer_file_source.cpp b/audiostream/src/ni/media/audio/source/gstreamer_file_source.cpp index 170ccab5..5e929ef2 100644 --- a/audiostream/src/ni/media/audio/source/gstreamer_file_source.cpp +++ b/audiostream/src/ni/media/audio/source/gstreamer_file_source.cpp @@ -44,18 +44,22 @@ namespace detail { static constexpr size_t size = s; static constexpr size_t mask = (s - 1); - std::vector m_buffer; + std::array m_buffer; uint64_t m_write_head = 0; uint64_t m_read_head = 0; void push(const T* data, size_t count) { + auto filled = m_write_head - m_read_head; + auto space = s - filled; + assert(count <= space); + if(count) { auto idx = m_write_head & mask; auto space = size - idx; auto for_now = std::min(space, count); - std::copy(data, data + for_now, m_buffer.begin() + idx); + std::copy(data, data + for_now, m_buffer.data() + idx); m_write_head += for_now; push(data + for_now, count - for_now); } @@ -70,12 +74,17 @@ namespace detail auto idx = m_read_head & mask; auto space = size - idx; auto for_now = std::min(space, count); - std::copy(m_buffer.begin() + idx, m_buffer.begin() + idx + for_now, data); + std::copy(m_buffer.data() + idx, m_buffer.data() + idx + for_now, data); m_read_head += for_now; return for_now + pull(data + for_now, count - for_now); } return 0; } + + void flush() + { + m_read_head = m_write_head = 0; + } }; } @@ -92,6 +101,24 @@ gstreamer_file_source::gstreamer_file_source(const std::string& path, audio::ifs gstreamer_file_source::~gstreamer_file_source() { + gst_element_set_state(m_pipeline.get(), GST_STATE_NULL); + wait_for_async_operation(); + m_pipeline.reset(); +} + +//---------------------------------------------------------------------------------------------------------------------- + +GstState gstreamer_file_source::wait_for_async_operation() +{ + GstState current_state = GST_STATE_VOID_PENDING; + GstState pending_state = GST_STATE_VOID_PENDING; + + while(gst_element_get_state(m_pipeline.get(), ¤t_state, &pending_state, GST_MSECOND) == GST_STATE_CHANGE_ASYNC) + { + g_main_context_iteration(nullptr, FALSE); + } + + return current_state; } //---------------------------------------------------------------------------------------------------------------------- @@ -144,23 +171,15 @@ GstElement* gstreamer_file_source::prepare_pipeline(const std::string& path) void gstreamer_file_source::preroll_pipeline() { - gst_element_set_state(m_pipeline.get(), GST_STATE_PLAYING); - GstState current_state = GST_STATE_VOID_PENDING; - GstState pending_state = GST_STATE_VOID_PENDING; + gst_element_set_state(m_pipeline.get(), GST_STATE_PAUSED); - while(gst_element_get_state(m_pipeline.get(), ¤t_state, &pending_state, GST_MSECOND) == GST_STATE_CHANGE_ASYNC) - { - g_main_context_iteration(nullptr, FALSE); - } + if(wait_for_async_operation() != GST_STATE_PAUSED) + throw std::runtime_error( "gstreamer_file_source: pipeline doesn't preroll into paused state" ); - std::cerr << "Pipeline is in state: " << current_state << std::endl; + gst_element_set_state(m_pipeline.get(), GST_STATE_PLAYING); - if(current_state != GST_STATE_PLAYING) - { - auto res = gst_element_get_state(m_pipeline.get(), ¤t_state, &pending_state, GST_MSECOND); - std::cerr << "Pipeline does not preroll: " << res << " " << current_state << std::endl; - throw std::runtime_error( "gstreamer_file_source: pipeline doesn't preroll" ); - } + if(wait_for_async_operation() != GST_STATE_PLAYING) + throw std::runtime_error( "gstreamer_file_source: pipeline doesn't preroll into playing state" ); } //---------------------------------------------------------------------------------------------------------------------- @@ -221,6 +240,8 @@ void gstreamer_file_source::onPadAdded(GstElement* element, GstPad* pad, GstElem std::streampos gstreamer_file_source::seek(offset_type off, BOOST_IOS::seekdir way ) { + assert( 0 == off % m_info.bytes_per_frame() ); + int64_t newPosition = 0; if (way == BOOST_IOS::seekdir::_S_beg) @@ -234,18 +255,19 @@ std::streampos gstreamer_file_source::seek(offset_type off, BOOST_IOS::seekdir w else if (way == BOOST_IOS::seekdir::_S_end) { int64_t end = 0; - if(!gst_element_query_duration (m_pipeline.get(), GST_FORMAT_DEFAULT, &end)) + if(!gst_element_query_duration (m_pipeline.get(), GST_FORMAT_BYTES, &end)) { throw std::runtime_error( "gstreamer_file_source: seeking from end of file impossible - could not query duration" ); } newPosition = end + off; } - if(newPosition != m_position) + if(m_position != newPosition) { m_position = newPosition; + m_ring_buffer->flush(); - if(!gst_element_seek_simple(m_pipeline.get(), GST_FORMAT_DEFAULT, (GstSeekFlags)(GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE), m_position)) + if(!gst_element_seek_simple(m_pipeline.get(), GST_FORMAT_BYTES, (GstSeekFlags)(GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE), m_position)) { throw std::runtime_error( "gstreamer_file_source: seeking failed" ); } @@ -256,40 +278,44 @@ std::streampos gstreamer_file_source::seek(offset_type off, BOOST_IOS::seekdir w //---------------------------------------------------------------------------------------------------------------------- -std::streamsize gstreamer_file_source::read(char* dst, std::streamsize size) +std::streamsize gstreamer_file_source::read(char* dst, std::streamsize numBytesRequested) { - auto bytesPerFrame = m_info.bytes_per_frame(); - auto bufferedBytes = m_ring_buffer->pull(dst, size * bytesPerFrame); - auto bufferedFrames = bufferedBytes / bytesPerFrame; - dst += bufferedFrames; - size -= bufferedFrames; - - tGstPtr sink(gst_bin_get_by_name(GST_BIN(m_pipeline.get()), "sink"), gst_object_unref); - GstAppSink *app_sink = reinterpret_cast(sink.get()); + auto read_bytes = m_ring_buffer->pull(dst, numBytesRequested); - tGstPtr sample(gst_app_sink_pull_sample(app_sink), (GUnref) gst_sample_unref); + dst += read_bytes; + numBytesRequested -= read_bytes; + m_position += read_bytes; - if(sample) + if(numBytesRequested) { - auto buffer = gst_sample_get_buffer(sample.get()); + tGstPtr sink(gst_bin_get_by_name(GST_BIN(m_pipeline.get()), "sink"), gst_object_unref); + GstAppSink *app_sink = reinterpret_cast(sink.get()); - GstMapInfo mapped; + tGstPtr sample(gst_app_sink_pull_sample(app_sink), (GUnref) gst_sample_unref); - if(gst_buffer_map(buffer, &mapped, GST_MAP_READ)) + if(sample) { - auto numBytesRequested = size * bytesPerFrame; - auto for_now = std::min(mapped.size, numBytesRequested); + auto buffer = gst_sample_get_buffer(sample.get()); + + GstMapInfo mapped; - std::copy(mapped.data, mapped.data + for_now, dst); + if(gst_buffer_map(buffer, &mapped, GST_MAP_READ)) + { + auto for_now = std::min(mapped.size, numBytesRequested); + std::copy(mapped.data, mapped.data + for_now, dst); + read_bytes += for_now; + dst += for_now; + numBytesRequested -= for_now; + m_position += for_now; - m_ring_buffer->push((char*) mapped.data + for_now, mapped.size - for_now); - bufferedFrames += for_now / bytesPerFrame; + m_ring_buffer->push((char*) mapped.data + for_now, mapped.size - for_now); + gst_buffer_unmap(buffer, &mapped); - gst_buffer_unmap(buffer, &mapped); + return read_bytes + read(dst, numBytesRequested); + } } } - m_position += bufferedFrames; - return bufferedFrames; + return read_bytes; } diff --git a/audiostream/src/ni/media/audio/source/gstreamer_file_source.h b/audiostream/src/ni/media/audio/source/gstreamer_file_source.h index 88bbd40c..9575c630 100644 --- a/audiostream/src/ni/media/audio/source/gstreamer_file_source.h +++ b/audiostream/src/ni/media/audio/source/gstreamer_file_source.h @@ -69,6 +69,7 @@ class gstreamer_file_source void setup_source(const std::string& path, audio::ifstream_info::container_type container); GstElement* prepare_pipeline(const std::string& path); void preroll_pipeline(); + GstState wait_for_async_operation(); void fill_format_info(GstStructure *caps_struct, audio::ifstream_info::container_type container); static void onPadAdded(GstElement* element, GstPad* pad, GstElement* sink); @@ -78,7 +79,7 @@ class gstreamer_file_source template using tGstPtr = std::unique_ptr; tGstPtr m_pipeline; - using RingBuffer = detail::RingBuffer; + using RingBuffer = detail::RingBuffer; std::unique_ptr m_ring_buffer; int64_t m_position = 0; }; From 9130479b81711e598c93a91e30b4134712c0d524 Mon Sep 17 00:00:00 2001 From: Henry Hoegelow Date: Fri, 5 Jan 2018 16:23:02 +0100 Subject: [PATCH 03/28] improve readability --- .../ni/media/audio/source/gstreamer_file_source.cpp | 10 +++++++++- .../src/ni/media/audio/source/gstreamer_file_source.h | 1 + 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/audiostream/src/ni/media/audio/source/gstreamer_file_source.cpp b/audiostream/src/ni/media/audio/source/gstreamer_file_source.cpp index 5e929ef2..930aed43 100644 --- a/audiostream/src/ni/media/audio/source/gstreamer_file_source.cpp +++ b/audiostream/src/ni/media/audio/source/gstreamer_file_source.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include @@ -279,6 +280,13 @@ std::streampos gstreamer_file_source::seek(offset_type off, BOOST_IOS::seekdir w //---------------------------------------------------------------------------------------------------------------------- std::streamsize gstreamer_file_source::read(char* dst, std::streamsize numBytesRequested) +{ + return recursive_read(dst, numBytesRequested); +} + +//---------------------------------------------------------------------------------------------------------------------- + +std::streamsize gstreamer_file_source::recursive_read(char* dst, std::streamsize numBytesRequested) { auto read_bytes = m_ring_buffer->pull(dst, numBytesRequested); @@ -311,7 +319,7 @@ std::streamsize gstreamer_file_source::read(char* dst, std::streamsize numBytesR m_ring_buffer->push((char*) mapped.data + for_now, mapped.size - for_now); gst_buffer_unmap(buffer, &mapped); - return read_bytes + read(dst, numBytesRequested); + return read_bytes + recursive_read(dst, numBytesRequested); } } } diff --git a/audiostream/src/ni/media/audio/source/gstreamer_file_source.h b/audiostream/src/ni/media/audio/source/gstreamer_file_source.h index 9575c630..f3cb9f2f 100644 --- a/audiostream/src/ni/media/audio/source/gstreamer_file_source.h +++ b/audiostream/src/ni/media/audio/source/gstreamer_file_source.h @@ -71,6 +71,7 @@ class gstreamer_file_source void preroll_pipeline(); GstState wait_for_async_operation(); void fill_format_info(GstStructure *caps_struct, audio::ifstream_info::container_type container); + std::streamsize recursive_read(char* dst, std::streamsize numBytesRequested); static void onPadAdded(GstElement* element, GstPad* pad, GstElement* sink); From 693b84dafe322c3916bf0f2d8257c02ec77fff22 Mon Sep 17 00:00:00 2001 From: Henry Hoegelow Date: Mon, 8 Jan 2018 14:44:14 +0100 Subject: [PATCH 04/28] add gstreamer dependencies for ci run --- .travis.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.travis.yml b/.travis.yml index e5ce5e32..df2c579c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,6 +25,13 @@ matrix: - libogg-dev - libvorbis-dev - libflac++-dev + - gstreamer1.0-libav-dev + - gstreamer1.0-nice-dev + - gstreamer1.0-plugins-base-dev + - gstreamer1.0-plugins-good-dev + - gstreamer1.0-plugins-ugly-dev + - gstreamer1.0-plugins-bad-dev + - gstreamer1.0-plugins-bad-faad-dev env: MATRIX_EVAL="CONFIG=Release && CXX=g++-5" From e9254c116a255c46204b66151b2ee62368ec1a27 Mon Sep 17 00:00:00 2001 From: Henry Hoegelow Date: Mon, 8 Jan 2018 14:48:51 +0100 Subject: [PATCH 05/28] fix intendation issue --- .travis.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index df2c579c..a008a297 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,13 +25,13 @@ matrix: - libogg-dev - libvorbis-dev - libflac++-dev - - gstreamer1.0-libav-dev - - gstreamer1.0-nice-dev - - gstreamer1.0-plugins-base-dev - - gstreamer1.0-plugins-good-dev - - gstreamer1.0-plugins-ugly-dev - - gstreamer1.0-plugins-bad-dev - - gstreamer1.0-plugins-bad-faad-dev + - gstreamer1.0-libav-dev + - gstreamer1.0-nice-dev + - gstreamer1.0-plugins-base-dev + - gstreamer1.0-plugins-good-dev + - gstreamer1.0-plugins-ugly-dev + - gstreamer1.0-plugins-bad-dev + - gstreamer1.0-plugins-bad-faad-dev env: MATRIX_EVAL="CONFIG=Release && CXX=g++-5" From 07341d1f4ecf4c02fed1b0cf6c61c12a3e52b544 Mon Sep 17 00:00:00 2001 From: Henry Hoegelow Date: Mon, 8 Jan 2018 15:02:37 +0100 Subject: [PATCH 06/28] blindflight: configureing ubuntu packages not having ubuntu --- .travis.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index a008a297..14f4a17b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,13 +25,13 @@ matrix: - libogg-dev - libvorbis-dev - libflac++-dev - - gstreamer1.0-libav-dev - - gstreamer1.0-nice-dev - - gstreamer1.0-plugins-base-dev - - gstreamer1.0-plugins-good-dev - - gstreamer1.0-plugins-ugly-dev - - gstreamer1.0-plugins-bad-dev - - gstreamer1.0-plugins-bad-faad-dev + - gstreamer1.0-libav + - gstreamer1.0-plugins-base + - gstreamer1.0-plugins-good + - gstreamer1.0-plugins-ugly + - gstreamer1.0-plugins-bad + - gstreamer1.0-plugins-bad-faad + - libgstreamer1.0-dev env: MATRIX_EVAL="CONFIG=Release && CXX=g++-5" From 8ec93ffdfb2a0b51b85f622f6a9fd0f7f47db0a1 Mon Sep 17 00:00:00 2001 From: Henry Hoegelow Date: Mon, 8 Jan 2018 15:14:50 +0100 Subject: [PATCH 07/28] blindflight: configureing ubuntu packages not having ubuntu --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 14f4a17b..7dd5d742 100644 --- a/.travis.yml +++ b/.travis.yml @@ -32,6 +32,7 @@ matrix: - gstreamer1.0-plugins-bad - gstreamer1.0-plugins-bad-faad - libgstreamer1.0-dev + - libgstreamer-plugins-base1.0-dev env: MATRIX_EVAL="CONFIG=Release && CXX=g++-5" From f46c5d2d4a1d7e2020c291618a389cc664431c76 Mon Sep 17 00:00:00 2001 From: Henry Hoegelow Date: Thu, 11 Jan 2018 08:12:25 +0100 Subject: [PATCH 08/28] fix formatting (apply clang-format) --- .../audio/source/gstreamer_file_source.cpp | 353 +++++++++--------- .../audio/source/gstreamer_file_source.h | 50 +-- 2 files changed, 207 insertions(+), 196 deletions(-) diff --git a/audiostream/src/ni/media/audio/source/gstreamer_file_source.cpp b/audiostream/src/ni/media/audio/source/gstreamer_file_source.cpp index 930aed43..00497199 100644 --- a/audiostream/src/ni/media/audio/source/gstreamer_file_source.cpp +++ b/audiostream/src/ni/media/audio/source/gstreamer_file_source.cpp @@ -30,300 +30,307 @@ #include #include +#include #include #include -#include #include -#include +#include #include namespace detail { - template - struct RingBuffer +template +struct RingBuffer +{ + static constexpr size_t size = s; + static constexpr size_t mask = ( s - 1 ); + std::array m_buffer; + uint64_t m_write_head = 0; + uint64_t m_read_head = 0; + + void push( const T* data, size_t count ) { - static constexpr size_t size = s; - static constexpr size_t mask = (s - 1); - std::array m_buffer; - uint64_t m_write_head = 0; - uint64_t m_read_head = 0; + auto filled = m_write_head - m_read_head; + auto space = s - filled; + assert( count <= space ); - void push(const T* data, size_t count) + if ( count ) { - auto filled = m_write_head - m_read_head; - auto space = s - filled; - assert(count <= space); - - if(count) - { - auto idx = m_write_head & mask; - auto space = size - idx; - auto for_now = std::min(space, count); - std::copy(data, data + for_now, m_buffer.data() + idx); + auto idx = m_write_head & mask; + auto space = size - idx; + auto for_now = std::min( space, count ); + std::copy( data, data + for_now, m_buffer.data() + idx ); m_write_head += for_now; - push(data + for_now, count - for_now); - } + push( data + for_now, count - for_now ); } + } + + size_t pull( T* data, size_t count ) + { + count = std::min( count, m_write_head - m_read_head ); - size_t pull(T* data, size_t count) + if ( count ) { - count = std::min(count, m_write_head - m_read_head); - - if(count) - { - auto idx = m_read_head & mask; - auto space = size - idx; - auto for_now = std::min(space, count); - std::copy(m_buffer.data() + idx, m_buffer.data() + idx + for_now, data); + auto idx = m_read_head & mask; + auto space = size - idx; + auto for_now = std::min( space, count ); + std::copy( m_buffer.data() + idx, m_buffer.data() + idx + for_now, data ); m_read_head += for_now; - return for_now + pull(data + for_now, count - for_now); - } - return 0; + return for_now + pull( data + for_now, count - for_now ); } + return 0; + } - void flush() - { - m_read_head = m_write_head = 0; - } - }; + void flush() + { + m_read_head = m_write_head = 0; + } +}; } //---------------------------------------------------------------------------------------------------------------------- -gstreamer_file_source::gstreamer_file_source(const std::string& path, audio::ifstream_info::container_type container, size_t stream) : - m_pipeline(nullptr, gst_object_unref), - m_ring_buffer(new RingBuffer()) +gstreamer_file_source::gstreamer_file_source( const std::string& path, + audio::ifstream_info::container_type container, + size_t stream ) +: m_pipeline( nullptr, gst_object_unref ) +, m_ring_buffer( new RingBuffer() ) { - init_gstreamer(); - setup_source(path, container); + init_gstreamer(); + setup_source( path, container ); } //---------------------------------------------------------------------------------------------------------------------- gstreamer_file_source::~gstreamer_file_source() { - gst_element_set_state(m_pipeline.get(), GST_STATE_NULL); - wait_for_async_operation(); - m_pipeline.reset(); + gst_element_set_state( m_pipeline.get(), GST_STATE_NULL ); + wait_for_async_operation(); + m_pipeline.reset(); } //---------------------------------------------------------------------------------------------------------------------- GstState gstreamer_file_source::wait_for_async_operation() { - GstState current_state = GST_STATE_VOID_PENDING; - GstState pending_state = GST_STATE_VOID_PENDING; + GstState current_state = GST_STATE_VOID_PENDING; + GstState pending_state = GST_STATE_VOID_PENDING; - while(gst_element_get_state(m_pipeline.get(), ¤t_state, &pending_state, GST_MSECOND) == GST_STATE_CHANGE_ASYNC) - { - g_main_context_iteration(nullptr, FALSE); - } + while ( gst_element_get_state( m_pipeline.get(), ¤t_state, &pending_state, GST_MSECOND ) + == GST_STATE_CHANGE_ASYNC ) + { + g_main_context_iteration( nullptr, FALSE ); + } - return current_state; + return current_state; } //---------------------------------------------------------------------------------------------------------------------- void gstreamer_file_source::init_gstreamer() { - static bool gstreamer_initialized = false; + static bool gstreamer_initialized = false; - if(!std::exchange(gstreamer_initialized, true)) - { - gst_init(0, nullptr); - } + if ( !std::exchange( gstreamer_initialized, true ) ) + { + gst_init( 0, nullptr ); + } } //---------------------------------------------------------------------------------------------------------------------- -void gstreamer_file_source::setup_source(const std::string& path, audio::ifstream_info::container_type container) +void gstreamer_file_source::setup_source( const std::string& path, audio::ifstream_info::container_type container ) { - GstElement* sink = prepare_pipeline(path); - auto sinkpad = gst_element_get_static_pad(sink, "sink"); - auto caps = gst_pad_get_current_caps(sinkpad); - auto caps_struct = gst_caps_get_structure(caps, 0); - fill_format_info(caps_struct, container); + GstElement* sink = prepare_pipeline( path ); + auto sinkpad = gst_element_get_static_pad( sink, "sink" ); + auto caps = gst_pad_get_current_caps( sinkpad ); + auto caps_struct = gst_caps_get_structure( caps, 0 ); + fill_format_info( caps_struct, container ); } //---------------------------------------------------------------------------------------------------------------------- -GstElement* gstreamer_file_source::prepare_pipeline(const std::string& path) +GstElement* gstreamer_file_source::prepare_pipeline( const std::string& path ) { - m_pipeline.reset(gst_pipeline_new("pipeline")); + m_pipeline.reset( gst_pipeline_new( "pipeline" ) ); - GstElement* source = gst_element_factory_make("filesrc", "source"); - GstElement* decodebin = gst_element_factory_make("decodebin", "decoder"); - GstElement* queue = gst_element_factory_make("queue", "queue"); - GstElement* sink = gst_element_factory_make("appsink", "sink"); + GstElement* source = gst_element_factory_make( "filesrc", "source" ); + GstElement* decodebin = gst_element_factory_make( "decodebin", "decoder" ); + GstElement* queue = gst_element_factory_make( "queue", "queue" ); + GstElement* sink = gst_element_factory_make( "appsink", "sink" ); - gst_bin_add_many(GST_BIN(m_pipeline.get()), source, decodebin, queue, sink, nullptr); - gst_element_link_many(source, decodebin, NULL); - gst_element_link_many(queue, sink, NULL); + gst_bin_add_many( GST_BIN( m_pipeline.get() ), source, decodebin, queue, sink, nullptr ); + gst_element_link_many( source, decodebin, NULL ); + gst_element_link_many( queue, sink, NULL ); - g_object_set(source, "location", path.c_str(), NULL); + g_object_set( source, "location", path.c_str(), NULL ); - g_signal_connect(decodebin, "pad-added", G_CALLBACK(&onPadAdded), queue); + g_signal_connect( decodebin, "pad-added", G_CALLBACK( &onPadAdded ), queue ); - preroll_pipeline(); - return sink; + preroll_pipeline(); + return sink; } //---------------------------------------------------------------------------------------------------------------------- void gstreamer_file_source::preroll_pipeline() { - gst_element_set_state(m_pipeline.get(), GST_STATE_PAUSED); + gst_element_set_state( m_pipeline.get(), GST_STATE_PAUSED ); - if(wait_for_async_operation() != GST_STATE_PAUSED) - throw std::runtime_error( "gstreamer_file_source: pipeline doesn't preroll into paused state" ); + if ( wait_for_async_operation() != GST_STATE_PAUSED ) + throw std::runtime_error( "gstreamer_file_source: pipeline doesn't preroll into paused state" ); - gst_element_set_state(m_pipeline.get(), GST_STATE_PLAYING); + gst_element_set_state( m_pipeline.get(), GST_STATE_PLAYING ); - if(wait_for_async_operation() != GST_STATE_PLAYING) - throw std::runtime_error( "gstreamer_file_source: pipeline doesn't preroll into playing state" ); + if ( wait_for_async_operation() != GST_STATE_PLAYING ) + throw std::runtime_error( "gstreamer_file_source: pipeline doesn't preroll into playing state" ); } //---------------------------------------------------------------------------------------------------------------------- -void gstreamer_file_source::fill_format_info(GstStructure *caps_struct, audio::ifstream_info::container_type container) +void gstreamer_file_source::fill_format_info( GstStructure* caps_struct, + audio::ifstream_info::container_type container ) { - m_info.container(container); - m_info.codec(audio::ifstream_info::codec_type::mp3); - m_info.lossless(false); + m_info.container( container ); + m_info.codec( audio::ifstream_info::codec_type::mp3 ); + m_info.lossless( false ); - gint64 num_frames = 0; - gst_element_query_duration(m_pipeline.get(), GST_FORMAT_DEFAULT, &num_frames); - m_info.num_frames(num_frames); + gint64 num_frames = 0; + gst_element_query_duration( m_pipeline.get(), GST_FORMAT_DEFAULT, &num_frames ); + m_info.num_frames( num_frames ); - int sample_rate = 0; - gst_structure_get_int(caps_struct, "rate", &sample_rate); - m_info.sample_rate(sample_rate); + int sample_rate = 0; + gst_structure_get_int( caps_struct, "rate", &sample_rate ); + m_info.sample_rate( sample_rate ); - int num_channels = 0; - gst_structure_get_int(caps_struct, "channels", &num_channels); - m_info.num_channels(num_channels); + int num_channels = 0; + gst_structure_get_int( caps_struct, "channels", &num_channels ); + m_info.num_channels( num_channels ); - m_info.format(create_runtime_format(caps_struct)); + m_info.format( create_runtime_format( caps_struct ) ); } //---------------------------------------------------------------------------------------------------------------------- -pcm::runtime_format gstreamer_file_source::create_runtime_format(GstStructure* caps_struct) +pcm::runtime_format gstreamer_file_source::create_runtime_format( GstStructure* caps_struct ) { - const gchar* format = gst_structure_get_string(caps_struct, "format"); - pcm::number_type number_type = gst_format_char_to_number_type(format[0]); - auto srcDepth = std::atol(format + 1); - auto endian = (strcmp(format, "BE") == 0) ? pcm::big_endian : pcm::little_endian; - return pcm::runtime_format(number_type, srcDepth, endian); + const gchar* format = gst_structure_get_string( caps_struct, "format" ); + pcm::number_type number_type = gst_format_char_to_number_type( format[0] ); + auto srcDepth = std::atol( format + 1 ); + auto endian = ( strcmp( format, "BE" ) == 0 ) ? pcm::big_endian : pcm::little_endian; + return pcm::runtime_format( number_type, srcDepth, endian ); } //---------------------------------------------------------------------------------------------------------------------- -pcm::number_type gstreamer_file_source::gst_format_char_to_number_type(const gchar format) +pcm::number_type gstreamer_file_source::gst_format_char_to_number_type( const gchar format ) { - if(format == 'U') - return pcm::unsigned_integer; - else if(format == 'F') - return pcm::floating_point; + if ( format == 'U' ) + return pcm::unsigned_integer; + else if ( format == 'F' ) + return pcm::floating_point; - return pcm::signed_integer; + return pcm::signed_integer; } //---------------------------------------------------------------------------------------------------------------------- -void gstreamer_file_source::onPadAdded(GstElement* element, GstPad* pad, GstElement* sink) +void gstreamer_file_source::onPadAdded( GstElement* element, GstPad* pad, GstElement* sink ) { - tGstPtr sinkpad(gst_element_get_static_pad(sink, "sink"), gst_object_unref); - gst_pad_link(pad, sinkpad.get()); + tGstPtr sinkpad( gst_element_get_static_pad( sink, "sink" ), gst_object_unref ); + gst_pad_link( pad, sinkpad.get() ); } //---------------------------------------------------------------------------------------------------------------------- -std::streampos gstreamer_file_source::seek(offset_type off, BOOST_IOS::seekdir way ) +std::streampos gstreamer_file_source::seek( offset_type off, BOOST_IOS::seekdir way ) { - assert( 0 == off % m_info.bytes_per_frame() ); - - int64_t newPosition = 0; - - if (way == BOOST_IOS::seekdir::_S_beg) - { - newPosition = off; - } - else if (way == BOOST_IOS::seekdir::_S_cur) - { - newPosition = m_position + off; - } - else if (way == BOOST_IOS::seekdir::_S_end) - { - int64_t end = 0; - if(!gst_element_query_duration (m_pipeline.get(), GST_FORMAT_BYTES, &end)) + assert( 0 == off % m_info.bytes_per_frame() ); + + int64_t newPosition = 0; + + if ( way == BOOST_IOS::seekdir::_S_beg ) { - throw std::runtime_error( "gstreamer_file_source: seeking from end of file impossible - could not query duration" ); + newPosition = off; + } + else if ( way == BOOST_IOS::seekdir::_S_cur ) + { + newPosition = m_position + off; + } + else if ( way == BOOST_IOS::seekdir::_S_end ) + { + int64_t end = 0; + if ( !gst_element_query_duration( m_pipeline.get(), GST_FORMAT_BYTES, &end ) ) + { + throw std::runtime_error( + "gstreamer_file_source: seeking from end of file impossible - could not query duration" ); + } + newPosition = end + off; } - newPosition = end + off; - } - - if(m_position != newPosition) - { - m_position = newPosition; - m_ring_buffer->flush(); - if(!gst_element_seek_simple(m_pipeline.get(), GST_FORMAT_BYTES, (GstSeekFlags)(GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE), m_position)) + if ( m_position != newPosition ) { - throw std::runtime_error( "gstreamer_file_source: seeking failed" ); + m_position = newPosition; + m_ring_buffer->flush(); + + if ( !gst_element_seek_simple( m_pipeline.get(), + GST_FORMAT_BYTES, + ( GstSeekFlags )( GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE ), + m_position ) ) + { + throw std::runtime_error( "gstreamer_file_source: seeking failed" ); + } } - } - return m_position; + return m_position; } //---------------------------------------------------------------------------------------------------------------------- -std::streamsize gstreamer_file_source::read(char* dst, std::streamsize numBytesRequested) +std::streamsize gstreamer_file_source::read( char* dst, std::streamsize numBytesRequested ) { - return recursive_read(dst, numBytesRequested); + return recursive_read( dst, numBytesRequested ); } //---------------------------------------------------------------------------------------------------------------------- -std::streamsize gstreamer_file_source::recursive_read(char* dst, std::streamsize numBytesRequested) +std::streamsize gstreamer_file_source::recursive_read( char* dst, std::streamsize numBytesRequested ) { - auto read_bytes = m_ring_buffer->pull(dst, numBytesRequested); + auto read_bytes = m_ring_buffer->pull( dst, numBytesRequested ); - dst += read_bytes; - numBytesRequested -= read_bytes; - m_position += read_bytes; + dst += read_bytes; + numBytesRequested -= read_bytes; + m_position += read_bytes; - if(numBytesRequested) - { - tGstPtr sink(gst_bin_get_by_name(GST_BIN(m_pipeline.get()), "sink"), gst_object_unref); - GstAppSink *app_sink = reinterpret_cast(sink.get()); + if ( numBytesRequested ) + { + tGstPtr sink( gst_bin_get_by_name( GST_BIN( m_pipeline.get() ), "sink" ), gst_object_unref ); + GstAppSink* app_sink = reinterpret_cast( sink.get() ); - tGstPtr sample(gst_app_sink_pull_sample(app_sink), (GUnref) gst_sample_unref); + tGstPtr sample( gst_app_sink_pull_sample( app_sink ), (GUnref) gst_sample_unref ); - if(sample) - { - auto buffer = gst_sample_get_buffer(sample.get()); + if ( sample ) + { + auto buffer = gst_sample_get_buffer( sample.get() ); - GstMapInfo mapped; + GstMapInfo mapped; - if(gst_buffer_map(buffer, &mapped, GST_MAP_READ)) - { - auto for_now = std::min(mapped.size, numBytesRequested); - std::copy(mapped.data, mapped.data + for_now, dst); - read_bytes += for_now; - dst += for_now; - numBytesRequested -= for_now; - m_position += for_now; + if ( gst_buffer_map( buffer, &mapped, GST_MAP_READ ) ) + { + auto for_now = std::min( mapped.size, numBytesRequested ); + std::copy( mapped.data, mapped.data + for_now, dst ); + read_bytes += for_now; + dst += for_now; + numBytesRequested -= for_now; + m_position += for_now; - m_ring_buffer->push((char*) mapped.data + for_now, mapped.size - for_now); - gst_buffer_unmap(buffer, &mapped); + m_ring_buffer->push( (char*) mapped.data + for_now, mapped.size - for_now ); + gst_buffer_unmap( buffer, &mapped ); - return read_bytes + recursive_read(dst, numBytesRequested); - } + return read_bytes + recursive_read( dst, numBytesRequested ); + } + } } - } - return read_bytes; + return read_bytes; } - diff --git a/audiostream/src/ni/media/audio/source/gstreamer_file_source.h b/audiostream/src/ni/media/audio/source/gstreamer_file_source.h index f3cb9f2f..a583720e 100644 --- a/audiostream/src/ni/media/audio/source/gstreamer_file_source.h +++ b/audiostream/src/ni/media/audio/source/gstreamer_file_source.h @@ -27,60 +27,64 @@ #include #include -#include #include +#include namespace detail { - template struct RingBuffer; +template +struct RingBuffer; } class gstreamer_file_source { - public: +public: //---------------------------------------------------------------------------------------------------------------------- using offset_type = boost::iostreams::stream_offset; - using FrameRange = boost::icl::right_open_interval; + using FrameRange = boost::icl::right_open_interval; using FrameRangeSet = boost::icl::interval_set; - template - using MfTypePtr = std::unique_ptr>; + template + using MfTypePtr = std::unique_ptr>; //---------------------------------------------------------------------------------------------------------------------- - explicit gstreamer_file_source(const std::string& path, audio::ifstream_info::container_type container, size_t stream = 0); + explicit gstreamer_file_source( const std::string& path, + audio::ifstream_info::container_type container, + size_t stream = 0 ); ~gstreamer_file_source(); audio::ifstream_info info() const { - return m_info; + return m_info; } - std::streampos seek(offset_type, BOOST_IOS::seekdir ); + std::streampos seek( offset_type, BOOST_IOS::seekdir ); std::streamsize read( char*, std::streamsize ); - private: - static void init_gstreamer(); - static pcm::runtime_format create_runtime_format(GstStructure* caps_struct); - static pcm::number_type gst_format_char_to_number_type(const gchar format); +private: + static void init_gstreamer(); + static pcm::runtime_format create_runtime_format( GstStructure* caps_struct ); + static pcm::number_type gst_format_char_to_number_type( const gchar format ); - void setup_source(const std::string& path, audio::ifstream_info::container_type container); - GstElement* prepare_pipeline(const std::string& path); - void preroll_pipeline(); + void setup_source( const std::string& path, audio::ifstream_info::container_type container ); + GstElement* prepare_pipeline( const std::string& path ); + void preroll_pipeline(); GstState wait_for_async_operation(); - void fill_format_info(GstStructure *caps_struct, audio::ifstream_info::container_type container); - std::streamsize recursive_read(char* dst, std::streamsize numBytesRequested); + void fill_format_info( GstStructure* caps_struct, audio::ifstream_info::container_type container ); + std::streamsize recursive_read( char* dst, std::streamsize numBytesRequested ); - static void onPadAdded(GstElement* element, GstPad* pad, GstElement* sink); + static void onPadAdded( GstElement* element, GstPad* pad, GstElement* sink ); audio::ifstream_info m_info; - using GUnref = void(*)(gpointer); - template using tGstPtr = std::unique_ptr; + using GUnref = void ( * )( gpointer ); + template + using tGstPtr = std::unique_ptr; tGstPtr m_pipeline; using RingBuffer = detail::RingBuffer; std::unique_ptr m_ring_buffer; - int64_t m_position = 0; - }; + int64_t m_position = 0; +}; From d147f117912b63719b98b52740e2454f00b084f7 Mon Sep 17 00:00:00 2001 From: Henry Hoegelow Date: Thu, 11 Jan 2018 08:13:26 +0100 Subject: [PATCH 09/28] fix alignment --- audiostream/CMakeLists.txt | 4 ++-- cmake/FindGStreamer.cmake | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/audiostream/CMakeLists.txt b/audiostream/CMakeLists.txt index 6203fb12..d3062871 100644 --- a/audiostream/CMakeLists.txt +++ b/audiostream/CMakeLists.txt @@ -71,7 +71,7 @@ endif() set( COMPILE_WITH_COREAUDIO DONT_COMPILE) set( COMPILE_WITH_MEDIA_FOUNDATION DONT_COMPILE) -set( COMPILE_WITH_GSTREAMER DONT_COMPILE) +set( COMPILE_WITH_GSTREAMER DONT_COMPILE) #----------------------------------------------------------------------------------------------------------------------- # dependencies @@ -252,7 +252,7 @@ add_src_file (FILES_media_audio_source "src/ni/media/audio/source/container_sou add_src_file (FILES_media_audio_source "src/ni/media/audio/source/core_audio_file_source.cpp" ${COMPILE_WITH_COREAUDIO} WITH_HEADER ) add_src_file (FILES_media_audio_source "src/ni/media/audio/source/media_foundation_helper.h" ${COMPILE_WITH_MEDIA_FOUNDATION} ) add_src_file (FILES_media_audio_source "src/ni/media/audio/source/media_foundation_file_source.cpp" ${COMPILE_WITH_MEDIA_FOUNDATION} WITH_HEADER ) -add_src_file (FILES_media_audio_source "src/ni/media/audio/source/gstreamer_file_source.cpp" ${COMPILE_WITH_GSTREAMER} WITH_HEADER ) +add_src_file (FILES_media_audio_source "src/ni/media/audio/source/gstreamer_file_source.cpp" ${COMPILE_WITH_GSTREAMER} WITH_HEADER ) add_src_file (FILES_media_audio_source "src/ni/media/audio/source/aiff_source.h" ${COMPILE_WITH_AIFF_DECODING} ) add_src_file (FILES_media_audio_source "src/ni/media/audio/source/aiff_file_source.h" ${COMPILE_WITH_AIFF_DECODING} ) add_src_file (FILES_media_audio_source "src/ni/media/audio/source/aiff_vector_source.h" ${COMPILE_WITH_AIFF_DECODING} ) diff --git a/cmake/FindGStreamer.cmake b/cmake/FindGStreamer.cmake index 066152b1..61b1df45 100644 --- a/cmake/FindGStreamer.cmake +++ b/cmake/FindGStreamer.cmake @@ -10,7 +10,7 @@ mark_as_advanced(GSTREAMER_INCLUDE_DIRS GSTREAMER_LIBRARY) if( GSTREAMER_FOUND ) - if( NOT TARGET GSTREAMER::gstreamer ) + if( NOT TARGET GSTREAMER::gstreamer ) add_library(GSTREAMER::gstreamer SHARED IMPORTED) set_target_properties(GSTREAMER::gstreamer PROPERTIES IMPORTED_LINK_INTERFACE_LANGUAGES "C" From 1c58318a3a025a5e1b5b81da6d977a3f6473b713 Mon Sep 17 00:00:00 2001 From: Henry Hoegelow Date: Wed, 17 Jan 2018 08:27:57 +0100 Subject: [PATCH 10/28] incorporated some of the pull request comments --- .../audio/source/gstreamer_file_source.cpp | 33 ++++++++++--------- .../audio/source/gstreamer_file_source.h | 8 ----- 2 files changed, 17 insertions(+), 24 deletions(-) diff --git a/audiostream/src/ni/media/audio/source/gstreamer_file_source.cpp b/audiostream/src/ni/media/audio/source/gstreamer_file_source.cpp index 00497199..67f3226a 100644 --- a/audiostream/src/ni/media/audio/source/gstreamer_file_source.cpp +++ b/audiostream/src/ni/media/audio/source/gstreamer_file_source.cpp @@ -25,17 +25,9 @@ #include #include -#include -#include -#include #include #include -#include -#include -#include -#include - #include namespace detail @@ -196,15 +188,21 @@ void gstreamer_file_source::fill_format_info( GstStructure* m_info.lossless( false ); gint64 num_frames = 0; - gst_element_query_duration( m_pipeline.get(), GST_FORMAT_DEFAULT, &num_frames ); + if ( !gst_element_query_duration( m_pipeline.get(), GST_FORMAT_DEFAULT, &num_frames ) ) + throw std::runtime_error( "gstreamer_file_source: could not query duration from gstreamer" ); + m_info.num_frames( num_frames ); int sample_rate = 0; - gst_structure_get_int( caps_struct, "rate", &sample_rate ); + if ( !gst_structure_get_int( caps_struct, "rate", &sample_rate ) ) + throw std::runtime_error( "gstreamer_file_source: could not query sample rate from gstreamer" ); + m_info.sample_rate( sample_rate ); int num_channels = 0; - gst_structure_get_int( caps_struct, "channels", &num_channels ); + if ( !gst_structure_get_int( caps_struct, "channels", &num_channels ) ) + throw std::runtime_error( "gstreamer_file_source: could not query number of channels from gstreamer" ); + m_info.num_channels( num_channels ); m_info.format( create_runtime_format( caps_struct ) ); @@ -214,11 +212,14 @@ void gstreamer_file_source::fill_format_info( GstStructure* pcm::runtime_format gstreamer_file_source::create_runtime_format( GstStructure* caps_struct ) { - const gchar* format = gst_structure_get_string( caps_struct, "format" ); - pcm::number_type number_type = gst_format_char_to_number_type( format[0] ); - auto srcDepth = std::atol( format + 1 ); - auto endian = ( strcmp( format, "BE" ) == 0 ) ? pcm::big_endian : pcm::little_endian; - return pcm::runtime_format( number_type, srcDepth, endian ); + if ( auto format = gst_structure_get_string( caps_struct, "format" ) ) + { + pcm::number_type number_type = gst_format_char_to_number_type( format[0] ); + auto srcDepth = std::atol( format + 1 ); + auto endian = ( strcmp( format, "BE" ) == 0 ) ? pcm::big_endian : pcm::little_endian; + return pcm::runtime_format( number_type, srcDepth, endian ); + } + throw std::runtime_error( "gstreamer_file_source: could not get runtime format from gstreamer caps" ); } //---------------------------------------------------------------------------------------------------------------------- diff --git a/audiostream/src/ni/media/audio/source/gstreamer_file_source.h b/audiostream/src/ni/media/audio/source/gstreamer_file_source.h index a583720e..1163d610 100644 --- a/audiostream/src/ni/media/audio/source/gstreamer_file_source.h +++ b/audiostream/src/ni/media/audio/source/gstreamer_file_source.h @@ -22,8 +22,6 @@ #pragma once -#include -#include #include #include @@ -43,12 +41,6 @@ class gstreamer_file_source using offset_type = boost::iostreams::stream_offset; - using FrameRange = boost::icl::right_open_interval; - using FrameRangeSet = boost::icl::interval_set; - - template - using MfTypePtr = std::unique_ptr>; - //---------------------------------------------------------------------------------------------------------------------- explicit gstreamer_file_source( const std::string& path, From 90190498a2030bc50e5989a72bd8af134ba69ef5 Mon Sep 17 00:00:00 2001 From: Marc Boucek Date: Sat, 24 Mar 2018 06:29:18 -0700 Subject: [PATCH 11/28] fix retrieval of num_frames info --- .../audio/source/gstreamer_file_source.cpp | 27 +++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/audiostream/src/ni/media/audio/source/gstreamer_file_source.cpp b/audiostream/src/ni/media/audio/source/gstreamer_file_source.cpp index 67f3226a..832ad3f9 100644 --- a/audiostream/src/ni/media/audio/source/gstreamer_file_source.cpp +++ b/audiostream/src/ni/media/audio/source/gstreamer_file_source.cpp @@ -25,10 +25,10 @@ #include #include -#include +#include #include -#include +#include namespace detail { @@ -183,22 +183,33 @@ void gstreamer_file_source::preroll_pipeline() void gstreamer_file_source::fill_format_info( GstStructure* caps_struct, audio::ifstream_info::container_type container ) { + + using namespace std::chrono; + m_info.container( container ); m_info.codec( audio::ifstream_info::codec_type::mp3 ); m_info.lossless( false ); - gint64 num_frames = 0; - if ( !gst_element_query_duration( m_pipeline.get(), GST_FORMAT_DEFAULT, &num_frames ) ) - throw std::runtime_error( "gstreamer_file_source: could not query duration from gstreamer" ); - - m_info.num_frames( num_frames ); - int sample_rate = 0; if ( !gst_structure_get_int( caps_struct, "rate", &sample_rate ) ) throw std::runtime_error( "gstreamer_file_source: could not query sample rate from gstreamer" ); m_info.sample_rate( sample_rate ); + gint64 num_frames = 0; + if ( gst_element_query_duration( m_pipeline.get(), GST_FORMAT_DEFAULT, &num_frames ) && num_frames >= 0 ) + { + m_info.num_frames( size_t( num_frames ) ); + } + else // retrieving num_frames failed: fallback to retrieve time information instead (less precise) + { + gint64 duration_ns = 0; // in nanoseconds + if ( !gst_element_query_duration( m_pipeline.get(), GST_FORMAT_TIME, &duration_ns ) ) + throw std::runtime_error( "gstreamer_file_source: could not query duration from gstreamer" ); + + m_info.num_frames( size_t( duration( nanoseconds( duration_ns ) ).count() * sample_rate ) ); + } + int num_channels = 0; if ( !gst_structure_get_int( caps_struct, "channels", &num_channels ) ) throw std::runtime_error( "gstreamer_file_source: could not query number of channels from gstreamer" ); From 9ed24e3db9788b83db732398ee72a3da6fa496fe Mon Sep 17 00:00:00 2001 From: Marc Boucek Date: Sat, 24 Mar 2018 07:07:19 -0700 Subject: [PATCH 12/28] use absolute_position for seeking, return -1 on seek error --- .../audio/source/gstreamer_file_source.cpp | 41 ++++++------------- 1 file changed, 13 insertions(+), 28 deletions(-) diff --git a/audiostream/src/ni/media/audio/source/gstreamer_file_source.cpp b/audiostream/src/ni/media/audio/source/gstreamer_file_source.cpp index 832ad3f9..db18836e 100644 --- a/audiostream/src/ni/media/audio/source/gstreamer_file_source.cpp +++ b/audiostream/src/ni/media/audio/source/gstreamer_file_source.cpp @@ -86,7 +86,7 @@ gstreamer_file_source::gstreamer_file_source( const std::string& audio::ifstream_info::container_type container, size_t stream ) : m_pipeline( nullptr, gst_object_unref ) -, m_ring_buffer( new RingBuffer() ) +, m_ring_buffer( std::make_unique() ) { init_gstreamer(); setup_source( path, container ); @@ -259,38 +259,23 @@ std::streampos gstreamer_file_source::seek( offset_type off, BOOST_IOS::seekdir { assert( 0 == off % m_info.bytes_per_frame() ); - int64_t newPosition = 0; + const auto beg = std::streampos( 0 ); + const auto end = std::streampos( info().num_bytes() ); + const auto pos = absolute_position( m_position, beg, end, off, way ); - if ( way == BOOST_IOS::seekdir::_S_beg ) + if ( m_position != pos ) { - newPosition = off; - } - else if ( way == BOOST_IOS::seekdir::_S_cur ) - { - newPosition = m_position + off; - } - else if ( way == BOOST_IOS::seekdir::_S_end ) - { - int64_t end = 0; - if ( !gst_element_query_duration( m_pipeline.get(), GST_FORMAT_BYTES, &end ) ) + if ( gst_element_seek_simple( m_pipeline.get(), + GST_FORMAT_BYTES, + ( GstSeekFlags )( GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE ), + pos ) ) { - throw std::runtime_error( - "gstreamer_file_source: seeking from end of file impossible - could not query duration" ); + m_ring_buffer->flush(); + m_position = pos; } - newPosition = end + off; - } - - if ( m_position != newPosition ) - { - m_position = newPosition; - m_ring_buffer->flush(); - - if ( !gst_element_seek_simple( m_pipeline.get(), - GST_FORMAT_BYTES, - ( GstSeekFlags )( GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE ), - m_position ) ) + else { - throw std::runtime_error( "gstreamer_file_source: seeking failed" ); + return -1; } } From e45327e971962d72651d1266840e21298faea9b0 Mon Sep 17 00:00:00 2001 From: Marc Boucek Date: Tue, 27 Mar 2018 15:40:14 +0200 Subject: [PATCH 13/28] improve error reporting for failing streams use small epsilon to compare samples --- .../test/ni/media/read_interlaced_test.h | 31 +++++++++++++------ audiostream/test/ni/media/source_test.cpp | 27 ++++++++++------ 2 files changed, 40 insertions(+), 18 deletions(-) diff --git a/audiostream/test/ni/media/read_interlaced_test.h b/audiostream/test/ni/media/read_interlaced_test.h index 934c5a0f..c9ede856 100644 --- a/audiostream/test/ni/media/read_interlaced_test.h +++ b/audiostream/test/ni/media/read_interlaced_test.h @@ -28,8 +28,12 @@ #include +#include +#include + #include +#include #include #include #include @@ -54,7 +58,7 @@ void read_interlaced_test( Stream&& stream, std::streamoff frame_offset = 0, size_t num_blocks = 1 ) { - std::vector samples1, samples2; + std::vector buffer1, buffer2; const auto num_samples = num_frames_per_block * stream.info().num_channels(); const auto num_bytes = std::streamoff( num_frames_per_block * stream.info().bytes_per_frame() ); @@ -77,7 +81,7 @@ void read_interlaced_test( Stream&& stream, expected[0].first = expected[0].last; actual[0].first = stream.seekg( expected[0].first ).tellg(); - detail::push_back_samples( samples1, stream, num_samples ); + detail::push_back_samples( buffer1, stream, num_samples ); expected[0].last = expected[0].first + num_bytes; actual[0].last = stream.tellg(); @@ -86,7 +90,7 @@ void read_interlaced_test( Stream&& stream, expected[1].first = actual[0].first + byte_offset; actual[1].first = stream.seekg( expected[1].first ).tellg(); - detail::push_back_samples( samples2, stream, num_samples ); + detail::push_back_samples( buffer2, stream, num_samples ); expected[1].last = expected[1].first + num_bytes; actual[1].last = stream.tellg(); @@ -98,12 +102,21 @@ void read_interlaced_test( Stream&& stream, } if ( frame_offset < 0 ) - std::swap( samples1, samples2 ); + std::swap( buffer1, buffer2 ); + + const auto offset = std::abs( frame_offset ) * stream.info().num_channels(); + + using samples_diff_type = std::iterator_traits::difference_type; + auto samples1 = + std::vector( std::next( buffer1.begin(), static_cast( offset ) ), buffer1.end() ); + + auto samples2 = std::vector( buffer2.begin(), buffer2.begin() + boost::size( samples1 ) ); + + auto float_compare = []( float lhs, float rhs ) { + return std::abs( lhs - rhs ) <= std::numeric_limits::epsilon(); + }; - const auto sample_offset = std::abs( frame_offset ) * stream.info().num_channels(); + bool samples_are_equal = boost::equal( samples1, samples2 ); - using samples_diff_type = std::iterator_traits::difference_type; - EXPECT_TRUE( std::equal( std::next( samples1.begin(), static_cast( sample_offset ) ), - samples1.end(), - samples2.begin() ) ); + EXPECT_TRUE( samples_are_equal ); } diff --git a/audiostream/test/ni/media/source_test.cpp b/audiostream/test/ni/media/source_test.cpp index 720a66d9..f4701b8b 100644 --- a/audiostream/test/ni/media/source_test.cpp +++ b/audiostream/test/ni/media/source_test.cpp @@ -89,31 +89,40 @@ struct stream_opener //---------------------------------------------------------------------------------------------------------------------- -} // namespace detail - - template -Stream source_test::open_file_as() +void open_file_impl( Stream& stream, const std::string& filename ) { + + std::string error; try { - return {detail::stream_opener::open( file_name() )}; + stream = detail::stream_opener::open( filename ); } catch ( const std::runtime_error& re ) { - ADD_FAILURE() << "Runtime error: " << re.what() << std::endl; + error = std::string( "Runtime error: " ) + re.what(); } catch ( const std::exception& ex ) { - ADD_FAILURE() << "Error occurred: " << ex.what() << std::endl; + error = std::string( "Unknown error: " ) + ex.what(); } catch ( ... ) { - ADD_FAILURE() << "Unknown failure occurred"; + error = std::string( "Unknown error" ); } + ASSERT_TRUE( stream.good() ) << error; +} + +} // namespace detail + - return {}; +template +Stream source_test::open_file_as() +{ + Stream stream; + detail::open_file_impl( stream, file_name() ); + return stream; } template audio::ifstream source_test::open_file_as(); From 18234628189986857056d2f323bb77ca539964b0 Mon Sep 17 00:00:00 2001 From: Marc Boucek Date: Wed, 28 Mar 2018 13:22:02 +0200 Subject: [PATCH 14/28] change m_position type to std::streampos --- audiostream/src/ni/media/audio/source/gstreamer_file_source.cpp | 2 +- audiostream/src/ni/media/audio/source/gstreamer_file_source.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/audiostream/src/ni/media/audio/source/gstreamer_file_source.cpp b/audiostream/src/ni/media/audio/source/gstreamer_file_source.cpp index db18836e..e9432c32 100644 --- a/audiostream/src/ni/media/audio/source/gstreamer_file_source.cpp +++ b/audiostream/src/ni/media/audio/source/gstreamer_file_source.cpp @@ -268,7 +268,7 @@ std::streampos gstreamer_file_source::seek( offset_type off, BOOST_IOS::seekdir if ( gst_element_seek_simple( m_pipeline.get(), GST_FORMAT_BYTES, ( GstSeekFlags )( GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE ), - pos ) ) + (gint64) pos ) ) { m_ring_buffer->flush(); m_position = pos; diff --git a/audiostream/src/ni/media/audio/source/gstreamer_file_source.h b/audiostream/src/ni/media/audio/source/gstreamer_file_source.h index 1163d610..cb1dbbeb 100644 --- a/audiostream/src/ni/media/audio/source/gstreamer_file_source.h +++ b/audiostream/src/ni/media/audio/source/gstreamer_file_source.h @@ -78,5 +78,5 @@ class gstreamer_file_source using RingBuffer = detail::RingBuffer; std::unique_ptr m_ring_buffer; - int64_t m_position = 0; + std::streampos m_position = 0; }; From 76366dcf2060c92eb5280135d3faa93c9e7050ac Mon Sep 17 00:00:00 2001 From: Marc Boucek Date: Wed, 28 Mar 2018 13:28:47 +0200 Subject: [PATCH 15/28] read_interlaced_test: add more valuable test output --- .../test/ni/media/read_interlaced_test.h | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/audiostream/test/ni/media/read_interlaced_test.h b/audiostream/test/ni/media/read_interlaced_test.h index c9ede856..8bb94e08 100644 --- a/audiostream/test/ni/media/read_interlaced_test.h +++ b/audiostream/test/ni/media/read_interlaced_test.h @@ -28,7 +28,7 @@ #include -#include +#include #include @@ -112,11 +112,25 @@ void read_interlaced_test( Stream&& stream, auto samples2 = std::vector( buffer2.begin(), buffer2.begin() + boost::size( samples1 ) ); - auto float_compare = []( float lhs, float rhs ) { - return std::abs( lhs - rhs ) <= std::numeric_limits::epsilon(); - }; + auto diff = std::vector( samples1.size() ); + boost::transform( samples1, samples2, diff.begin(), std::minus{} ); + + size_t num_errors = 0; + float total_error = 0; + + auto float_is_zero = []( float value ) { return std::abs( value ) <= std::numeric_limits::epsilon(); }; + + for ( auto it = diff.begin(), end = diff.end(); // + it = std::find_if_not( it, end, float_is_zero ), it != end; + ++it, ++num_errors ) + { + total_error += std::abs( *it ); + } - bool samples_are_equal = boost::equal( samples1, samples2 ); + auto error_rate = (float) num_errors / diff.size(); + auto average_error = total_error / diff.size(); - EXPECT_TRUE( samples_are_equal ); + EXPECT_EQ( num_errors, 0 ); + EXPECT_FLOAT_EQ( error_rate, 0 ); + EXPECT_FLOAT_EQ( average_error, 0 ); } From 4643b10e3b3be8fa92f2d80e3e1f83e35866d811 Mon Sep 17 00:00:00 2001 From: Henry Hoegelow Date: Thu, 21 Dec 2017 17:43:38 +0100 Subject: [PATCH 16/28] adding support for mp3, m4a and wma files on linux by using gstreamer library this is the initial revision, tests are not yet green, to be continued --- audiostream/CMakeLists.txt | 29 +- .../audio/source/gstreamer_file_source.cpp | 295 ++++++++++++++++++ .../audio/source/gstreamer_file_source.h | 84 +++++ .../ni/media/audio/source/mp3_file_source.cpp | 4 + .../ni/media/audio/source/mp3_file_source.h | 2 + .../ni/media/audio/source/mp4_file_source.cpp | 4 + .../ni/media/audio/source/mp4_file_source.h | 2 + .../ni/media/audio/source/wma_file_source.cpp | 8 + .../ni/media/audio/source/wma_file_source.h | 5 + cmake/FindGObject.cmake | 5 + cmake/FindGStreamer.cmake | 23 ++ cmake/FindGStreamerApp.cmake | 23 ++ cmake/FindGlib.cmake | 5 + 13 files changed, 487 insertions(+), 2 deletions(-) create mode 100644 audiostream/src/ni/media/audio/source/gstreamer_file_source.cpp create mode 100644 audiostream/src/ni/media/audio/source/gstreamer_file_source.h create mode 100644 cmake/FindGObject.cmake create mode 100644 cmake/FindGStreamer.cmake create mode 100644 cmake/FindGStreamerApp.cmake create mode 100644 cmake/FindGlib.cmake diff --git a/audiostream/CMakeLists.txt b/audiostream/CMakeLists.txt index 55c04341..1556f32a 100644 --- a/audiostream/CMakeLists.txt +++ b/audiostream/CMakeLists.txt @@ -7,7 +7,7 @@ option( NIMEDIA_ENABLE_AIFF_ENCODING "Enable ni-media aiff encoding" ON ) option( NIMEDIA_ENABLE_FLAC_DECODING "Enable ni-media flac decoding" ON ) option( NIMEDIA_ENABLE_OGG_DECODING "Enable ni-media ogg decoding" ON ) -if ( APPLE OR WIN32 ) +if ( APPLE OR WIN32 OR LINUX ) option( NIMEDIA_ENABLE_MP3_DECODING "Enable ni-media mp3 decoding" ON ) option( NIMEDIA_ENABLE_MP4_DECODING "Enable ni-media mp4 decoding" ON ) else() @@ -21,7 +21,7 @@ else() option( NIMEDIA_ENABLE_ITUNES_DECODING "Enable ni-media iTunes decoding" OFF ) endif() -if( WIN32 ) +if( WIN32 OR LINUX ) option( NIMEDIA_ENABLE_WMA_DECODING "Enable ni-media wma decoding" ON ) else() option( NIMEDIA_ENABLE_WMA_DECODING "Enable ni-media wma decoding" OFF ) @@ -80,6 +80,7 @@ endif() set( COMPILE_WITH_COREAUDIO DONT_COMPILE) set( COMPILE_WITH_MEDIA_FOUNDATION DONT_COMPILE) +set( COMPILE_WITH_GSTREAMER DONT_COMPILE) #----------------------------------------------------------------------------------------------------------------------- # dependencies @@ -143,6 +144,29 @@ if( NIMEDIA_ENABLE_MP3_DECODING OR NIMEDIA_ENABLE_MP4_DECODING OR NIMEDIA_ENABLE list(APPEND codec_libraries mfplat.lib mfreadwrite.lib mfuuid.lib Propsys.lib) + elseif( LINUX ) + + set(COMPILE_WITH_GSTREAMER) + + find_package(Glib REQUIRED) + find_package(GStreamer REQUIRED) + find_package(GStreamerApp REQUIRED) + find_package(GObject REQUIRED) + + if ( NOT TARGET GSTREAMER::gstreamer ) + message(FATAL_ERROR + "You are building ni-media with GStreamer decoding support but the required gstreamer-1.0 library was not found\n" + "Make sure library can be found or disable GSTREAMER decoding by setting:\n" + " * NIMEDIA_ENABLE_MP3_DECODING = OFF\n" + " * NIMEDIA_ENABLE_MP4_DECODING = OFF\n" + " * NIMEDIA_ENABLE_WMA_DECODING = OFF\n") + endif() + + list(APPEND codec_libraries GSTREAMER::gstreamer) + list(APPEND codec_libraries GSTREAMERAPP::gstreamerapp) + list(APPEND codec_libraries glib-2.0) + list(APPEND codec_libraries gobject-2.0) + else() message(FATAL_ERROR @@ -242,6 +266,7 @@ add_src_file (FILES_media_audio_source "src/ni/media/audio/source/container_sou add_src_file (FILES_media_audio_source "src/ni/media/audio/source/core_audio_file_source.cpp" ${COMPILE_WITH_COREAUDIO} WITH_HEADER ) add_src_file (FILES_media_audio_source "src/ni/media/audio/source/media_foundation_helper.h" ${COMPILE_WITH_MEDIA_FOUNDATION} ) add_src_file (FILES_media_audio_source "src/ni/media/audio/source/media_foundation_file_source.cpp" ${COMPILE_WITH_MEDIA_FOUNDATION} WITH_HEADER ) +add_src_file (FILES_media_audio_source "src/ni/media/audio/source/gstreamer_file_source.cpp" ${COMPILE_WITH_GSTREAMER} WITH_HEADER ) add_src_file (FILES_media_audio_source "src/ni/media/audio/source/aiff_source.h" ${COMPILE_WITH_AIFF_DECODING} ) add_src_file (FILES_media_audio_source "src/ni/media/audio/source/aiff_file_source.h" ${COMPILE_WITH_AIFF_DECODING} ) add_src_file (FILES_media_audio_source "src/ni/media/audio/source/aiff_vector_source.h" ${COMPILE_WITH_AIFF_DECODING} ) diff --git a/audiostream/src/ni/media/audio/source/gstreamer_file_source.cpp b/audiostream/src/ni/media/audio/source/gstreamer_file_source.cpp new file mode 100644 index 00000000..170ccab5 --- /dev/null +++ b/audiostream/src/ni/media/audio/source/gstreamer_file_source.cpp @@ -0,0 +1,295 @@ +// +// Copyright (c) 2017 Native Instruments GmbH, Berlin +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +#include "gstreamer_file_source.h" + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +namespace detail +{ + template + struct RingBuffer + { + static constexpr size_t size = s; + static constexpr size_t mask = (s - 1); + std::vector m_buffer; + uint64_t m_write_head = 0; + uint64_t m_read_head = 0; + + void push(const T* data, size_t count) + { + if(count) + { + auto idx = m_write_head & mask; + auto space = size - idx; + auto for_now = std::min(space, count); + std::copy(data, data + for_now, m_buffer.begin() + idx); + m_write_head += for_now; + push(data + for_now, count - for_now); + } + } + + size_t pull(T* data, size_t count) + { + count = std::min(count, m_write_head - m_read_head); + + if(count) + { + auto idx = m_read_head & mask; + auto space = size - idx; + auto for_now = std::min(space, count); + std::copy(m_buffer.begin() + idx, m_buffer.begin() + idx + for_now, data); + m_read_head += for_now; + return for_now + pull(data + for_now, count - for_now); + } + return 0; + } + }; +} + +//---------------------------------------------------------------------------------------------------------------------- +gstreamer_file_source::gstreamer_file_source(const std::string& path, audio::ifstream_info::container_type container, size_t stream) : + m_pipeline(nullptr, gst_object_unref), + m_ring_buffer(new RingBuffer()) +{ + init_gstreamer(); + setup_source(path, container); +} + +//---------------------------------------------------------------------------------------------------------------------- + +gstreamer_file_source::~gstreamer_file_source() +{ +} + +//---------------------------------------------------------------------------------------------------------------------- + +void gstreamer_file_source::init_gstreamer() +{ + static bool gstreamer_initialized = false; + + if(!std::exchange(gstreamer_initialized, true)) + { + gst_init(0, nullptr); + } +} + +//---------------------------------------------------------------------------------------------------------------------- + +void gstreamer_file_source::setup_source(const std::string& path, audio::ifstream_info::container_type container) +{ + GstElement* sink = prepare_pipeline(path); + auto sinkpad = gst_element_get_static_pad(sink, "sink"); + auto caps = gst_pad_get_current_caps(sinkpad); + auto caps_struct = gst_caps_get_structure(caps, 0); + fill_format_info(caps_struct, container); +} + +//---------------------------------------------------------------------------------------------------------------------- + +GstElement* gstreamer_file_source::prepare_pipeline(const std::string& path) +{ + m_pipeline.reset(gst_pipeline_new("pipeline")); + + GstElement* source = gst_element_factory_make("filesrc", "source"); + GstElement* decodebin = gst_element_factory_make("decodebin", "decoder"); + GstElement* queue = gst_element_factory_make("queue", "queue"); + GstElement* sink = gst_element_factory_make("appsink", "sink"); + + gst_bin_add_many(GST_BIN(m_pipeline.get()), source, decodebin, queue, sink, nullptr); + gst_element_link_many(source, decodebin, NULL); + gst_element_link_many(queue, sink, NULL); + + g_object_set(source, "location", path.c_str(), NULL); + + g_signal_connect(decodebin, "pad-added", G_CALLBACK(&onPadAdded), queue); + + preroll_pipeline(); + return sink; +} + +//---------------------------------------------------------------------------------------------------------------------- + +void gstreamer_file_source::preroll_pipeline() +{ + gst_element_set_state(m_pipeline.get(), GST_STATE_PLAYING); + GstState current_state = GST_STATE_VOID_PENDING; + GstState pending_state = GST_STATE_VOID_PENDING; + + while(gst_element_get_state(m_pipeline.get(), ¤t_state, &pending_state, GST_MSECOND) == GST_STATE_CHANGE_ASYNC) + { + g_main_context_iteration(nullptr, FALSE); + } + + std::cerr << "Pipeline is in state: " << current_state << std::endl; + + if(current_state != GST_STATE_PLAYING) + { + auto res = gst_element_get_state(m_pipeline.get(), ¤t_state, &pending_state, GST_MSECOND); + std::cerr << "Pipeline does not preroll: " << res << " " << current_state << std::endl; + throw std::runtime_error( "gstreamer_file_source: pipeline doesn't preroll" ); + } +} + +//---------------------------------------------------------------------------------------------------------------------- + +void gstreamer_file_source::fill_format_info(GstStructure *caps_struct, audio::ifstream_info::container_type container) +{ + m_info.container(container); + m_info.codec(audio::ifstream_info::codec_type::mp3); + m_info.lossless(false); + + gint64 num_frames = 0; + gst_element_query_duration(m_pipeline.get(), GST_FORMAT_DEFAULT, &num_frames); + m_info.num_frames(num_frames); + + int sample_rate = 0; + gst_structure_get_int(caps_struct, "rate", &sample_rate); + m_info.sample_rate(sample_rate); + + int num_channels = 0; + gst_structure_get_int(caps_struct, "channels", &num_channels); + m_info.num_channels(num_channels); + + m_info.format(create_runtime_format(caps_struct)); +} + +//---------------------------------------------------------------------------------------------------------------------- + +pcm::runtime_format gstreamer_file_source::create_runtime_format(GstStructure* caps_struct) +{ + const gchar* format = gst_structure_get_string(caps_struct, "format"); + pcm::number_type number_type = gst_format_char_to_number_type(format[0]); + auto srcDepth = std::atol(format + 1); + auto endian = (strcmp(format, "BE") == 0) ? pcm::big_endian : pcm::little_endian; + return pcm::runtime_format(number_type, srcDepth, endian); +} + +//---------------------------------------------------------------------------------------------------------------------- + +pcm::number_type gstreamer_file_source::gst_format_char_to_number_type(const gchar format) +{ + if(format == 'U') + return pcm::unsigned_integer; + else if(format == 'F') + return pcm::floating_point; + + return pcm::signed_integer; +} + +//---------------------------------------------------------------------------------------------------------------------- + +void gstreamer_file_source::onPadAdded(GstElement* element, GstPad* pad, GstElement* sink) +{ + tGstPtr sinkpad(gst_element_get_static_pad(sink, "sink"), gst_object_unref); + gst_pad_link(pad, sinkpad.get()); +} + +//---------------------------------------------------------------------------------------------------------------------- + +std::streampos gstreamer_file_source::seek(offset_type off, BOOST_IOS::seekdir way ) +{ + int64_t newPosition = 0; + + if (way == BOOST_IOS::seekdir::_S_beg) + { + newPosition = off; + } + else if (way == BOOST_IOS::seekdir::_S_cur) + { + newPosition = m_position + off; + } + else if (way == BOOST_IOS::seekdir::_S_end) + { + int64_t end = 0; + if(!gst_element_query_duration (m_pipeline.get(), GST_FORMAT_DEFAULT, &end)) + { + throw std::runtime_error( "gstreamer_file_source: seeking from end of file impossible - could not query duration" ); + } + newPosition = end + off; + } + + if(newPosition != m_position) + { + m_position = newPosition; + + if(!gst_element_seek_simple(m_pipeline.get(), GST_FORMAT_DEFAULT, (GstSeekFlags)(GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE), m_position)) + { + throw std::runtime_error( "gstreamer_file_source: seeking failed" ); + } + } + + return m_position; +} + +//---------------------------------------------------------------------------------------------------------------------- + +std::streamsize gstreamer_file_source::read(char* dst, std::streamsize size) +{ + auto bytesPerFrame = m_info.bytes_per_frame(); + auto bufferedBytes = m_ring_buffer->pull(dst, size * bytesPerFrame); + auto bufferedFrames = bufferedBytes / bytesPerFrame; + dst += bufferedFrames; + size -= bufferedFrames; + + tGstPtr sink(gst_bin_get_by_name(GST_BIN(m_pipeline.get()), "sink"), gst_object_unref); + GstAppSink *app_sink = reinterpret_cast(sink.get()); + + tGstPtr sample(gst_app_sink_pull_sample(app_sink), (GUnref) gst_sample_unref); + + if(sample) + { + auto buffer = gst_sample_get_buffer(sample.get()); + + GstMapInfo mapped; + + if(gst_buffer_map(buffer, &mapped, GST_MAP_READ)) + { + auto numBytesRequested = size * bytesPerFrame; + auto for_now = std::min(mapped.size, numBytesRequested); + + std::copy(mapped.data, mapped.data + for_now, dst); + + m_ring_buffer->push((char*) mapped.data + for_now, mapped.size - for_now); + bufferedFrames += for_now / bytesPerFrame; + + gst_buffer_unmap(buffer, &mapped); + } + } + + m_position += bufferedFrames; + return bufferedFrames; +} + diff --git a/audiostream/src/ni/media/audio/source/gstreamer_file_source.h b/audiostream/src/ni/media/audio/source/gstreamer_file_source.h new file mode 100644 index 00000000..88bbd40c --- /dev/null +++ b/audiostream/src/ni/media/audio/source/gstreamer_file_source.h @@ -0,0 +1,84 @@ +// +// Copyright (c) 2017 Native Instruments GmbH, Berlin +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +#pragma once + +#include +#include +#include +#include + +#include +#include + +namespace detail +{ + template struct RingBuffer; +} + +class gstreamer_file_source +{ + public: + //---------------------------------------------------------------------------------------------------------------------- + + using offset_type = boost::iostreams::stream_offset; + + using FrameRange = boost::icl::right_open_interval; + using FrameRangeSet = boost::icl::interval_set; + + template + using MfTypePtr = std::unique_ptr>; + + //---------------------------------------------------------------------------------------------------------------------- + + explicit gstreamer_file_source(const std::string& path, audio::ifstream_info::container_type container, size_t stream = 0); + ~gstreamer_file_source(); + + audio::ifstream_info info() const + { + return m_info; + } + + std::streampos seek(offset_type, BOOST_IOS::seekdir ); + std::streamsize read( char*, std::streamsize ); + + private: + static void init_gstreamer(); + static pcm::runtime_format create_runtime_format(GstStructure* caps_struct); + static pcm::number_type gst_format_char_to_number_type(const gchar format); + + void setup_source(const std::string& path, audio::ifstream_info::container_type container); + GstElement* prepare_pipeline(const std::string& path); + void preroll_pipeline(); + void fill_format_info(GstStructure *caps_struct, audio::ifstream_info::container_type container); + + static void onPadAdded(GstElement* element, GstPad* pad, GstElement* sink); + + audio::ifstream_info m_info; + using GUnref = void(*)(gpointer); + template using tGstPtr = std::unique_ptr; + tGstPtr m_pipeline; + + using RingBuffer = detail::RingBuffer; + std::unique_ptr m_ring_buffer; + int64_t m_position = 0; + }; diff --git a/audiostream/src/ni/media/audio/source/mp3_file_source.cpp b/audiostream/src/ni/media/audio/source/mp3_file_source.cpp index 09cddf1c..e22f27ec 100644 --- a/audiostream/src/ni/media/audio/source/mp3_file_source.cpp +++ b/audiostream/src/ni/media/audio/source/mp3_file_source.cpp @@ -26,6 +26,8 @@ #include "core_audio_file_source.h" #elif BOOST_OS_WINDOWS #include "media_foundation_file_source.h" +#elif BOOST_OS_LINUX +#include "gstreamer_file_source.h" #endif //---------------------------------------------------------------------------------------------------------------------- @@ -55,6 +57,8 @@ void mp3_file_source::open( const std::string& path ) m_impl.reset( new core_audio_file_source( path, audio::ifstream_info::container_type::mp3 ) ); #elif BOOST_OS_WINDOWS m_impl.reset( new media_foundation_file_source( path, audio::ifstream_info::container_type::mp3 ) ); +#elif BOOST_OS_LINUX + m_impl.reset( new gstreamer_file_source( path, audio::ifstream_info::container_type::mp3 ) ); #endif } diff --git a/audiostream/src/ni/media/audio/source/mp3_file_source.h b/audiostream/src/ni/media/audio/source/mp3_file_source.h index e3340d20..39dc7eb3 100644 --- a/audiostream/src/ni/media/audio/source/mp3_file_source.h +++ b/audiostream/src/ni/media/audio/source/mp3_file_source.h @@ -61,5 +61,7 @@ class mp3_file_source std::unique_ptr m_impl; #elif BOOST_OS_WINDOWS std::unique_ptr m_impl; +#elif BOOST_OS_LINUX + std::unique_ptr m_impl; #endif }; diff --git a/audiostream/src/ni/media/audio/source/mp4_file_source.cpp b/audiostream/src/ni/media/audio/source/mp4_file_source.cpp index c68f9db0..259fae63 100644 --- a/audiostream/src/ni/media/audio/source/mp4_file_source.cpp +++ b/audiostream/src/ni/media/audio/source/mp4_file_source.cpp @@ -26,6 +26,8 @@ #include "core_audio_file_source.h" #elif BOOST_OS_WINDOWS #include "media_foundation_file_source.h" +#elif BOOST_OS_LINUX +#include "gstreamer_file_source.h" #endif //---------------------------------------------------------------------------------------------------------------------- @@ -55,6 +57,8 @@ void mp4_file_source::open( const std::string& path, size_t stream ) m_impl.reset( new core_audio_file_source( path, audio::ifstream_info::container_type::mp4, stream ) ); #elif BOOST_OS_WINDOWS m_impl.reset( new media_foundation_file_source( path, audio::ifstream_info::container_type::mp4, stream ) ); +#elif BOOST_OS_LINUX + m_impl.reset( new gstreamer_file_source( path, audio::ifstream_info::container_type::mp4 ) ); #endif } diff --git a/audiostream/src/ni/media/audio/source/mp4_file_source.h b/audiostream/src/ni/media/audio/source/mp4_file_source.h index e5fe4679..26ec3b91 100644 --- a/audiostream/src/ni/media/audio/source/mp4_file_source.h +++ b/audiostream/src/ni/media/audio/source/mp4_file_source.h @@ -61,5 +61,7 @@ class mp4_file_source std::unique_ptr m_impl; #elif BOOST_OS_WINDOWS std::unique_ptr m_impl; +#elif BOOST_OS_LINUX + std::unique_ptr m_impl; #endif }; diff --git a/audiostream/src/ni/media/audio/source/wma_file_source.cpp b/audiostream/src/ni/media/audio/source/wma_file_source.cpp index aa4ddec7..61821808 100644 --- a/audiostream/src/ni/media/audio/source/wma_file_source.cpp +++ b/audiostream/src/ni/media/audio/source/wma_file_source.cpp @@ -22,7 +22,11 @@ #include +#if BOOST_OS_WINDOWS #include "media_foundation_file_source.h" +#elif BOOST_OS_LINUX +#include "gstreamer_file_source.h" +#endif //---------------------------------------------------------------------------------------------------------------------- @@ -47,7 +51,11 @@ wma_file_source::wma_file_source( const std::string& path ) void wma_file_source::open( const std::string& path ) { +#if BOOST_OS_WINDOWS m_impl.reset( new media_foundation_file_source( path, audio::ifstream_info::container_type::wma ) ); +#elif BOOST_OS_LINUX + m_impl.reset( new gstreamer_file_source( path, audio::ifstream_info::container_type::wma ) ); +#endif } //---------------------------------------------------------------------------------------------------------------------- diff --git a/audiostream/src/ni/media/audio/source/wma_file_source.h b/audiostream/src/ni/media/audio/source/wma_file_source.h index 30c09a6b..050a9701 100644 --- a/audiostream/src/ni/media/audio/source/wma_file_source.h +++ b/audiostream/src/ni/media/audio/source/wma_file_source.h @@ -26,6 +26,7 @@ #include #include +#include #include @@ -57,5 +58,9 @@ class wma_file_source auto info() const -> info_type; private: +#if BOOST_OS_WINDOWS std::unique_ptr m_impl; +#elif BOOST_OS_LINUX + std::unique_ptr m_impl; +#endif }; diff --git a/cmake/FindGObject.cmake b/cmake/FindGObject.cmake new file mode 100644 index 00000000..afb8cc8d --- /dev/null +++ b/cmake/FindGObject.cmake @@ -0,0 +1,5 @@ +find_package (PkgConfig REQUIRED) +pkg_check_modules (GOBJECT2 REQUIRED gobject-2.0) + +include_directories (${GOBJECT2_INCLUDE_DIRS}) +link_directories (${GOBJECT2_LIBRARY_DIRS}) diff --git a/cmake/FindGStreamer.cmake b/cmake/FindGStreamer.cmake new file mode 100644 index 00000000..066152b1 --- /dev/null +++ b/cmake/FindGStreamer.cmake @@ -0,0 +1,23 @@ +include(FindPackageHandleStandardArgs) + +find_package(PkgConfig REQUIRED) + +pkg_check_modules (GSTREAMER REQUIRED gstreamer-1.0) +find_library(GSTREAMER_LIBRARY NAMES gstreamer-1.0) + +find_package_handle_standard_args(GSTREAMER DEFAULT_MSG GSTREAMER_INCLUDE_DIRS GSTREAMER_LIBRARY) +mark_as_advanced(GSTREAMER_INCLUDE_DIRS GSTREAMER_LIBRARY) + + +if( GSTREAMER_FOUND ) + if( NOT TARGET GSTREAMER::gstreamer ) + add_library(GSTREAMER::gstreamer SHARED IMPORTED) + set_target_properties(GSTREAMER::gstreamer PROPERTIES + IMPORTED_LINK_INTERFACE_LANGUAGES "C" + IMPORTED_LOCATION ${GSTREAMER_LIBRARY} + INTERFACE_INCLUDE_DIRECTORIES "${GSTREAMER_INCLUDE_DIRS}" + INTERFACE_LINK_LIBRARIES "${GSTREAMER_LIBRARY}" + ) + endif() +endif() + diff --git a/cmake/FindGStreamerApp.cmake b/cmake/FindGStreamerApp.cmake new file mode 100644 index 00000000..ecb9d014 --- /dev/null +++ b/cmake/FindGStreamerApp.cmake @@ -0,0 +1,23 @@ +include(FindPackageHandleStandardArgs) + +find_package(PkgConfig REQUIRED) + +pkg_check_modules (GSTREAMERAPP REQUIRED gstreamer-app-1.0) +find_library(GSTREAMERAPP_LIBRARY NAMES gstapp-1.0) + +find_package_handle_standard_args(GSTREAMERAPP DEFAULT_MSG GSTREAMERAPP_INCLUDE_DIRS GSTREAMERAPP_LIBRARY) +mark_as_advanced(GSTREAMERAPP_INCLUDE_DIRS GSTREAMERAPP_LIBRARY) + + +if( GSTREAMERAPP_FOUND ) + if( NOT TARGET GSTREAMERAPP::gstreamerapp ) + add_library(GSTREAMERAPP::gstreamerapp SHARED IMPORTED) + set_target_properties(GSTREAMERAPP::gstreamerapp PROPERTIES + IMPORTED_LINK_INTERFACE_LANGUAGES "C" + IMPORTED_LOCATION ${GSTREAMERAPP_LIBRARY} + INTERFACE_INCLUDE_DIRECTORIES "${GSTREAMERAPP_INCLUDE_DIRS}" + INTERFACE_LINK_LIBRARIES "${GSTREAMERAPP_LIBRARY}" + ) + endif() +endif() + diff --git a/cmake/FindGlib.cmake b/cmake/FindGlib.cmake new file mode 100644 index 00000000..383f0712 --- /dev/null +++ b/cmake/FindGlib.cmake @@ -0,0 +1,5 @@ +find_package (PkgConfig REQUIRED) +pkg_check_modules (GLIB2 REQUIRED glib-2.0) + +include_directories (${GLIB2_INCLUDE_DIRS}) +link_directories (${GLIB2_LIBRARY_DIRS}) From 8f1123b3f3773e21385bab73ac6038dff9d3a030 Mon Sep 17 00:00:00 2001 From: Henry Hoegelow Date: Fri, 22 Dec 2017 17:14:27 +0100 Subject: [PATCH 17/28] fixed some tests, still some failing --- .../audio/source/gstreamer_file_source.cpp | 110 +++++++++++------- .../audio/source/gstreamer_file_source.h | 3 +- 2 files changed, 70 insertions(+), 43 deletions(-) diff --git a/audiostream/src/ni/media/audio/source/gstreamer_file_source.cpp b/audiostream/src/ni/media/audio/source/gstreamer_file_source.cpp index 170ccab5..5e929ef2 100644 --- a/audiostream/src/ni/media/audio/source/gstreamer_file_source.cpp +++ b/audiostream/src/ni/media/audio/source/gstreamer_file_source.cpp @@ -44,18 +44,22 @@ namespace detail { static constexpr size_t size = s; static constexpr size_t mask = (s - 1); - std::vector m_buffer; + std::array m_buffer; uint64_t m_write_head = 0; uint64_t m_read_head = 0; void push(const T* data, size_t count) { + auto filled = m_write_head - m_read_head; + auto space = s - filled; + assert(count <= space); + if(count) { auto idx = m_write_head & mask; auto space = size - idx; auto for_now = std::min(space, count); - std::copy(data, data + for_now, m_buffer.begin() + idx); + std::copy(data, data + for_now, m_buffer.data() + idx); m_write_head += for_now; push(data + for_now, count - for_now); } @@ -70,12 +74,17 @@ namespace detail auto idx = m_read_head & mask; auto space = size - idx; auto for_now = std::min(space, count); - std::copy(m_buffer.begin() + idx, m_buffer.begin() + idx + for_now, data); + std::copy(m_buffer.data() + idx, m_buffer.data() + idx + for_now, data); m_read_head += for_now; return for_now + pull(data + for_now, count - for_now); } return 0; } + + void flush() + { + m_read_head = m_write_head = 0; + } }; } @@ -92,6 +101,24 @@ gstreamer_file_source::gstreamer_file_source(const std::string& path, audio::ifs gstreamer_file_source::~gstreamer_file_source() { + gst_element_set_state(m_pipeline.get(), GST_STATE_NULL); + wait_for_async_operation(); + m_pipeline.reset(); +} + +//---------------------------------------------------------------------------------------------------------------------- + +GstState gstreamer_file_source::wait_for_async_operation() +{ + GstState current_state = GST_STATE_VOID_PENDING; + GstState pending_state = GST_STATE_VOID_PENDING; + + while(gst_element_get_state(m_pipeline.get(), ¤t_state, &pending_state, GST_MSECOND) == GST_STATE_CHANGE_ASYNC) + { + g_main_context_iteration(nullptr, FALSE); + } + + return current_state; } //---------------------------------------------------------------------------------------------------------------------- @@ -144,23 +171,15 @@ GstElement* gstreamer_file_source::prepare_pipeline(const std::string& path) void gstreamer_file_source::preroll_pipeline() { - gst_element_set_state(m_pipeline.get(), GST_STATE_PLAYING); - GstState current_state = GST_STATE_VOID_PENDING; - GstState pending_state = GST_STATE_VOID_PENDING; + gst_element_set_state(m_pipeline.get(), GST_STATE_PAUSED); - while(gst_element_get_state(m_pipeline.get(), ¤t_state, &pending_state, GST_MSECOND) == GST_STATE_CHANGE_ASYNC) - { - g_main_context_iteration(nullptr, FALSE); - } + if(wait_for_async_operation() != GST_STATE_PAUSED) + throw std::runtime_error( "gstreamer_file_source: pipeline doesn't preroll into paused state" ); - std::cerr << "Pipeline is in state: " << current_state << std::endl; + gst_element_set_state(m_pipeline.get(), GST_STATE_PLAYING); - if(current_state != GST_STATE_PLAYING) - { - auto res = gst_element_get_state(m_pipeline.get(), ¤t_state, &pending_state, GST_MSECOND); - std::cerr << "Pipeline does not preroll: " << res << " " << current_state << std::endl; - throw std::runtime_error( "gstreamer_file_source: pipeline doesn't preroll" ); - } + if(wait_for_async_operation() != GST_STATE_PLAYING) + throw std::runtime_error( "gstreamer_file_source: pipeline doesn't preroll into playing state" ); } //---------------------------------------------------------------------------------------------------------------------- @@ -221,6 +240,8 @@ void gstreamer_file_source::onPadAdded(GstElement* element, GstPad* pad, GstElem std::streampos gstreamer_file_source::seek(offset_type off, BOOST_IOS::seekdir way ) { + assert( 0 == off % m_info.bytes_per_frame() ); + int64_t newPosition = 0; if (way == BOOST_IOS::seekdir::_S_beg) @@ -234,18 +255,19 @@ std::streampos gstreamer_file_source::seek(offset_type off, BOOST_IOS::seekdir w else if (way == BOOST_IOS::seekdir::_S_end) { int64_t end = 0; - if(!gst_element_query_duration (m_pipeline.get(), GST_FORMAT_DEFAULT, &end)) + if(!gst_element_query_duration (m_pipeline.get(), GST_FORMAT_BYTES, &end)) { throw std::runtime_error( "gstreamer_file_source: seeking from end of file impossible - could not query duration" ); } newPosition = end + off; } - if(newPosition != m_position) + if(m_position != newPosition) { m_position = newPosition; + m_ring_buffer->flush(); - if(!gst_element_seek_simple(m_pipeline.get(), GST_FORMAT_DEFAULT, (GstSeekFlags)(GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE), m_position)) + if(!gst_element_seek_simple(m_pipeline.get(), GST_FORMAT_BYTES, (GstSeekFlags)(GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE), m_position)) { throw std::runtime_error( "gstreamer_file_source: seeking failed" ); } @@ -256,40 +278,44 @@ std::streampos gstreamer_file_source::seek(offset_type off, BOOST_IOS::seekdir w //---------------------------------------------------------------------------------------------------------------------- -std::streamsize gstreamer_file_source::read(char* dst, std::streamsize size) +std::streamsize gstreamer_file_source::read(char* dst, std::streamsize numBytesRequested) { - auto bytesPerFrame = m_info.bytes_per_frame(); - auto bufferedBytes = m_ring_buffer->pull(dst, size * bytesPerFrame); - auto bufferedFrames = bufferedBytes / bytesPerFrame; - dst += bufferedFrames; - size -= bufferedFrames; - - tGstPtr sink(gst_bin_get_by_name(GST_BIN(m_pipeline.get()), "sink"), gst_object_unref); - GstAppSink *app_sink = reinterpret_cast(sink.get()); + auto read_bytes = m_ring_buffer->pull(dst, numBytesRequested); - tGstPtr sample(gst_app_sink_pull_sample(app_sink), (GUnref) gst_sample_unref); + dst += read_bytes; + numBytesRequested -= read_bytes; + m_position += read_bytes; - if(sample) + if(numBytesRequested) { - auto buffer = gst_sample_get_buffer(sample.get()); + tGstPtr sink(gst_bin_get_by_name(GST_BIN(m_pipeline.get()), "sink"), gst_object_unref); + GstAppSink *app_sink = reinterpret_cast(sink.get()); - GstMapInfo mapped; + tGstPtr sample(gst_app_sink_pull_sample(app_sink), (GUnref) gst_sample_unref); - if(gst_buffer_map(buffer, &mapped, GST_MAP_READ)) + if(sample) { - auto numBytesRequested = size * bytesPerFrame; - auto for_now = std::min(mapped.size, numBytesRequested); + auto buffer = gst_sample_get_buffer(sample.get()); + + GstMapInfo mapped; - std::copy(mapped.data, mapped.data + for_now, dst); + if(gst_buffer_map(buffer, &mapped, GST_MAP_READ)) + { + auto for_now = std::min(mapped.size, numBytesRequested); + std::copy(mapped.data, mapped.data + for_now, dst); + read_bytes += for_now; + dst += for_now; + numBytesRequested -= for_now; + m_position += for_now; - m_ring_buffer->push((char*) mapped.data + for_now, mapped.size - for_now); - bufferedFrames += for_now / bytesPerFrame; + m_ring_buffer->push((char*) mapped.data + for_now, mapped.size - for_now); + gst_buffer_unmap(buffer, &mapped); - gst_buffer_unmap(buffer, &mapped); + return read_bytes + read(dst, numBytesRequested); + } } } - m_position += bufferedFrames; - return bufferedFrames; + return read_bytes; } diff --git a/audiostream/src/ni/media/audio/source/gstreamer_file_source.h b/audiostream/src/ni/media/audio/source/gstreamer_file_source.h index 88bbd40c..9575c630 100644 --- a/audiostream/src/ni/media/audio/source/gstreamer_file_source.h +++ b/audiostream/src/ni/media/audio/source/gstreamer_file_source.h @@ -69,6 +69,7 @@ class gstreamer_file_source void setup_source(const std::string& path, audio::ifstream_info::container_type container); GstElement* prepare_pipeline(const std::string& path); void preroll_pipeline(); + GstState wait_for_async_operation(); void fill_format_info(GstStructure *caps_struct, audio::ifstream_info::container_type container); static void onPadAdded(GstElement* element, GstPad* pad, GstElement* sink); @@ -78,7 +79,7 @@ class gstreamer_file_source template using tGstPtr = std::unique_ptr; tGstPtr m_pipeline; - using RingBuffer = detail::RingBuffer; + using RingBuffer = detail::RingBuffer; std::unique_ptr m_ring_buffer; int64_t m_position = 0; }; From e6a05c156a0dfe2fa51ab4ea8b5aed41e54980de Mon Sep 17 00:00:00 2001 From: Henry Hoegelow Date: Fri, 5 Jan 2018 16:23:02 +0100 Subject: [PATCH 18/28] improve readability --- .../ni/media/audio/source/gstreamer_file_source.cpp | 10 +++++++++- .../src/ni/media/audio/source/gstreamer_file_source.h | 1 + 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/audiostream/src/ni/media/audio/source/gstreamer_file_source.cpp b/audiostream/src/ni/media/audio/source/gstreamer_file_source.cpp index 5e929ef2..930aed43 100644 --- a/audiostream/src/ni/media/audio/source/gstreamer_file_source.cpp +++ b/audiostream/src/ni/media/audio/source/gstreamer_file_source.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include @@ -279,6 +280,13 @@ std::streampos gstreamer_file_source::seek(offset_type off, BOOST_IOS::seekdir w //---------------------------------------------------------------------------------------------------------------------- std::streamsize gstreamer_file_source::read(char* dst, std::streamsize numBytesRequested) +{ + return recursive_read(dst, numBytesRequested); +} + +//---------------------------------------------------------------------------------------------------------------------- + +std::streamsize gstreamer_file_source::recursive_read(char* dst, std::streamsize numBytesRequested) { auto read_bytes = m_ring_buffer->pull(dst, numBytesRequested); @@ -311,7 +319,7 @@ std::streamsize gstreamer_file_source::read(char* dst, std::streamsize numBytesR m_ring_buffer->push((char*) mapped.data + for_now, mapped.size - for_now); gst_buffer_unmap(buffer, &mapped); - return read_bytes + read(dst, numBytesRequested); + return read_bytes + recursive_read(dst, numBytesRequested); } } } diff --git a/audiostream/src/ni/media/audio/source/gstreamer_file_source.h b/audiostream/src/ni/media/audio/source/gstreamer_file_source.h index 9575c630..f3cb9f2f 100644 --- a/audiostream/src/ni/media/audio/source/gstreamer_file_source.h +++ b/audiostream/src/ni/media/audio/source/gstreamer_file_source.h @@ -71,6 +71,7 @@ class gstreamer_file_source void preroll_pipeline(); GstState wait_for_async_operation(); void fill_format_info(GstStructure *caps_struct, audio::ifstream_info::container_type container); + std::streamsize recursive_read(char* dst, std::streamsize numBytesRequested); static void onPadAdded(GstElement* element, GstPad* pad, GstElement* sink); From 7146d7374228cb70cf25b32650127a9f8c3f35ab Mon Sep 17 00:00:00 2001 From: Henry Hoegelow Date: Mon, 8 Jan 2018 14:44:14 +0100 Subject: [PATCH 19/28] add gstreamer dependencies for ci run --- .travis.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.travis.yml b/.travis.yml index b1ccb736..84508c88 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,6 +24,13 @@ matrix: - libogg-dev - libvorbis-dev - libflac++-dev + - gstreamer1.0-libav-dev + - gstreamer1.0-nice-dev + - gstreamer1.0-plugins-base-dev + - gstreamer1.0-plugins-good-dev + - gstreamer1.0-plugins-ugly-dev + - gstreamer1.0-plugins-bad-dev + - gstreamer1.0-plugins-bad-faad-dev env: MATRIX_EVAL="CONFIG=Release && CXX=g++-5" From eea5bb401f9c949a44536200bdafad23fb8eb357 Mon Sep 17 00:00:00 2001 From: Henry Hoegelow Date: Mon, 8 Jan 2018 14:48:51 +0100 Subject: [PATCH 20/28] fix intendation issue --- .travis.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 84508c88..a88a54a6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,13 +24,13 @@ matrix: - libogg-dev - libvorbis-dev - libflac++-dev - - gstreamer1.0-libav-dev - - gstreamer1.0-nice-dev - - gstreamer1.0-plugins-base-dev - - gstreamer1.0-plugins-good-dev - - gstreamer1.0-plugins-ugly-dev - - gstreamer1.0-plugins-bad-dev - - gstreamer1.0-plugins-bad-faad-dev + - gstreamer1.0-libav-dev + - gstreamer1.0-nice-dev + - gstreamer1.0-plugins-base-dev + - gstreamer1.0-plugins-good-dev + - gstreamer1.0-plugins-ugly-dev + - gstreamer1.0-plugins-bad-dev + - gstreamer1.0-plugins-bad-faad-dev env: MATRIX_EVAL="CONFIG=Release && CXX=g++-5" From 48c73f29b5f72eb210695debf95fa69ed02e28ef Mon Sep 17 00:00:00 2001 From: Henry Hoegelow Date: Mon, 8 Jan 2018 15:02:37 +0100 Subject: [PATCH 21/28] blindflight: configureing ubuntu packages not having ubuntu --- .travis.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index a88a54a6..3e432a73 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,13 +24,13 @@ matrix: - libogg-dev - libvorbis-dev - libflac++-dev - - gstreamer1.0-libav-dev - - gstreamer1.0-nice-dev - - gstreamer1.0-plugins-base-dev - - gstreamer1.0-plugins-good-dev - - gstreamer1.0-plugins-ugly-dev - - gstreamer1.0-plugins-bad-dev - - gstreamer1.0-plugins-bad-faad-dev + - gstreamer1.0-libav + - gstreamer1.0-plugins-base + - gstreamer1.0-plugins-good + - gstreamer1.0-plugins-ugly + - gstreamer1.0-plugins-bad + - gstreamer1.0-plugins-bad-faad + - libgstreamer1.0-dev env: MATRIX_EVAL="CONFIG=Release && CXX=g++-5" From 7dde3c71cb2040c948a067d91ddac029e6964062 Mon Sep 17 00:00:00 2001 From: Henry Hoegelow Date: Mon, 8 Jan 2018 15:14:50 +0100 Subject: [PATCH 22/28] blindflight: configureing ubuntu packages not having ubuntu --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 3e432a73..275f6ff0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,6 +31,7 @@ matrix: - gstreamer1.0-plugins-bad - gstreamer1.0-plugins-bad-faad - libgstreamer1.0-dev + - libgstreamer-plugins-base1.0-dev env: MATRIX_EVAL="CONFIG=Release && CXX=g++-5" From 44d9df955c6a1f659cb5021ea8fff0f222a35e33 Mon Sep 17 00:00:00 2001 From: Henry Hoegelow Date: Thu, 11 Jan 2018 08:12:25 +0100 Subject: [PATCH 23/28] fix formatting (apply clang-format) --- .../audio/source/gstreamer_file_source.cpp | 353 +++++++++--------- .../audio/source/gstreamer_file_source.h | 50 +-- 2 files changed, 207 insertions(+), 196 deletions(-) diff --git a/audiostream/src/ni/media/audio/source/gstreamer_file_source.cpp b/audiostream/src/ni/media/audio/source/gstreamer_file_source.cpp index 930aed43..00497199 100644 --- a/audiostream/src/ni/media/audio/source/gstreamer_file_source.cpp +++ b/audiostream/src/ni/media/audio/source/gstreamer_file_source.cpp @@ -30,300 +30,307 @@ #include #include +#include #include #include -#include #include -#include +#include #include namespace detail { - template - struct RingBuffer +template +struct RingBuffer +{ + static constexpr size_t size = s; + static constexpr size_t mask = ( s - 1 ); + std::array m_buffer; + uint64_t m_write_head = 0; + uint64_t m_read_head = 0; + + void push( const T* data, size_t count ) { - static constexpr size_t size = s; - static constexpr size_t mask = (s - 1); - std::array m_buffer; - uint64_t m_write_head = 0; - uint64_t m_read_head = 0; + auto filled = m_write_head - m_read_head; + auto space = s - filled; + assert( count <= space ); - void push(const T* data, size_t count) + if ( count ) { - auto filled = m_write_head - m_read_head; - auto space = s - filled; - assert(count <= space); - - if(count) - { - auto idx = m_write_head & mask; - auto space = size - idx; - auto for_now = std::min(space, count); - std::copy(data, data + for_now, m_buffer.data() + idx); + auto idx = m_write_head & mask; + auto space = size - idx; + auto for_now = std::min( space, count ); + std::copy( data, data + for_now, m_buffer.data() + idx ); m_write_head += for_now; - push(data + for_now, count - for_now); - } + push( data + for_now, count - for_now ); } + } + + size_t pull( T* data, size_t count ) + { + count = std::min( count, m_write_head - m_read_head ); - size_t pull(T* data, size_t count) + if ( count ) { - count = std::min(count, m_write_head - m_read_head); - - if(count) - { - auto idx = m_read_head & mask; - auto space = size - idx; - auto for_now = std::min(space, count); - std::copy(m_buffer.data() + idx, m_buffer.data() + idx + for_now, data); + auto idx = m_read_head & mask; + auto space = size - idx; + auto for_now = std::min( space, count ); + std::copy( m_buffer.data() + idx, m_buffer.data() + idx + for_now, data ); m_read_head += for_now; - return for_now + pull(data + for_now, count - for_now); - } - return 0; + return for_now + pull( data + for_now, count - for_now ); } + return 0; + } - void flush() - { - m_read_head = m_write_head = 0; - } - }; + void flush() + { + m_read_head = m_write_head = 0; + } +}; } //---------------------------------------------------------------------------------------------------------------------- -gstreamer_file_source::gstreamer_file_source(const std::string& path, audio::ifstream_info::container_type container, size_t stream) : - m_pipeline(nullptr, gst_object_unref), - m_ring_buffer(new RingBuffer()) +gstreamer_file_source::gstreamer_file_source( const std::string& path, + audio::ifstream_info::container_type container, + size_t stream ) +: m_pipeline( nullptr, gst_object_unref ) +, m_ring_buffer( new RingBuffer() ) { - init_gstreamer(); - setup_source(path, container); + init_gstreamer(); + setup_source( path, container ); } //---------------------------------------------------------------------------------------------------------------------- gstreamer_file_source::~gstreamer_file_source() { - gst_element_set_state(m_pipeline.get(), GST_STATE_NULL); - wait_for_async_operation(); - m_pipeline.reset(); + gst_element_set_state( m_pipeline.get(), GST_STATE_NULL ); + wait_for_async_operation(); + m_pipeline.reset(); } //---------------------------------------------------------------------------------------------------------------------- GstState gstreamer_file_source::wait_for_async_operation() { - GstState current_state = GST_STATE_VOID_PENDING; - GstState pending_state = GST_STATE_VOID_PENDING; + GstState current_state = GST_STATE_VOID_PENDING; + GstState pending_state = GST_STATE_VOID_PENDING; - while(gst_element_get_state(m_pipeline.get(), ¤t_state, &pending_state, GST_MSECOND) == GST_STATE_CHANGE_ASYNC) - { - g_main_context_iteration(nullptr, FALSE); - } + while ( gst_element_get_state( m_pipeline.get(), ¤t_state, &pending_state, GST_MSECOND ) + == GST_STATE_CHANGE_ASYNC ) + { + g_main_context_iteration( nullptr, FALSE ); + } - return current_state; + return current_state; } //---------------------------------------------------------------------------------------------------------------------- void gstreamer_file_source::init_gstreamer() { - static bool gstreamer_initialized = false; + static bool gstreamer_initialized = false; - if(!std::exchange(gstreamer_initialized, true)) - { - gst_init(0, nullptr); - } + if ( !std::exchange( gstreamer_initialized, true ) ) + { + gst_init( 0, nullptr ); + } } //---------------------------------------------------------------------------------------------------------------------- -void gstreamer_file_source::setup_source(const std::string& path, audio::ifstream_info::container_type container) +void gstreamer_file_source::setup_source( const std::string& path, audio::ifstream_info::container_type container ) { - GstElement* sink = prepare_pipeline(path); - auto sinkpad = gst_element_get_static_pad(sink, "sink"); - auto caps = gst_pad_get_current_caps(sinkpad); - auto caps_struct = gst_caps_get_structure(caps, 0); - fill_format_info(caps_struct, container); + GstElement* sink = prepare_pipeline( path ); + auto sinkpad = gst_element_get_static_pad( sink, "sink" ); + auto caps = gst_pad_get_current_caps( sinkpad ); + auto caps_struct = gst_caps_get_structure( caps, 0 ); + fill_format_info( caps_struct, container ); } //---------------------------------------------------------------------------------------------------------------------- -GstElement* gstreamer_file_source::prepare_pipeline(const std::string& path) +GstElement* gstreamer_file_source::prepare_pipeline( const std::string& path ) { - m_pipeline.reset(gst_pipeline_new("pipeline")); + m_pipeline.reset( gst_pipeline_new( "pipeline" ) ); - GstElement* source = gst_element_factory_make("filesrc", "source"); - GstElement* decodebin = gst_element_factory_make("decodebin", "decoder"); - GstElement* queue = gst_element_factory_make("queue", "queue"); - GstElement* sink = gst_element_factory_make("appsink", "sink"); + GstElement* source = gst_element_factory_make( "filesrc", "source" ); + GstElement* decodebin = gst_element_factory_make( "decodebin", "decoder" ); + GstElement* queue = gst_element_factory_make( "queue", "queue" ); + GstElement* sink = gst_element_factory_make( "appsink", "sink" ); - gst_bin_add_many(GST_BIN(m_pipeline.get()), source, decodebin, queue, sink, nullptr); - gst_element_link_many(source, decodebin, NULL); - gst_element_link_many(queue, sink, NULL); + gst_bin_add_many( GST_BIN( m_pipeline.get() ), source, decodebin, queue, sink, nullptr ); + gst_element_link_many( source, decodebin, NULL ); + gst_element_link_many( queue, sink, NULL ); - g_object_set(source, "location", path.c_str(), NULL); + g_object_set( source, "location", path.c_str(), NULL ); - g_signal_connect(decodebin, "pad-added", G_CALLBACK(&onPadAdded), queue); + g_signal_connect( decodebin, "pad-added", G_CALLBACK( &onPadAdded ), queue ); - preroll_pipeline(); - return sink; + preroll_pipeline(); + return sink; } //---------------------------------------------------------------------------------------------------------------------- void gstreamer_file_source::preroll_pipeline() { - gst_element_set_state(m_pipeline.get(), GST_STATE_PAUSED); + gst_element_set_state( m_pipeline.get(), GST_STATE_PAUSED ); - if(wait_for_async_operation() != GST_STATE_PAUSED) - throw std::runtime_error( "gstreamer_file_source: pipeline doesn't preroll into paused state" ); + if ( wait_for_async_operation() != GST_STATE_PAUSED ) + throw std::runtime_error( "gstreamer_file_source: pipeline doesn't preroll into paused state" ); - gst_element_set_state(m_pipeline.get(), GST_STATE_PLAYING); + gst_element_set_state( m_pipeline.get(), GST_STATE_PLAYING ); - if(wait_for_async_operation() != GST_STATE_PLAYING) - throw std::runtime_error( "gstreamer_file_source: pipeline doesn't preroll into playing state" ); + if ( wait_for_async_operation() != GST_STATE_PLAYING ) + throw std::runtime_error( "gstreamer_file_source: pipeline doesn't preroll into playing state" ); } //---------------------------------------------------------------------------------------------------------------------- -void gstreamer_file_source::fill_format_info(GstStructure *caps_struct, audio::ifstream_info::container_type container) +void gstreamer_file_source::fill_format_info( GstStructure* caps_struct, + audio::ifstream_info::container_type container ) { - m_info.container(container); - m_info.codec(audio::ifstream_info::codec_type::mp3); - m_info.lossless(false); + m_info.container( container ); + m_info.codec( audio::ifstream_info::codec_type::mp3 ); + m_info.lossless( false ); - gint64 num_frames = 0; - gst_element_query_duration(m_pipeline.get(), GST_FORMAT_DEFAULT, &num_frames); - m_info.num_frames(num_frames); + gint64 num_frames = 0; + gst_element_query_duration( m_pipeline.get(), GST_FORMAT_DEFAULT, &num_frames ); + m_info.num_frames( num_frames ); - int sample_rate = 0; - gst_structure_get_int(caps_struct, "rate", &sample_rate); - m_info.sample_rate(sample_rate); + int sample_rate = 0; + gst_structure_get_int( caps_struct, "rate", &sample_rate ); + m_info.sample_rate( sample_rate ); - int num_channels = 0; - gst_structure_get_int(caps_struct, "channels", &num_channels); - m_info.num_channels(num_channels); + int num_channels = 0; + gst_structure_get_int( caps_struct, "channels", &num_channels ); + m_info.num_channels( num_channels ); - m_info.format(create_runtime_format(caps_struct)); + m_info.format( create_runtime_format( caps_struct ) ); } //---------------------------------------------------------------------------------------------------------------------- -pcm::runtime_format gstreamer_file_source::create_runtime_format(GstStructure* caps_struct) +pcm::runtime_format gstreamer_file_source::create_runtime_format( GstStructure* caps_struct ) { - const gchar* format = gst_structure_get_string(caps_struct, "format"); - pcm::number_type number_type = gst_format_char_to_number_type(format[0]); - auto srcDepth = std::atol(format + 1); - auto endian = (strcmp(format, "BE") == 0) ? pcm::big_endian : pcm::little_endian; - return pcm::runtime_format(number_type, srcDepth, endian); + const gchar* format = gst_structure_get_string( caps_struct, "format" ); + pcm::number_type number_type = gst_format_char_to_number_type( format[0] ); + auto srcDepth = std::atol( format + 1 ); + auto endian = ( strcmp( format, "BE" ) == 0 ) ? pcm::big_endian : pcm::little_endian; + return pcm::runtime_format( number_type, srcDepth, endian ); } //---------------------------------------------------------------------------------------------------------------------- -pcm::number_type gstreamer_file_source::gst_format_char_to_number_type(const gchar format) +pcm::number_type gstreamer_file_source::gst_format_char_to_number_type( const gchar format ) { - if(format == 'U') - return pcm::unsigned_integer; - else if(format == 'F') - return pcm::floating_point; + if ( format == 'U' ) + return pcm::unsigned_integer; + else if ( format == 'F' ) + return pcm::floating_point; - return pcm::signed_integer; + return pcm::signed_integer; } //---------------------------------------------------------------------------------------------------------------------- -void gstreamer_file_source::onPadAdded(GstElement* element, GstPad* pad, GstElement* sink) +void gstreamer_file_source::onPadAdded( GstElement* element, GstPad* pad, GstElement* sink ) { - tGstPtr sinkpad(gst_element_get_static_pad(sink, "sink"), gst_object_unref); - gst_pad_link(pad, sinkpad.get()); + tGstPtr sinkpad( gst_element_get_static_pad( sink, "sink" ), gst_object_unref ); + gst_pad_link( pad, sinkpad.get() ); } //---------------------------------------------------------------------------------------------------------------------- -std::streampos gstreamer_file_source::seek(offset_type off, BOOST_IOS::seekdir way ) +std::streampos gstreamer_file_source::seek( offset_type off, BOOST_IOS::seekdir way ) { - assert( 0 == off % m_info.bytes_per_frame() ); - - int64_t newPosition = 0; - - if (way == BOOST_IOS::seekdir::_S_beg) - { - newPosition = off; - } - else if (way == BOOST_IOS::seekdir::_S_cur) - { - newPosition = m_position + off; - } - else if (way == BOOST_IOS::seekdir::_S_end) - { - int64_t end = 0; - if(!gst_element_query_duration (m_pipeline.get(), GST_FORMAT_BYTES, &end)) + assert( 0 == off % m_info.bytes_per_frame() ); + + int64_t newPosition = 0; + + if ( way == BOOST_IOS::seekdir::_S_beg ) { - throw std::runtime_error( "gstreamer_file_source: seeking from end of file impossible - could not query duration" ); + newPosition = off; + } + else if ( way == BOOST_IOS::seekdir::_S_cur ) + { + newPosition = m_position + off; + } + else if ( way == BOOST_IOS::seekdir::_S_end ) + { + int64_t end = 0; + if ( !gst_element_query_duration( m_pipeline.get(), GST_FORMAT_BYTES, &end ) ) + { + throw std::runtime_error( + "gstreamer_file_source: seeking from end of file impossible - could not query duration" ); + } + newPosition = end + off; } - newPosition = end + off; - } - - if(m_position != newPosition) - { - m_position = newPosition; - m_ring_buffer->flush(); - if(!gst_element_seek_simple(m_pipeline.get(), GST_FORMAT_BYTES, (GstSeekFlags)(GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE), m_position)) + if ( m_position != newPosition ) { - throw std::runtime_error( "gstreamer_file_source: seeking failed" ); + m_position = newPosition; + m_ring_buffer->flush(); + + if ( !gst_element_seek_simple( m_pipeline.get(), + GST_FORMAT_BYTES, + ( GstSeekFlags )( GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE ), + m_position ) ) + { + throw std::runtime_error( "gstreamer_file_source: seeking failed" ); + } } - } - return m_position; + return m_position; } //---------------------------------------------------------------------------------------------------------------------- -std::streamsize gstreamer_file_source::read(char* dst, std::streamsize numBytesRequested) +std::streamsize gstreamer_file_source::read( char* dst, std::streamsize numBytesRequested ) { - return recursive_read(dst, numBytesRequested); + return recursive_read( dst, numBytesRequested ); } //---------------------------------------------------------------------------------------------------------------------- -std::streamsize gstreamer_file_source::recursive_read(char* dst, std::streamsize numBytesRequested) +std::streamsize gstreamer_file_source::recursive_read( char* dst, std::streamsize numBytesRequested ) { - auto read_bytes = m_ring_buffer->pull(dst, numBytesRequested); + auto read_bytes = m_ring_buffer->pull( dst, numBytesRequested ); - dst += read_bytes; - numBytesRequested -= read_bytes; - m_position += read_bytes; + dst += read_bytes; + numBytesRequested -= read_bytes; + m_position += read_bytes; - if(numBytesRequested) - { - tGstPtr sink(gst_bin_get_by_name(GST_BIN(m_pipeline.get()), "sink"), gst_object_unref); - GstAppSink *app_sink = reinterpret_cast(sink.get()); + if ( numBytesRequested ) + { + tGstPtr sink( gst_bin_get_by_name( GST_BIN( m_pipeline.get() ), "sink" ), gst_object_unref ); + GstAppSink* app_sink = reinterpret_cast( sink.get() ); - tGstPtr sample(gst_app_sink_pull_sample(app_sink), (GUnref) gst_sample_unref); + tGstPtr sample( gst_app_sink_pull_sample( app_sink ), (GUnref) gst_sample_unref ); - if(sample) - { - auto buffer = gst_sample_get_buffer(sample.get()); + if ( sample ) + { + auto buffer = gst_sample_get_buffer( sample.get() ); - GstMapInfo mapped; + GstMapInfo mapped; - if(gst_buffer_map(buffer, &mapped, GST_MAP_READ)) - { - auto for_now = std::min(mapped.size, numBytesRequested); - std::copy(mapped.data, mapped.data + for_now, dst); - read_bytes += for_now; - dst += for_now; - numBytesRequested -= for_now; - m_position += for_now; + if ( gst_buffer_map( buffer, &mapped, GST_MAP_READ ) ) + { + auto for_now = std::min( mapped.size, numBytesRequested ); + std::copy( mapped.data, mapped.data + for_now, dst ); + read_bytes += for_now; + dst += for_now; + numBytesRequested -= for_now; + m_position += for_now; - m_ring_buffer->push((char*) mapped.data + for_now, mapped.size - for_now); - gst_buffer_unmap(buffer, &mapped); + m_ring_buffer->push( (char*) mapped.data + for_now, mapped.size - for_now ); + gst_buffer_unmap( buffer, &mapped ); - return read_bytes + recursive_read(dst, numBytesRequested); - } + return read_bytes + recursive_read( dst, numBytesRequested ); + } + } } - } - return read_bytes; + return read_bytes; } - diff --git a/audiostream/src/ni/media/audio/source/gstreamer_file_source.h b/audiostream/src/ni/media/audio/source/gstreamer_file_source.h index f3cb9f2f..a583720e 100644 --- a/audiostream/src/ni/media/audio/source/gstreamer_file_source.h +++ b/audiostream/src/ni/media/audio/source/gstreamer_file_source.h @@ -27,60 +27,64 @@ #include #include -#include #include +#include namespace detail { - template struct RingBuffer; +template +struct RingBuffer; } class gstreamer_file_source { - public: +public: //---------------------------------------------------------------------------------------------------------------------- using offset_type = boost::iostreams::stream_offset; - using FrameRange = boost::icl::right_open_interval; + using FrameRange = boost::icl::right_open_interval; using FrameRangeSet = boost::icl::interval_set; - template - using MfTypePtr = std::unique_ptr>; + template + using MfTypePtr = std::unique_ptr>; //---------------------------------------------------------------------------------------------------------------------- - explicit gstreamer_file_source(const std::string& path, audio::ifstream_info::container_type container, size_t stream = 0); + explicit gstreamer_file_source( const std::string& path, + audio::ifstream_info::container_type container, + size_t stream = 0 ); ~gstreamer_file_source(); audio::ifstream_info info() const { - return m_info; + return m_info; } - std::streampos seek(offset_type, BOOST_IOS::seekdir ); + std::streampos seek( offset_type, BOOST_IOS::seekdir ); std::streamsize read( char*, std::streamsize ); - private: - static void init_gstreamer(); - static pcm::runtime_format create_runtime_format(GstStructure* caps_struct); - static pcm::number_type gst_format_char_to_number_type(const gchar format); +private: + static void init_gstreamer(); + static pcm::runtime_format create_runtime_format( GstStructure* caps_struct ); + static pcm::number_type gst_format_char_to_number_type( const gchar format ); - void setup_source(const std::string& path, audio::ifstream_info::container_type container); - GstElement* prepare_pipeline(const std::string& path); - void preroll_pipeline(); + void setup_source( const std::string& path, audio::ifstream_info::container_type container ); + GstElement* prepare_pipeline( const std::string& path ); + void preroll_pipeline(); GstState wait_for_async_operation(); - void fill_format_info(GstStructure *caps_struct, audio::ifstream_info::container_type container); - std::streamsize recursive_read(char* dst, std::streamsize numBytesRequested); + void fill_format_info( GstStructure* caps_struct, audio::ifstream_info::container_type container ); + std::streamsize recursive_read( char* dst, std::streamsize numBytesRequested ); - static void onPadAdded(GstElement* element, GstPad* pad, GstElement* sink); + static void onPadAdded( GstElement* element, GstPad* pad, GstElement* sink ); audio::ifstream_info m_info; - using GUnref = void(*)(gpointer); - template using tGstPtr = std::unique_ptr; + using GUnref = void ( * )( gpointer ); + template + using tGstPtr = std::unique_ptr; tGstPtr m_pipeline; using RingBuffer = detail::RingBuffer; std::unique_ptr m_ring_buffer; - int64_t m_position = 0; - }; + int64_t m_position = 0; +}; From 7023f2f5b5c0e9c132a15963e0ffb524e7cb723d Mon Sep 17 00:00:00 2001 From: Henry Hoegelow Date: Thu, 11 Jan 2018 08:13:26 +0100 Subject: [PATCH 24/28] fix alignment --- audiostream/CMakeLists.txt | 4 ++-- cmake/FindGStreamer.cmake | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/audiostream/CMakeLists.txt b/audiostream/CMakeLists.txt index 1556f32a..b57c76f9 100644 --- a/audiostream/CMakeLists.txt +++ b/audiostream/CMakeLists.txt @@ -80,7 +80,7 @@ endif() set( COMPILE_WITH_COREAUDIO DONT_COMPILE) set( COMPILE_WITH_MEDIA_FOUNDATION DONT_COMPILE) -set( COMPILE_WITH_GSTREAMER DONT_COMPILE) +set( COMPILE_WITH_GSTREAMER DONT_COMPILE) #----------------------------------------------------------------------------------------------------------------------- # dependencies @@ -266,7 +266,7 @@ add_src_file (FILES_media_audio_source "src/ni/media/audio/source/container_sou add_src_file (FILES_media_audio_source "src/ni/media/audio/source/core_audio_file_source.cpp" ${COMPILE_WITH_COREAUDIO} WITH_HEADER ) add_src_file (FILES_media_audio_source "src/ni/media/audio/source/media_foundation_helper.h" ${COMPILE_WITH_MEDIA_FOUNDATION} ) add_src_file (FILES_media_audio_source "src/ni/media/audio/source/media_foundation_file_source.cpp" ${COMPILE_WITH_MEDIA_FOUNDATION} WITH_HEADER ) -add_src_file (FILES_media_audio_source "src/ni/media/audio/source/gstreamer_file_source.cpp" ${COMPILE_WITH_GSTREAMER} WITH_HEADER ) +add_src_file (FILES_media_audio_source "src/ni/media/audio/source/gstreamer_file_source.cpp" ${COMPILE_WITH_GSTREAMER} WITH_HEADER ) add_src_file (FILES_media_audio_source "src/ni/media/audio/source/aiff_source.h" ${COMPILE_WITH_AIFF_DECODING} ) add_src_file (FILES_media_audio_source "src/ni/media/audio/source/aiff_file_source.h" ${COMPILE_WITH_AIFF_DECODING} ) add_src_file (FILES_media_audio_source "src/ni/media/audio/source/aiff_vector_source.h" ${COMPILE_WITH_AIFF_DECODING} ) diff --git a/cmake/FindGStreamer.cmake b/cmake/FindGStreamer.cmake index 066152b1..61b1df45 100644 --- a/cmake/FindGStreamer.cmake +++ b/cmake/FindGStreamer.cmake @@ -10,7 +10,7 @@ mark_as_advanced(GSTREAMER_INCLUDE_DIRS GSTREAMER_LIBRARY) if( GSTREAMER_FOUND ) - if( NOT TARGET GSTREAMER::gstreamer ) + if( NOT TARGET GSTREAMER::gstreamer ) add_library(GSTREAMER::gstreamer SHARED IMPORTED) set_target_properties(GSTREAMER::gstreamer PROPERTIES IMPORTED_LINK_INTERFACE_LANGUAGES "C" From 59bd834849677b9b56b045c7289f7304c4e4fad2 Mon Sep 17 00:00:00 2001 From: Henry Hoegelow Date: Wed, 17 Jan 2018 08:27:57 +0100 Subject: [PATCH 25/28] incorporated some of the pull request comments --- .../audio/source/gstreamer_file_source.cpp | 33 ++++++++++--------- .../audio/source/gstreamer_file_source.h | 8 ----- 2 files changed, 17 insertions(+), 24 deletions(-) diff --git a/audiostream/src/ni/media/audio/source/gstreamer_file_source.cpp b/audiostream/src/ni/media/audio/source/gstreamer_file_source.cpp index 00497199..67f3226a 100644 --- a/audiostream/src/ni/media/audio/source/gstreamer_file_source.cpp +++ b/audiostream/src/ni/media/audio/source/gstreamer_file_source.cpp @@ -25,17 +25,9 @@ #include #include -#include -#include -#include #include #include -#include -#include -#include -#include - #include namespace detail @@ -196,15 +188,21 @@ void gstreamer_file_source::fill_format_info( GstStructure* m_info.lossless( false ); gint64 num_frames = 0; - gst_element_query_duration( m_pipeline.get(), GST_FORMAT_DEFAULT, &num_frames ); + if ( !gst_element_query_duration( m_pipeline.get(), GST_FORMAT_DEFAULT, &num_frames ) ) + throw std::runtime_error( "gstreamer_file_source: could not query duration from gstreamer" ); + m_info.num_frames( num_frames ); int sample_rate = 0; - gst_structure_get_int( caps_struct, "rate", &sample_rate ); + if ( !gst_structure_get_int( caps_struct, "rate", &sample_rate ) ) + throw std::runtime_error( "gstreamer_file_source: could not query sample rate from gstreamer" ); + m_info.sample_rate( sample_rate ); int num_channels = 0; - gst_structure_get_int( caps_struct, "channels", &num_channels ); + if ( !gst_structure_get_int( caps_struct, "channels", &num_channels ) ) + throw std::runtime_error( "gstreamer_file_source: could not query number of channels from gstreamer" ); + m_info.num_channels( num_channels ); m_info.format( create_runtime_format( caps_struct ) ); @@ -214,11 +212,14 @@ void gstreamer_file_source::fill_format_info( GstStructure* pcm::runtime_format gstreamer_file_source::create_runtime_format( GstStructure* caps_struct ) { - const gchar* format = gst_structure_get_string( caps_struct, "format" ); - pcm::number_type number_type = gst_format_char_to_number_type( format[0] ); - auto srcDepth = std::atol( format + 1 ); - auto endian = ( strcmp( format, "BE" ) == 0 ) ? pcm::big_endian : pcm::little_endian; - return pcm::runtime_format( number_type, srcDepth, endian ); + if ( auto format = gst_structure_get_string( caps_struct, "format" ) ) + { + pcm::number_type number_type = gst_format_char_to_number_type( format[0] ); + auto srcDepth = std::atol( format + 1 ); + auto endian = ( strcmp( format, "BE" ) == 0 ) ? pcm::big_endian : pcm::little_endian; + return pcm::runtime_format( number_type, srcDepth, endian ); + } + throw std::runtime_error( "gstreamer_file_source: could not get runtime format from gstreamer caps" ); } //---------------------------------------------------------------------------------------------------------------------- diff --git a/audiostream/src/ni/media/audio/source/gstreamer_file_source.h b/audiostream/src/ni/media/audio/source/gstreamer_file_source.h index a583720e..1163d610 100644 --- a/audiostream/src/ni/media/audio/source/gstreamer_file_source.h +++ b/audiostream/src/ni/media/audio/source/gstreamer_file_source.h @@ -22,8 +22,6 @@ #pragma once -#include -#include #include #include @@ -43,12 +41,6 @@ class gstreamer_file_source using offset_type = boost::iostreams::stream_offset; - using FrameRange = boost::icl::right_open_interval; - using FrameRangeSet = boost::icl::interval_set; - - template - using MfTypePtr = std::unique_ptr>; - //---------------------------------------------------------------------------------------------------------------------- explicit gstreamer_file_source( const std::string& path, From 54ea50df30a75d1305622828d6f3cd9f727d2d56 Mon Sep 17 00:00:00 2001 From: Henry Hoegelow Date: Mon, 16 Apr 2018 22:39:57 +0200 Subject: [PATCH 26/28] incorporated some of sdroege's comments --- .../audio/source/gstreamer_file_source.cpp | 59 ++++++++++++------- .../audio/source/gstreamer_file_source.h | 14 +++-- 2 files changed, 45 insertions(+), 28 deletions(-) diff --git a/audiostream/src/ni/media/audio/source/gstreamer_file_source.cpp b/audiostream/src/ni/media/audio/source/gstreamer_file_source.cpp index 67f3226a..929a2e2e 100644 --- a/audiostream/src/ni/media/audio/source/gstreamer_file_source.cpp +++ b/audiostream/src/ni/media/audio/source/gstreamer_file_source.cpp @@ -28,7 +28,6 @@ #include #include -#include namespace detail { @@ -37,9 +36,9 @@ struct RingBuffer { static constexpr size_t size = s; static constexpr size_t mask = ( s - 1 ); - std::array m_buffer; - uint64_t m_write_head = 0; - uint64_t m_read_head = 0; + std::array m_buffer; + uint64_t m_write_head = 0; + uint64_t m_read_head = 0; void push( const T* data, size_t count ) { @@ -86,6 +85,7 @@ gstreamer_file_source::gstreamer_file_source( const std::string& audio::ifstream_info::container_type container, size_t stream ) : m_pipeline( nullptr, gst_object_unref ) +, m_sink( nullptr, gst_object_unref ) , m_ring_buffer( new RingBuffer() ) { init_gstreamer(); @@ -133,10 +133,10 @@ void gstreamer_file_source::init_gstreamer() void gstreamer_file_source::setup_source( const std::string& path, audio::ifstream_info::container_type container ) { - GstElement* sink = prepare_pipeline( path ); - auto sinkpad = gst_element_get_static_pad( sink, "sink" ); - auto caps = gst_pad_get_current_caps( sinkpad ); - auto caps_struct = gst_caps_get_structure( caps, 0 ); + m_sink.reset( prepare_pipeline( path ) ); + auto sinkpad = gst_element_get_static_pad( m_sink.get(), "sink" ); + auto caps = gst_pad_get_current_caps( sinkpad ); + auto caps_struct = gst_caps_get_structure( caps, 0 ); fill_format_info( caps_struct, container ); } @@ -151,11 +151,12 @@ GstElement* gstreamer_file_source::prepare_pipeline( const std::string& path ) GstElement* queue = gst_element_factory_make( "queue", "queue" ); GstElement* sink = gst_element_factory_make( "appsink", "sink" ); - gst_bin_add_many( GST_BIN( m_pipeline.get() ), source, decodebin, queue, sink, nullptr ); + gst_bin_add_many( GST_BIN( m_pipeline.get() ), source, decodebin, queue, gst_object_ref( sink ), nullptr ); gst_element_link_many( source, decodebin, NULL ); gst_element_link_many( queue, sink, NULL ); g_object_set( source, "location", path.c_str(), NULL ); + g_object_set( sink, "sync", FALSE, NULL ); g_signal_connect( decodebin, "pad-added", G_CALLBACK( &onPadAdded ), queue ); @@ -187,18 +188,19 @@ void gstreamer_file_source::fill_format_info( GstStructure* m_info.codec( audio::ifstream_info::codec_type::mp3 ); m_info.lossless( false ); - gint64 num_frames = 0; - if ( !gst_element_query_duration( m_pipeline.get(), GST_FORMAT_DEFAULT, &num_frames ) ) - throw std::runtime_error( "gstreamer_file_source: could not query duration from gstreamer" ); - - m_info.num_frames( num_frames ); - int sample_rate = 0; if ( !gst_structure_get_int( caps_struct, "rate", &sample_rate ) ) throw std::runtime_error( "gstreamer_file_source: could not query sample rate from gstreamer" ); m_info.sample_rate( sample_rate ); + gint64 num_ns = 0; + if ( !gst_element_query_duration( m_pipeline.get(), GST_FORMAT_TIME, &num_ns ) ) + throw std::runtime_error( "gstreamer_file_source: could not query duration from gstreamer" ); + + gint64 num_frames = sample_rate * num_ns / GST_SECOND; + m_info.num_frames( num_frames ); + int num_channels = 0; if ( !gst_structure_get_int( caps_struct, "channels", &num_channels ) ) throw std::runtime_error( "gstreamer_file_source: could not query number of channels from gstreamer" ); @@ -239,7 +241,21 @@ pcm::number_type gstreamer_file_source::gst_format_char_to_number_type( const gc void gstreamer_file_source::onPadAdded( GstElement* element, GstPad* pad, GstElement* sink ) { tGstPtr sinkpad( gst_element_get_static_pad( sink, "sink" ), gst_object_unref ); - gst_pad_link( pad, sinkpad.get() ); + + if ( gst_pad_is_linked( sinkpad.get() ) ) + return; // already linked + + tGstPtr caps( gst_pad_get_current_caps( pad ), gst_object_unref ); + auto s = gst_caps_get_structure( caps.get(), 0 ); + auto name = gst_structure_get_name( s ); + + if ( !g_str_has_prefix( name, "audio/" ) ) + return; // not the kind of pad we are looking for, maybe video? + + auto result = gst_pad_link( pad, sinkpad.get() ); + + if ( result != GST_PAD_LINK_OK ) + throw std::runtime_error( "gstreamer_file_source: could not link pad" ); } //---------------------------------------------------------------------------------------------------------------------- @@ -305,14 +321,13 @@ std::streamsize gstreamer_file_source::recursive_read( char* dst, std::streamsiz if ( numBytesRequested ) { - tGstPtr sink( gst_bin_get_by_name( GST_BIN( m_pipeline.get() ), "sink" ), gst_object_unref ); - GstAppSink* app_sink = reinterpret_cast( sink.get() ); - - tGstPtr sample( gst_app_sink_pull_sample( app_sink ), (GUnref) gst_sample_unref ); + GstSample* samplePtr = nullptr; + g_signal_emit_by_name( m_sink.get(), "try-pull-sample", &samplePtr, nullptr ); - if ( sample ) + if ( samplePtr ) { - auto buffer = gst_sample_get_buffer( sample.get() ); + tGstPtr sample( samplePtr, (GUnref) gst_sample_unref ); + auto buffer = gst_sample_get_buffer( sample.get() ); GstMapInfo mapped; diff --git a/audiostream/src/ni/media/audio/source/gstreamer_file_source.h b/audiostream/src/ni/media/audio/source/gstreamer_file_source.h index 1163d610..62f2253d 100644 --- a/audiostream/src/ni/media/audio/source/gstreamer_file_source.h +++ b/audiostream/src/ni/media/audio/source/gstreamer_file_source.h @@ -59,13 +59,13 @@ class gstreamer_file_source private: static void init_gstreamer(); static pcm::runtime_format create_runtime_format( GstStructure* caps_struct ); - static pcm::number_type gst_format_char_to_number_type( const gchar format ); + static pcm::number_type gst_format_char_to_number_type( const gchar format ); - void setup_source( const std::string& path, audio::ifstream_info::container_type container ); - GstElement* prepare_pipeline( const std::string& path ); - void preroll_pipeline(); - GstState wait_for_async_operation(); - void fill_format_info( GstStructure* caps_struct, audio::ifstream_info::container_type container ); + void setup_source( const std::string& path, audio::ifstream_info::container_type container ); + GstElement* prepare_pipeline( const std::string& path ); + void preroll_pipeline(); + GstState wait_for_async_operation(); + void fill_format_info( GstStructure* caps_struct, audio::ifstream_info::container_type container ); std::streamsize recursive_read( char* dst, std::streamsize numBytesRequested ); static void onPadAdded( GstElement* element, GstPad* pad, GstElement* sink ); @@ -74,7 +74,9 @@ class gstreamer_file_source using GUnref = void ( * )( gpointer ); template using tGstPtr = std::unique_ptr; + tGstPtr m_pipeline; + tGstPtr m_sink; using RingBuffer = detail::RingBuffer; std::unique_ptr m_ring_buffer; From 4d82bdc3057dbdc0498a8ded5ae3dd45aabc3066 Mon Sep 17 00:00:00 2001 From: Henry Hoegelow Date: Tue, 17 Apr 2018 08:46:39 +0200 Subject: [PATCH 27/28] fixup --- .../src/ni/media/audio/source/gstreamer_file_source.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/audiostream/src/ni/media/audio/source/gstreamer_file_source.cpp b/audiostream/src/ni/media/audio/source/gstreamer_file_source.cpp index 929a2e2e..d65a7893 100644 --- a/audiostream/src/ni/media/audio/source/gstreamer_file_source.cpp +++ b/audiostream/src/ni/media/audio/source/gstreamer_file_source.cpp @@ -245,7 +245,7 @@ void gstreamer_file_source::onPadAdded( GstElement* element, GstPad* pad, GstEle if ( gst_pad_is_linked( sinkpad.get() ) ) return; // already linked - tGstPtr caps( gst_pad_get_current_caps( pad ), gst_object_unref ); + std::unique_ptr caps(gst_pad_get_current_caps(pad), gst_caps_unref); auto s = gst_caps_get_structure( caps.get(), 0 ); auto name = gst_structure_get_name( s ); @@ -322,7 +322,7 @@ std::streamsize gstreamer_file_source::recursive_read( char* dst, std::streamsiz if ( numBytesRequested ) { GstSample* samplePtr = nullptr; - g_signal_emit_by_name( m_sink.get(), "try-pull-sample", &samplePtr, nullptr ); + g_signal_emit_by_name( m_sink.get(), "try-pull-sample", 0, &samplePtr, nullptr ); if ( samplePtr ) { From cf1e7a80852395dd6c006b29f6e87dc48f973431 Mon Sep 17 00:00:00 2001 From: Henry Hoegelow Date: Tue, 17 Apr 2018 08:48:32 +0200 Subject: [PATCH 28/28] removed GstApp dependency --- audiostream/CMakeLists.txt | 2 -- cmake/FindGStreamerApp.cmake | 23 ----------------------- 2 files changed, 25 deletions(-) delete mode 100644 cmake/FindGStreamerApp.cmake diff --git a/audiostream/CMakeLists.txt b/audiostream/CMakeLists.txt index b57c76f9..adf52b81 100644 --- a/audiostream/CMakeLists.txt +++ b/audiostream/CMakeLists.txt @@ -150,7 +150,6 @@ if( NIMEDIA_ENABLE_MP3_DECODING OR NIMEDIA_ENABLE_MP4_DECODING OR NIMEDIA_ENABLE find_package(Glib REQUIRED) find_package(GStreamer REQUIRED) - find_package(GStreamerApp REQUIRED) find_package(GObject REQUIRED) if ( NOT TARGET GSTREAMER::gstreamer ) @@ -163,7 +162,6 @@ if( NIMEDIA_ENABLE_MP3_DECODING OR NIMEDIA_ENABLE_MP4_DECODING OR NIMEDIA_ENABLE endif() list(APPEND codec_libraries GSTREAMER::gstreamer) - list(APPEND codec_libraries GSTREAMERAPP::gstreamerapp) list(APPEND codec_libraries glib-2.0) list(APPEND codec_libraries gobject-2.0) diff --git a/cmake/FindGStreamerApp.cmake b/cmake/FindGStreamerApp.cmake deleted file mode 100644 index ecb9d014..00000000 --- a/cmake/FindGStreamerApp.cmake +++ /dev/null @@ -1,23 +0,0 @@ -include(FindPackageHandleStandardArgs) - -find_package(PkgConfig REQUIRED) - -pkg_check_modules (GSTREAMERAPP REQUIRED gstreamer-app-1.0) -find_library(GSTREAMERAPP_LIBRARY NAMES gstapp-1.0) - -find_package_handle_standard_args(GSTREAMERAPP DEFAULT_MSG GSTREAMERAPP_INCLUDE_DIRS GSTREAMERAPP_LIBRARY) -mark_as_advanced(GSTREAMERAPP_INCLUDE_DIRS GSTREAMERAPP_LIBRARY) - - -if( GSTREAMERAPP_FOUND ) - if( NOT TARGET GSTREAMERAPP::gstreamerapp ) - add_library(GSTREAMERAPP::gstreamerapp SHARED IMPORTED) - set_target_properties(GSTREAMERAPP::gstreamerapp PROPERTIES - IMPORTED_LINK_INTERFACE_LANGUAGES "C" - IMPORTED_LOCATION ${GSTREAMERAPP_LIBRARY} - INTERFACE_INCLUDE_DIRECTORIES "${GSTREAMERAPP_INCLUDE_DIRS}" - INTERFACE_LINK_LIBRARIES "${GSTREAMERAPP_LIBRARY}" - ) - endif() -endif() -