Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature add linux gstreamer support #11

Open
wants to merge 31 commits into
base: master
Choose a base branch
from

Conversation

hhoegelo
Copy link

@hhoegelo hhoegelo commented Jan 5, 2018

If you like, you can pull my attempt of adding mp3, m4a, alac and wma support on linux platform by using gstreamer framework. I didn't manage to have all tests green, though. For example, the 'interlaced_by_67_frames'-tests are off by 1 sample, which I - so far - couldn't find a reason for.

Most likely other people won't notice my fork. I hope, if you pull it into the base fork, we have a higher chance that anyone can help fixing.

@marcrambo
Copy link
Collaborator

Hey Henry!

That's great! Thanks a lot for the contribution. I will look at it as soon as possible. In the mean time could you please update the travis-ci to retrieve the gstreamer package so that the linux build passes?

Thanks,
Marc

@@ -71,6 +71,7 @@ endif()

set( COMPILE_WITH_COREAUDIO DONT_COMPILE)
set( COMPILE_WITH_MEDIA_FOUNDATION DONT_COMPILE)
set( COMPILE_WITH_GSTREAMER DONT_COMPILE)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pedantic: align DONT_COMPILE with above statements

@@ -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 )
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pedantic: align with other entires



if( GSTREAMER_FOUND )
if( NOT TARGET GSTREAMER::gstreamer )
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pedantic: alignment



if( GSTREAMERAPP_FOUND )
if( NOT TARGET GSTREAMERAPP::gstreamerapp )
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pedantic: alignment

Copy link

@kevin-- kevin-- left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. I would just ask that you apply the .clang-format rules found in the root of the repo, along with the other formatting in CMake files


if ( numBytesRequested )
{
tGstPtr<GstElement> sink( gst_bin_get_by_name( GST_BIN( m_pipeline.get() ), "sink" ), gst_object_unref );
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be better to store the sink pointer somewhere instead of retrieving it all the time

{
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;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You probably want to use GstAudioInfo here and in the function above. it gives you a simple C struct with all these information you want to extract from the caps. Also check the GstAudioFormatInfo that is part of the GstAudioInfo.

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 );
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check return value


void gstreamer_file_source::preroll_pipeline()
{
gst_element_set_state( m_pipeline.get(), GST_STATE_PAUSED );
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check return value


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 );
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

best to check return values, things can fail

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 );
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're leaking the caps and sinkpad here.

gstreamer_file_source::~gstreamer_file_source()
{
gst_element_set_state( m_pipeline.get(), GST_STATE_NULL );
wait_for_async_operation();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Downwards state change is always sync

@sdroege
Copy link

sdroege commented Apr 15, 2018

@hhoegelo Can you explain the easiest way to reproduce the 1-sample-off problem and what information you gathered so far?

If I understand correctly, this happens on specific files (WMA? Or everything? All WMA files or a specific one?) and if you decode from the beginning vs. you seek to a position, you would end up one sample off? Is it always absolutely one sample, or would it sometimes be more or less, or would every seek make it one more sample off?

Re main loop: this is not required, correct. You have multiple options, but first of all: why do you care? You want to block until error or reading samples is possible? And then blocking-read the samples?

  1. Blocking retrieval of messages from the GStreamer bus to know when pre-rolling has happened. This does not help you much here because you also block on reading samples and you want to continue getting bus message to know when an error happened
  2. Integration of the GStreamer bus into whatever event loop you're using here. Details depend on how that event loop work
  3. Synchronously getting messages from the bus and handling them from there. You could use that to get notified about errors once pre-rolling has happened in 1) for example. Note that you must call things like setting pipeline states from a different thread then the sync message handler.

From what I saw in the code so far I would suggest a combination of 1) and 3). You block until error or the sink is ready (you received the ASYNC_DONE message on the bus), by calling gst_bus_timed_pop_filtered(bus, whatever_timeout_you_want_in_ns, GST_MESSAGE_ERROR | GST_MESSAGE_EOS | GST_MESSAGE_ASYNC_DONE). In addition you add a sync bus handler (gst_bus_set_sync_handler) that ignores all messages until ASYNC_DONE is received. Once ASYNC_DONE is returned from gst_bus_timed_pop_filtered you are pre-rolled and can start. If instead ERROR or EOS is received you know that nothing will ever work here. You then start pulling from the appsink, but if the sync bus handler notifies you about ERROR, you asynchronously report that somehow and shut down the pipeline (gst_element_call_async might be useful for the latter).

@sdroege
Copy link

sdroege commented Apr 15, 2018

Sorry if some comments appeared twice, GitHub had some kind of hickup while I was doing the review and returned a few server errors

@kevin--
Copy link

kevin-- commented Apr 15, 2018

thanks for the thorough look @sdroege really appreciate seeing this level of collab

@hhoegelo hhoegelo force-pushed the feature-add-linux-gstreamer-support branch from 1c58318 to 54ea50d Compare April 16, 2018 20:40
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" );
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line randomly throws. There seems to be a race condition when setting / checking the state

@marcrambo
Copy link
Collaborator

@hhoegelo can you please pull in my branch https://github.com/marcrambo/ni-media/tree/feature-add-linux-gstreamer-support? I reenabled the tests and commited a fix for all the tests failing due to an exception thrown while retrieving the track length. Also I improved the logging so we have a better overview of which tests are still failing.

@marcrambo
Copy link
Collaborator

@sdroege Thanks a lot for your help which is really greatly appreciated! I was wondering if it was possible to run gstreamer in "synchronous" mode i.e. without having to do "preroll pipeline" and so on. Calls to gestream_source::read don't need to be realtime capable i.e. non blocking. Actually all the other backend sources are synchronous (except itunes_source which also does some sort of asynchronous prerolling / caching as in gstreamer )

@sdroege
Copy link

sdroege commented Apr 17, 2018

@sdroege Thanks a lot for your help which is really greatly appreciated! I was wondering if it was possible to run gstreamer in "synchronous" mode i.e. without having to do "preroll pipeline" and so on. Calls to gestream_source::read don't need to be realtime capable i.e. non blocking. Actually all the other backend sources are synchronous (except itunes_source which also does some sort of asynchronous prerolling / caching as in gstreamer )

@marcrambo That's exactly how it would behave with my suggestion (1 + 3) from comment #11 (comment)

While GStreamer is always async to some degree, there is API that makes it easy enough to build a sync API on top

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

9 participants