diff --git a/data/meson.build b/data/meson.build new file mode 100644 index 0000000000..4a2d306ef3 --- /dev/null +++ b/data/meson.build @@ -0,0 +1,22 @@ +encoding_targetsdir = join_paths(get_option('datadir'), + 'gstreamer-' + api_version, 'encoding-profiles') + +encoding_targets = [ + ['file-extension', ['targets/file-extension/ogv.gep', + 'targets/file-extension/oga.gep', + 'targets/file-extension/mkv.gep', + 'targets/file-extension/mp3.gep', + 'targets/file-extension/webm.gep', + 'targets/file-extension/flv.gep', + 'targets/file-extension/mp4.gep', + 'targets/file-extension/avi.gep',], + ], + ['online-services', ['targets/online-service/youtube.gep',]], + ['device', ['targets/device/dvd.gep',]], +] + +foreach path_targets : encoding_targets + dir = join_paths(encoding_targetsdir, path_targets.get(0)) + etargets = path_targets.get(1) + install_data(sources: etargets, install_dir: dir) +endforeach diff --git a/data/targets/device/dvd.gep b/data/targets/device/dvd.gep new file mode 100644 index 0000000000..d376047211 --- /dev/null +++ b/data/targets/device/dvd.gep @@ -0,0 +1,24 @@ +[GStreamer Encoding Target] +name=dvd +category=device +description=Encoding target suitable for DVDs + +[profile-dvd] +name=dvd +type=container +description[c]=This is an encoding profile usable for DVDs +format=video/mpeg, mpegversion=(int)2, systemstream=(boolean)true + +[streamprofile-dvd-0] +parent=dvd +type=video +format=video/mpeg, mpegversion=(int)2, systemstream=(boolean)false +presence=0 +pass=0 +variableframerate=false + +[streamprofile-dvd-1] +parent=dvd +type=audio +format=audio/mpeg, mpegversion=(int)1, layer=(int)2 +presence=0 diff --git a/data/targets/file-extension/avi.gep b/data/targets/file-extension/avi.gep new file mode 100644 index 0000000000..7131ba4142 --- /dev/null +++ b/data/targets/file-extension/avi.gep @@ -0,0 +1,21 @@ +[GStreamer Encoding Target] +name=avi +category=file-extension +description=Default target for files with a .avi extension + +[profile-default] +name=default +type=container +description=Default profile for files with a .avi extension. +format=video/x-msvideo + +[streamprofile-default-0] +parent=default +type=audio +format=audio/mpeg, mpegversion=(int)1, mpegaudioversion=(int)1, layer=(int)3 + +[streamprofile-default-1] +parent=default +type=video +format=video/mpeg, mpegversion=(int)4 + diff --git a/data/targets/file-extension/flv.gep b/data/targets/file-extension/flv.gep new file mode 100644 index 0000000000..59380ce77f --- /dev/null +++ b/data/targets/file-extension/flv.gep @@ -0,0 +1,32 @@ +[GStreamer Encoding Target] +name=flv +category=file-extension +description=Default target for files with a .flv extension + +[profile-default] +name=default +type=container +description=Default profile for files with a .flv extension. +format=video/x-flv + +[streamprofile-default-0] +parent=default +type=audio +format=audio/mpeg,mpegversion=4 + +[streamprofile-default-1] +parent=default +type=audio +format=audio/mpeg, mpegversion=(int)1, mpegaudioversion=(int)1, layer=(int)3 + +[streamprofile-default-2] +parent=default +type=video +format=video/x-h264 +preset=Profile YouTube +pass=0 + +[streamprofile-default-3] +parent=default +type=video +format=video/x-h264 diff --git a/data/targets/file-extension/mkv.gep b/data/targets/file-extension/mkv.gep new file mode 100644 index 0000000000..1f2c562d40 --- /dev/null +++ b/data/targets/file-extension/mkv.gep @@ -0,0 +1,28 @@ +[GStreamer Encoding Target] +name=mkv;matroska; +category=file-extension +description=Default target for files with a .mkv extension + +[profile-default] +name=default +description=Default profile for files with a .mkv extension. Audio stream can be either opus (default) or vorbis depending on what is available on the system. Video stream will be either in vp8 (default) or vp9. +type=container +format=video/x-matroska + +[streamprofile-default-0] +parent=default +type=audio +format=audio/x-vorbis;audio/x-opus + +[streamprofile-default-1] +parent=default +type=video +format=video/x-h264 +preset=Quality Normal +pass=0 + +[streamprofile-default-2] +parent=default +type=video +format=video/x-h264 +pass=0 diff --git a/data/targets/file-extension/mp3.gep b/data/targets/file-extension/mp3.gep new file mode 100644 index 0000000000..74b191f48c --- /dev/null +++ b/data/targets/file-extension/mp3.gep @@ -0,0 +1,15 @@ +[GStreamer Encoding Target] +name=mp3 +category=file-extension +description=Default target for files with a .mp3 extension + +[profile-default] +name=default +type=container +description=Default profile for files with a .mp3 extension. +format=application/x-id3 + +[streamprofile-default-0] +parent=default +type=audio +format=audio/mpeg, mpegversion=(int)1, mpegaudioversion=(int)1, layer=(int)3 diff --git a/data/targets/file-extension/mp4.gep b/data/targets/file-extension/mp4.gep new file mode 100644 index 0000000000..1ea3cf6205 --- /dev/null +++ b/data/targets/file-extension/mp4.gep @@ -0,0 +1,34 @@ +[GStreamer Encoding Target] +name=mp4;mov +category=file-extension +description=Default target for files with a .mp4 and .mov extension + +[profile-default] +name=default +type=container +description=Default profile for files with a .mp4 extension. Suitable for uploading to youtube. +format=video/quicktime +preset=Profile YouTube + +[streamprofile-default-0] +parent=default +type=audio +format=audio/mpeg,mpegversion=4,base-profile=lc,rate={48000,96000},channels=2;audio/mpeg,mpegversion=4,base-profile=lc,rate={48000,96000} +restriction=audio/x-raw,channels=6,channel-mask=0x3f;audio/x-raw,channels=2 + +[streamprofile-default-1] +parent=default +type=video +format=video/x-h264 +preset=Profile YouTube +pass=0 + +[streamprofile-default-2] +parent=default +type=audio +format=audio/mpeg,mpegversion=4 + +[streamprofile-default-3] +parent=default +type=video +format=video/x-h264 diff --git a/data/targets/file-extension/oga.gep b/data/targets/file-extension/oga.gep new file mode 100644 index 0000000000..0f80a585a5 --- /dev/null +++ b/data/targets/file-extension/oga.gep @@ -0,0 +1,15 @@ +[GStreamer Encoding Target] +name=oga +category=file-extension +description=Default target for files with a .ogg and friends extension + +[profile-default] +name=default +description=Default target for files with a .ogg and friends extension +type=container +format=audio/ogg + +[streamprofile-default-0] +parent=default +type=audio +format=audio/x-vorbis;audio/x-opus diff --git a/data/targets/file-extension/ogv.gep b/data/targets/file-extension/ogv.gep new file mode 100644 index 0000000000..bf3cf77a28 --- /dev/null +++ b/data/targets/file-extension/ogv.gep @@ -0,0 +1,25 @@ +[GStreamer Encoding Target] +name=ogv;ogg +category=file-extension +description=Default target for files with a .ogg and friends extension + +[profile-default] +name=default +description=Default target for files with a .ogg and friends extension +type=container +format=application/ogg + +[streamprofile-default-0] +parent=default +type=audio +format=audio/x-vorbis;audio/x-opus + +[streamprofile-default-1] +parent=default +type=video +format=video/x-theora + +[streamprofile-default-2] +parent=default +type=video +format=video/x-vp8 diff --git a/data/targets/file-extension/webm.gep b/data/targets/file-extension/webm.gep new file mode 100644 index 0000000000..526cf47845 --- /dev/null +++ b/data/targets/file-extension/webm.gep @@ -0,0 +1,21 @@ +[GStreamer Encoding Target] +name=webm +category=file-extension +description=Default target for files with a .webm extension + +[profile-default] +name=default +description=Default profile for files with a .webm extension. Audio stream can be either vorbis (default) or opus depending on what is available on the system. Video stream will be either in vp8 (default) or vp9. +type=container +format=video/webm + +[streamprofile-default-0] +parent=default +type=audio +format=audio/x-vorbis;audio/x-opus + +[streamprofile-default-1] +parent=default +type=video +format=video/x-vp8;video/x-vp9 +pass=0 diff --git a/data/targets/online-service/youtube.gep b/data/targets/online-service/youtube.gep new file mode 100644 index 0000000000..2d847ee540 --- /dev/null +++ b/data/targets/online-service/youtube.gep @@ -0,0 +1,24 @@ +[GStreamer Encoding Target] +name=youtube;yt +category=online-service +description=Recommended encoding settings for YouTube + +[profile-default] +name=default +type=container +description=Youtube recommended profile with automatic audio setting +format=video/quicktime +preset=Profile YouTube + +[streamprofile-default-0] +parent=default +type=audio +format=audio/mpeg,mpegversion=4,base-profile=lc +restriction=audio/x-raw,channels=6,rate={48000,96000};audio/x-raw,channels=2,rate={48000,96000} + +[streamprofile-default-1] +parent=default +type=video +format=video/x-h264,profile=high +preset=Profile YouTube +pass=0 diff --git a/docs/libs/gst-plugins-bad-libs-sections.txt b/docs/libs/gst-plugins-bad-libs-sections.txt deleted file mode 100644 index cc229a96ae..0000000000 --- a/docs/libs/gst-plugins-bad-libs-sections.txt +++ /dev/null @@ -1,1140 +0,0 @@ -# codecparsers -
-gsth264parser -h264parser -gst/codecparsers/gsth264parser.h -GST_H264_MAX_SPS_COUNT -GST_H264_MAX_PPS_COUNT -GST_H264_IS_P_SLICE -GST_H264_IS_B_SLICE -GST_H264_IS_I_SLICE -GST_H264_IS_SP_SLICE -GST_H264_IS_SI_SLICE -GstH264NalUnitType -GstH264ParserResult -GstH264SEIPayloadType -GstH264SEIPicStructType -GstH264SliceType -GstH264NalParser -GstH264NalUnit -GstH264SPS -GstH264PPS -GstH264HRDParams -GstH264VUIParams -GstH264DecRefPicMarking -GstH264RefPicMarking -GstH264PredWeightTable -GstH264SliceHdr -GstH264ClockTimestamp -GstH264PicTiming -GstH264BufferingPeriod -GstH264SEIMessage -gst_h264_parser_identify_nalu -gst_h264_parser_identify_nalu_avc -gst_h264_parser_parse_nal -gst_h264_parser_parse_slice_hdr -gst_h264_parser_parse_sps -gst_h264_parser_parse_pps -gst_h264_parser_parse_sei -gst_h264_nal_parser_new -gst_h264_nal_parser_free -gst_h264_parse_sps -gst_h264_parse_pps -gst_h264_pps_clear -gst_h264_quant_matrix_8x8_get_zigzag_from_raster -gst_h264_quant_matrix_8x8_get_raster_from_zigzag -gst_h264_quant_matrix_4x4_get_zigzag_from_raster -gst_h264_quant_matrix_4x4_get_raster_from_zigzag -gst_h264_video_calculate_framerate - - -
- -
-gstjpegparser -jpegparser -gst/codecparsers/gstjpegparser.h -GST_JPEG_MAX_FRAME_COMPONENTS -GST_JPEG_MAX_SCAN_COMPONENTS -GST_JPEG_MAX_QUANT_ELEMENTS -GstJpegMarker; -GST_JPEG_MARKER_SOF_MIN -GST_JPEG_MARKER_SOF_MAX -GST_JPEG_MARKER_APP_MIN -GST_JPEG_MARKER_APP_MAX -GST_JPEG_MARKER_RST_MIN -GST_JPEG_MARKER_RST_MAX -GstJpegEntropyCodingMode -GstJpegProfile -GstJpegSegment -gst_jpeg_parse -GstJpegFrameHdr -GstJpegFrameComponent -gst_jpeg_segment_parse_frame_header -GstJpegScanHdr -GstJpegScanComponent -gst_jpeg_segment_parse_scan_header -GstJpegHuffmanTables -GstJpegHuffmanTable -gst_jpeg_segment_parse_huffman_table -GstJpegQuantTable -gst_jpeg_segment_parse_quantization_table -gst_jpeg_segment_parse_restart_interval -gst_jpeg_get_default_quantization_tables -gst_jpeg_get_default_huffman_tables - - -
- -
-gstvc1parser -vc1parser -gst/codecparsers/gstvc1parser.h -MAX_HRD_NUM_LEAKY_BUCKETS -GST_VC1_BFRACTION_BASIS -GstVC1StartCode -GstVC1Profile -GstVC1ParserResult -GstVC1PictureType -GstVC1Level -GstVC1QuantizerSpec -GstVC1DQProfile -GstVC1Condover -GstVC1MvMode -GstVC1SeqHdr -GstVC1AdvancedSeqHdr -GstVC1SeqLayer -GstVC1SeqStructA -GstVC1SeqStructB -GstVC1SeqStructC -GstVC1HrdParam -GstVC1EntryPointHdr -GstVC1FrameHdr -GstVC1PicAdvanced -GstVC1PicSimpleMain -GstVC1Picture -GstVC1VopDquant -GstVC1BDU -gst_vc1_identify_next_bdu -gst_vc1_parse_sequence_header -gst_vc1_parse_sequence_layer -gst_vc1_parse_sequence_header_struct_a -gst_vc1_parse_sequence_header_struct_b -gst_vc1_parse_sequence_header_struct_c -gst_vc1_parse_entry_point_header -gst_vc1_parse_frame_header -gst_vc1_bitplanes_new -gst_vc1_bitplanes_free -gst_vc1_bitplanes_free_1 -gst_vc1_bitplanes_ensure_size - - -
- -
-gstmpegvideometa -gst/codecparsers/gstmpegvideometa.h -GST_MPEG_VIDEO_META_API_TYPE -GST_MPEG_VIDEO_META_INFO -GstMpegVideoMeta -gst_buffer_add_mpeg_video_meta -gst_buffer_get_mpeg_video_meta -gst_mpeg_video_meta_get_info - -gst_mpeg_video_meta_api_get_type -
- - -
-gstmpegvideoparser -mpegvideoparser -gst/codecparsers/gstmpegvideoparser.h -GstMpegVideoPacketTypeCode -GstMpegVideoPacketExtensionCode -GstMpegVideoLevel -GstMpegVideoProfile -GstMpegVideoPictureType -GstMpegVideoPictureStructure -GstMpegVideoSequenceHdr -GstMpegVideoSequenceExt -GstMpegVideoPictureHdr -GstMpegVideoGop -GstMpegVideoPictureExt -GstMpegVideoQuantMatrixExt -GstMpegVideoTypeOffsetSize -gst_mpeg_video_parse -gst_mpeg_video_packet_parse_sequence_header -gst_mpeg_video_packet_parse_sequence_extension -gst_mpeg_video_packet_parse_sequence_display_extension -gst_mpeg_video_packet_parse_sequence_scalable_extension -gst_mpeg_video_packet_parse_picture_header -gst_mpeg_video_packet_parse_picture_extension -gst_mpeg_video_packet_parse_gop -gst_mpeg_video_packet_parse_slice_header -gst_mpeg_video_packet_parse_quant_matrix_extension -gst_mpeg_video_finalise_mpeg2_sequence_header -gst_mpeg_video_quant_matrix_get_raster_from_zigzag -gst_mpeg_video_quant_matrix_get_zigzag_from_raster - - -
- -
-gstmpeg4parser -mpeg4parser -gst/codecparsers/gstmpeg4parser.h -GstMpeg4StartCode -GstMpeg4VisualObjectType -GstMpeg4AspectRatioInfo -GstMpeg4ParseResult -GstMpeg4VideoObjectCodingType -GstMpeg4ChromaFormat -GstMpeg4VideoObjectLayerShape -GstMpeg4SpriteEnable -GstMpeg4Profile -GstMpeg4Level -GstMpeg4VisualObjectSequence -GstMpeg4VisualObject -GstMpeg4VideoSignalType -GstMpeg4VideoPlaneShortHdr -GstMpeg4VideoObjectLayer -GstMpeg4SpriteTrajectory -GstMpeg4GroupOfVOP -GstMpeg4VideoObjectPlane -GstMpeg4Packet -GstMpeg4VideoPacketHdr -gst_mpeg4_parse -gst_mpeg4_parse_video_object_plane -gst_mpeg4_parse_group_of_vop -gst_mpeg4_parse_video_object_layer -gst_mpeg4_parse_visual_object -gst_mpeg4_parse_visual_object_sequence -gst_mpeg4_parse_video_packet_header - - -
- -
-gstmpegts - -gst_mpegts_initialize -
- -
-gstmpegtsdescriptor - -GstMpegtsDescriptor -GstMpegtsDescriptorType -GstMpegtsMiscDescriptorType -gst_mpegts_find_descriptor -gst_mpegts_parse_descriptors -gst_mpegts_descriptor_from_custom - -gst_mpegts_descriptor_from_registration - -GstMpegtsISO639LanguageDescriptor -GstMpegtsIso639AudioType -gst_mpegts_descriptor_parse_iso_639_language -gst_mpegts_descriptor_parse_iso_639_language_idx -gst_mpegts_descriptor_parse_iso_639_language_nb -gst_mpegts_iso_639_language_descriptor_free - -GstMpegtsLogicalChannel -GstMpegtsLogicalChannelDescriptor -gst_mpegts_descriptor_parse_logical_channel - -GST_TYPE_MPEGTS_DVB_CODE_RATE -GST_TYPE_MPEGTS_CABLE_OUTER_FEC_SCHEME -GST_TYPE_MPEGTS_MODULATION_TYPE -GST_TYPE_MPEGTS_SATELLITE_POLARIZATION_TYPE -GST_TYPE_MPEGTS_SATELLITE_ROLLOFF -GST_TYPE_MPEGTS_ISO_639_LANGUAGE -GST_TYPE_MPEGTS_DESCRIPTOR -GST_TYPE_MPEGTS_DVB_SERVICE_TYPE -GST_TYPE_MPEGTS_DESCRIPTOR_TYPE -GST_TYPE_MPEGTS_ISO639_AUDIO_TYPE -GST_TYPE_MPEGTS_ATSC_DESCRIPTOR_TYPE -GST_TYPE_MPEGTS_DVB_DESCRIPTOR_TYPE -GST_TYPE_MPEGTS_ISDB_DESCRIPTOR_TYPE -GST_TYPE_MPEGTS_MISC_DESCRIPTOR_TYPE -gst_mpegts_descriptor_get_type -gst_mpegts_iso_639_language_get_type -gst_mpegts_cable_outer_fec_scheme_get_type -gst_mpegts_modulation_type_get_type -gst_mpegts_satellite_polarization_type_get_type -gst_mpegts_satellite_rolloff_get_type -gst_mpegts_dvb_code_rate_get_type -gst_mpegts_descriptor_type_get_type -gst_mpegts_atsc_descriptor_type_get_type -gst_mpegts_dvb_descriptor_type_get_type -gst_mpegts_isdb_descriptor_type_get_type -gst_mpegts_misc_descriptor_type_get_type -gst_mpegts_iso639_audio_type_get_type -gst_mpegts_dvb_service_type_get_type -
- -
-gst-atsc-descriptor -GstMpegtsATSCDescriptorType - -GST_TYPE_MPEGTS_ATSC_DESCRIPTOR_TYPE -gst_mpegts_atsc_descriptor_type_get_type -
- -
-gst-dvb-descriptor -GstMpegtsDVBDescriptorType -GstMpegtsDVBExtendedDescriptorType - -GstMpegtsContent -gst_mpegts_descriptor_parse_dvb_content - -GstMpegtsComponentDescriptor -gst_mpegts_dvb_component_descriptor_free -gst_mpegts_descriptor_parse_dvb_component - -GstMpegtsExtendedEventItem -GstMpegtsExtendedEventDescriptor -gst_mpegts_extended_event_descriptor_free -gst_mpegts_descriptor_parse_dvb_extended_event - -GstMpegtsSatelliteDeliverySystemDescriptor -GstMpegtsDVBCodeRate -GstMpegtsModulationType -GstMpegtsSatellitePolarizationType -GstMpegtsSatelliteRolloff -gst_mpegts_descriptor_parse_satellite_delivery_system - -GstMpegtsCableDeliverySystemDescriptor -GstMpegtsCableOuterFECScheme -gst_mpegts_descriptor_parse_cable_delivery_system - -GstMpegtsTerrestrialDeliverySystemDescriptor -GstMpegtsTerrestrialTransmissionMode -GstMpegtsTerrestrialGuardInterval -GstMpegtsTerrestrialHierarchy -GstMpegtsModulationType -GstMpegtsDVBCodeRate -gst_mpegts_descriptor_parse_terrestrial_delivery_system - -GstMpegtsT2DeliverySystemCellExtension -GstMpegtsT2DeliverySystemCell -GstMpegtsT2DeliverySystemDescriptor -gst_mpegts_t2_delivery_system_descriptor_free -gst_mpegts_descriptor_parse_dvb_t2_delivery_system - -gst_mpegts_descriptor_parse_dvb_short_event - -gst_mpegts_descriptor_parse_dvb_network_name -gst_mpegts_descriptor_from_dvb_network_name - -GstMpegtsDVBServiceType -gst_mpegts_descriptor_parse_dvb_service -gst_mpegts_descriptor_from_dvb_service - -GstMpegtsDVBTeletextType -gst_mpegts_descriptor_parse_dvb_teletext_idx -gst_mpegts_descriptor_parse_dvb_teletext_nb - -gst_mpegts_descriptor_parse_dvb_subtitling_idx -gst_mpegts_descriptor_parse_dvb_subtitling_nb -gst_mpegts_descriptor_from_dvb_subtitling - -GstMpegtsDVBLinkageType -GstMpegtsDVBLinkageHandOverType -GstMpegtsDVBLinkageMobileHandOver -GstMpegtsDVBLinkageEvent -GstMpegtsDVBLinkageExtendedEvent -GstMpegtsDVBLinkageDescriptor -gst_mpegts_dvb_linkage_descriptor_free -gst_mpegts_dvb_linkage_descriptor_get_mobile_hand_over -gst_mpegts_dvb_linkage_descriptor_get_event -gst_mpegts_dvb_linkage_descriptor_get_extended_event -gst_mpegts_descriptor_parse_dvb_linkage - -gst_mpegts_descriptor_parse_dvb_private_data_specifier - -gst_mpegts_descriptor_parse_dvb_frequency_list - -GstMpegtsDataBroadcastDescriptor -gst_mpegts_dvb_data_broadcast_descriptor_free -gst_mpegts_descriptor_parse_dvb_data_broadcast - -GstMpegtsDVBScramblingModeType -gst_mpegts_descriptor_parse_dvb_scrambling - -gst_mpegts_descriptor_parse_dvb_data_broadcast_id - -GstMpegtsDVBParentalRatingItem -gst_mpegts_descriptor_parse_dvb_parental_rating - -gst_mpegts_descriptor_parse_dvb_stream_identifier - -gst_mpegts_descriptor_parse_dvb_ca_identifier - -GstMpegtsDVBServiceListItem -gst_mpegts_descriptor_parse_dvb_service_list - -gst_mpegts_descriptor_parse_dvb_stuffing - -gst_mpegts_descriptor_parse_dvb_bouquet_name - -GstMpegtsDvbMultilingualNetworkNameItem -gst_mpegts_descriptor_parse_dvb_multilingual_network_name - -GstMpegtsDvbMultilingualBouquetNameItem -gst_mpegts_descriptor_parse_dvb_multilingual_bouquet_name - -GstMpegtsDvbMultilingualServiceNameItem -gst_mpegts_descriptor_parse_dvb_multilingual_service_name - -GstMpegtsDvbMultilingualComponentItem -gst_mpegts_descriptor_parse_dvb_multilingual_component - -GST_TYPE_MPEGTS_DVB_CODE_RATE -GST_TYPE_MPEGTS_COMPONENT_DESCRIPTOR -GST_TYPE_MPEGTS_DVB_DATA_BROADCAST_DESCRIPTOR -GST_TYPE_MPEGTS_DVB_LINKAGE_DESCRIPTOR -GST_TYPE_MPEGTS_EXTENDED_EVENT_DESCRIPTOR -GST_TYPE_MPEGTS_T2_DELIVERY_SYSTEM_DESCRIPTOR -gst_mpegts_dvb_code_rate_get_type -gst_mpegts_component_descriptor_get_type -gst_mpegts_dvb_data_broadcast_descriptor_get_type -gst_mpegts_dvb_linkage_descriptor_get_type -gst_mpegts_extended_event_descriptor_get_type -gst_mpegts_t2_delivery_system_descriptor_get_type -
- -
-gst-isdb-descriptor -GstMpegtsISDBDescriptorType - -GST_TYPE_MPEGTS_ISDB_DESCRIPTOR_TYPE -gst_mpegts_isdb_descriptor_type_get_type -
- -
-gstmpegtssection - -GST_MPEGTS_SECTION_TYPE -GstMpegtsSection -GstMpegtsSectionTableID -GstMpegtsSectionType -gst_message_new_mpegts_section -gst_message_parse_mpegts_section -gst_mpegts_section_send_event -gst_event_parse_mpegts_section -gst_mpegts_section_packetize -gst_mpegts_section_new -gst_mpegts_section_ref -gst_mpegts_section_unref - -GstMpegtsPatProgram -gst_mpegts_section_get_pat -gst_mpegts_pat_new -gst_mpegts_pat_program_new -gst_mpegts_section_from_pat - -GstMpegtsPMT -GstMpegtsPMTStream -GstMpegtsStreamType -gst_mpegts_section_get_pmt -gst_mpegts_pmt_new -gst_mpegts_pmt_stream_new -gst_mpegts_section_from_pmt - -gst_mpegts_section_get_tsdt - -gst_mpegts_section_get_cat - -GST_TYPE_MPEGTS_SECTION_TABLE_ID -GST_TYPE_MPEGTS_SECTION_TYPE -GST_TYPE_MPEGTS_SECTION_DVB_TABLE_ID -GST_MPEGTS_SECTION -GST_TYPE_MPEGTS_STREAM_TYPE -GST_TYPE_MPEGTS_PMT -GST_TYPE_MPEGTS_PMT_STREAM -GST_TYPE_MPEGTS_SECTION -gst_mpegts_section_table_id_get_type -gst_mpegts_section_type_get_type -gst_mpegts_pmt_get_type -gst_mpegts_pmt_stream_get_type -gst_mpegts_section_get_type -gst_mpegts_stream_type_get_type -
- -
-gst-atsc-section -GstMpegtsSectionATSCTableID -GstMpegtsAtscVCTSource -GstMpegtsAtscVCT -gst_mpegts_section_get_atsc_tvct -gst_mpegts_section_get_atsc_cvct -GstMpegtsAtscMGTTableType -GstMpegtsAtscMGTTable -GstMpegtsAtscMGT -gst_mpegts_section_get_atsc_mgt -gst_mpegts_atsc_string_segment_get_string -GstMpegtsAtscMultString -GstMpegtsAtscEITEvent -GstMpegtsAtscEIT -gst_mpegts_section_get_atsc_eit -GstMpegtsAtscETT -gst_mpegts_section_get_atsc_ett -GstMpegtsAtscSTT -gst_mpegts_section_get_atsc_stt -gst_mpegts_atsc_stt_get_datetime_utc - -GST_TYPE_MPEGTS_ATSC_EIT -GST_TYPE_MPEGTS_ATSC_EIT_EVENT -GST_TYPE_MPEGTS_ATSC_ETT -GST_TYPE_MPEGTS_ATSC_MGT -GST_TYPE_MPEGTS_ATSC_MGT_TABLE -GST_TYPE_MPEGTS_ATSC_MULT_STRING -GST_TYPE_MPEGTS_ATSC_STRING_SEGMENT -GST_TYPE_MPEGTS_ATSC_STT -GST_TYPE_MPEGTS_ATSC_VCT -GST_TYPE_MPEGTS_ATSC_VCT_SOURCE -GstMpegtsAtscStringSegment -gst_mpegts_atsc_eit_event_get_type -gst_mpegts_atsc_eit_get_type -gst_mpegts_atsc_ett_get_type -gst_mpegts_atsc_mgt_get_type -gst_mpegts_atsc_mgt_table_get_type -gst_mpegts_atsc_mult_string_get_type -gst_mpegts_atsc_string_segment_get_type -gst_mpegts_atsc_stt_get_type -gst_mpegts_atsc_vct_get_type -gst_mpegts_atsc_vct_source_get_type -
- -
-gst-dvb-section -GstMpegtsSectionDVBTableID - -GstMpegtsNIT -GstMpegtsNITStream -gst_mpegts_section_get_nit -gst_mpegts_nit_new -gst_mpegts_nit_stream_new -gst_mpegts_section_from_nit - -GstMpegtsBAT -GstMpegtsBATStream -gst_mpegts_section_get_bat - -GstMpegtsSDT -GstMpegtsSDTService -gst_mpegts_section_get_sdt -gst_mpegts_sdt_new -gst_mpegts_sdt_service_new -gst_mpegts_section_from_sdt - -GstMpegtsEIT -GstMpegtsEITEvent -GstMpegtsRunningStatus -gst_mpegts_section_get_eit - -gst_mpegts_section_get_tdt - -GstMpegtsTOT -gst_mpegts_section_get_tot - -GST_TYPE_MPEGTS_BAT -GST_TYPE_MPEGTS_BAT_STREAM -GST_TYPE_MPEGTS_EIT -GST_TYPE_MPEGTS_EIT_EVENT -GST_TYPE_MPEGTS_NIT -GST_TYPE_MPEGTS_NIT_STREAM -GST_TYPE_MPEGTS_SDT -GST_TYPE_MPEGTS_SDT_SERVICE -GST_TYPE_MPEGTS_TOT -GST_TYPE_MPEGTS_SECTION_DVB_TABLE_ID -GST_TYPE_MPEGTS_RUNNING_STATUS -gst_mpegts_running_status_get_type -gst_mpegts_section_dvb_table_id_get_type -gst_mpegts_bat_stream_get_type -gst_mpegts_bat_get_type -gst_mpegts_eit_event_get_type -gst_mpegts_eit_get_type -gst_mpegts_nit_get_type -gst_mpegts_nit_stream_get_type -gst_mpegts_sdt_get_type -gst_mpegts_sdt_service_get_type -gst_mpegts_tot_get_type -
- - -
-gstphotography -GstPhotography -GstPhotography -GstPhotographyNoiseReduction -GstPhotographyWhiteBalanceMode -GstPhotographyColorToneMode -GstPhotographySceneMode -GstPhotographyFlashMode -GstPhotographyFlickerReductionMode -GstPhotographyFocusMode -GstPhotographyFocusStatus -GstPhotographyCaps -GstPhotographyShakeRisk -GstPhotographySettings -GstPhotographyCapturePrepared -GST_PHOTOGRAPHY_AUTOFOCUS_DONE -GST_PHOTOGRAPHY_SHAKE_RISK -GST_PHOTOGRAPHY_PROP_WB_MODE -GST_PHOTOGRAPHY_PROP_COLOUR_TONE -GST_PHOTOGRAPHY_PROP_SCENE_MODE -GST_PHOTOGRAPHY_PROP_FLASH_MODE -GST_PHOTOGRAPHY_PROP_NOISE_REDUCTION -GST_PHOTOGRAPHY_PROP_FOCUS_STATUS -GST_PHOTOGRAPHY_PROP_CAPABILITIES -GST_PHOTOGRAPHY_PROP_SHAKE_RISK -GST_PHOTOGRAPHY_PROP_EV_COMP -GST_PHOTOGRAPHY_PROP_ISO_SPEED -GST_PHOTOGRAPHY_PROP_APERTURE -GST_PHOTOGRAPHY_PROP_EXPOSURE -GST_PHOTOGRAPHY_PROP_IMAGE_CAPTURE_SUPPORTED_CAPS -GST_PHOTOGRAPHY_PROP_IMAGE_PREVIEW_SUPPORTED_CAPS -GST_PHOTOGRAPHY_PROP_FLICKER_MODE -GST_PHOTOGRAPHY_PROP_FOCUS_MODE -GST_PHOTOGRAPHY_PROP_ZOOM -gst_photography_get_ev_compensation -gst_photography_get_iso_speed -gst_photography_get_aperture -gst_photography_get_exposure -gst_photography_get_white_balance_mode -gst_photography_get_color_tone_mode -gst_photography_get_scene_mode -gst_photography_get_flash_mode -gst_photography_get_flicker_mode -gst_photography_get_focus_mode -gst_photography_get_noise_reduction -gst_photography_get_zoom -gst_photography_set_ev_compensation -gst_photography_set_iso_speed -gst_photography_set_aperture -gst_photography_set_exposure -gst_photography_set_white_balance_mode -gst_photography_set_color_tone_mode -gst_photography_set_scene_mode -gst_photography_set_flash_mode -gst_photography_set_flicker_mode -gst_photography_set_focus_mode -gst_photography_set_noise_reduction -gst_photography_set_zoom -gst_photography_get_capabilities -gst_photography_prepare_for_capture -gst_photography_set_autofocus -gst_photography_set_config -gst_photography_get_config - -GST_PHOTOGRAPHY -GST_IS_PHOTOGRAPHY -GST_TYPE_PHOTOGRAPHY -gst_photography_get_type -GST_PHOTOGRAPHY_GET_INTERFACE -
- -
-gstbasecamerasrc -GstBaseCameraSrc -GST_BASE_CAMERA_SRC_CAST -GST_BASE_CAMERA_SRC_VIEWFINDER_PAD_NAME -GST_BASE_CAMERA_SRC_IMAGE_PAD_NAME -GST_BASE_CAMERA_SRC_VIDEO_PAD_NAME -GST_BASE_CAMERA_SRC_PREVIEW_MESSAGE_NAME -GstBaseCameraSrc -GstBaseCameraSrcClass -MIN_ZOOM -MAX_ZOOM -ZOOM_1X -gst_base_camera_src_get_photography -gst_base_camera_src_get_color_balance -gst_base_camera_src_set_mode -gst_base_camera_src_setup_zoom -gst_base_camera_src_setup_preview -gst_base_camera_src_finish_capture -gst_base_camera_src_post_preview - -GST_BASE_CAMERA_SRC -GST_IS_BASE_CAMERA_SRC -GST_TYPE_BASE_CAMERA_SRC -gst_base_camera_src_get_type -GST_BASE_CAMERA_SRC_CLASS -GST_IS_BASE_CAMERA_SRC_CLASS -GST_BASE_CAMERA_SRC_GET_CLASS -
- -
-gstsignalprocessor -GstSignalProcessor -GstSignalProcessorClassFlags -GST_SIGNAL_PROCESSOR_CLASS_CAN_PROCESS_IN_PLACE -GST_SIGNAL_PROCESSOR_CLASS_SET_CAN_PROCESS_IN_PLACE -GstSignalProcessorState -GST_SIGNAL_PROCESSOR_IS_INITIALIZED -GST_SIGNAL_PROCESSOR_IS_RUNNING -GstSignalProcessorGroup -GstSignalProcessor -GstSignalProcessorClass -gst_signal_processor_class_add_pad_template - -GST_SIGNAL_PROCESSOR -GST_IS_SIGNAL_PROCESSOR -GST_TYPE_SIGNAL_PROCESSOR -gst_signal_processor_get_type -GST_SIGNAL_PROCESSOR_CLASS -GST_IS_SIGNAL_PROCESSOR_CLASS -GST_SIGNAL_PROCESSOR_GET_CLASS -
- - -
-photography-enumtypes -gst_photography_noise_reduction_get_type -GST_TYPE_PHOTOGRAPHY_NOISE_REDUCTION -gst_white_balance_mode_get_type -GST_TYPE_WHITE_BALANCE_MODE -gst_colour_tone_mode_get_type -GST_TYPE_COLOUR_TONE_MODE -gst_scene_mode_get_type -GST_TYPE_SCENE_MODE -gst_flash_mode_get_type -GST_TYPE_FLASH_MODE -gst_focus_status_get_type -GST_TYPE_FOCUS_STATUS -gst_photo_caps_get_type -GST_TYPE_PHOTO_CAPS -gst_photo_shake_risk_get_type -GST_TYPE_PHOTO_SHAKE_RISK -gst_flicker_reduction_mode_get_type -GST_TYPE_FLICKER_REDUCTION_MODE -gst_focus_mode_get_type -GST_TYPE_FOCUS_MODE -
- -
-gstcamerabin-enum -DEFAULT_WIDTH -DEFAULT_HEIGHT -DEFAULT_CAPTURE_WIDTH -DEFAULT_CAPTURE_HEIGHT -DEFAULT_FPS_N -DEFAULT_FPS_D -DEFAULT_ZOOM -GstCameraBinMode -GST_TYPE_CAMERABIN_MODE -gst_camerabin_mode_get_type -
- -
-gstcamerabinpreview -gst_camerabin_create_preview_pipeline -gst_camerabin_destroy_preview_pipeline -gst_camerabin_preview_pipeline_post -gst_camerabin_preview_set_caps -
- -
-gstinsertbin -GstInsertbin -GstInsertBin -GstInsertBinClass -GstInsertBinCallback -gst_insert_bin_new -gst_insert_bin_append -gst_insert_bin_prepend -gst_insert_bin_insert_after -gst_insert_bin_insert_before -gst_insert_bin_remove - -GST_INSERT_BIN -GST_INSERT_BIN_CLASS -GST_INSERT_BIN_GET_CLASS -GST_IS_INSERT_BIN -GST_IS_INSERT_BIN_CLASS -GST_TYPE_INSERT_BIN -gst_insert_bin_get_type - -GstInsertBinPrivate -
- -
-gstplayer -GstPlayer - -gst_player_new - -gst_player_play -gst_player_pause -gst_player_stop - -gst_player_seek - -gst_player_set_uri -gst_player_get_uri - -gst_player_get_duration -gst_player_get_position - -gst_player_set_volume -gst_player_set_mute -gst_player_get_volume -gst_player_get_mute - -gst_player_get_pipeline - -gst_player_set_config -gst_player_get_config - -GstPlayerState -gst_player_state_get_name - -GST_PLAYER_ERROR -GstPlayerError -gst_player_error_get_name - -gst_player_get_media_info - -gst_player_set_audio_track -gst_player_set_video_track -gst_player_set_subtitle_track - -gst_player_get_current_audio_track -gst_player_get_current_video_track -gst_player_get_current_subtitle_track - -gst_player_set_audio_track_enabled -gst_player_set_video_track_enabled -gst_player_set_subtitle_track_enabled - -gst_player_set_subtitle_uri -gst_player_get_subtitle_uri - -gst_player_set_visualization -gst_player_set_visualization_enabled -gst_player_get_current_visualization - -GstPlayerColorBalanceType -gst_player_color_balance_type_get_name - -gst_player_has_color_balance -gst_player_set_color_balance -gst_player_get_color_balance - -gst_player_get_multiview_mode -gst_player_set_multiview_mode -gst_player_get_multiview_flags -gst_player_set_multiview_flags - -gst_player_get_audio_video_offset -gst_player_set_audio_video_offset - -gst_player_get_subtitle_video_offset -gst_player_set_subtitle_video_offset - -gst_player_get_rate -gst_player_set_rate - -GstPlayerSnapshotFormat -gst_player_get_video_snapshot - -GstPlayerSignalDispatcher -GstPlayerSignalDispatcherInterface - -GstPlayerVideoRenderer -GstPlayerVideoRendererInterface - - -gst_player_config_set_position_update_interval -gst_player_config_get_position_update_interval - -gst_player_config_set_user_agent -gst_player_config_get_user_agent - -gst_player_config_set_seek_accurate -gst_player_config_get_seek_accurate - - -GST_IS_PLAYER -GST_IS_PLAYER_CLASS -GST_PLAYER -GST_PLAYER_CAST -GST_PLAYER_CLASS -GST_PLAYER_GET_CLASS -GST_TYPE_PLAYER -GstPlayerClass -gst_player_get_type - -gst_player_visualization_get_type - -GST_TYPE_PLAYER_ERROR -gst_player_error_quark -gst_player_error_get_type - -GST_TYPE_PLAYER_STATE -gst_player_state_get_type - -GST_TYPE_PLAYER_COLOR_BALANCE_TYPE -gst_player_color_balance_type_get_type - -GST_TYPE_PLAYER_SIGNAL_DISPATCHER -GST_PLAYER_SIGNAL_DISPATCHER -GST_IS_PLAYER_SIGNAL_DISPATCHER -GST_PLAYER_SIGNAL_DISPATCHER_GET_INTERFACE -gst_player_signal_dispatcher_get_type - -GST_TYPE_PLAYER_VIDEO_RENDERER -GST_IS_PLAYER_VIDEO_RENDERER -GST_PLAYER_VIDEO_RENDERER -GST_PLAYER_VIDEO_RENDERER_GET_INTERFACE -gst_player_video_renderer_get_type -
- -
-gstplayer-gmaincontextsignaldispatcher -gst_player_g_main_context_signal_dispatcher_new - - -GST_PLAYER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER -GST_PLAYER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER_CAST -GST_PLAYER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER_CLASS -GST_PLAYER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER_GET_CLASS -GST_IS_PLAYER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER -GST_IS_PLAYER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER_CLASS -GST_TYPE_PLAYER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER - -gst_player_g_main_context_signal_dispatcher_get_type -
- -
-gstplayer-videooverlayvideorenderer -GstPlayerVideoOverlayVideoRenderer - -gst_player_video_overlay_video_renderer_new -gst_player_video_overlay_video_renderer_new_with_sink -gst_player_video_overlay_video_renderer_get_window_handle -gst_player_video_overlay_video_renderer_set_window_handle - -gst_player_video_overlay_video_renderer_expose - -gst_player_video_overlay_video_renderer_get_render_rectangle -gst_player_video_overlay_video_renderer_set_render_rectangle - - -GST_PLAYER_VIDEO_OVERLAY_VIDEO_RENDERER -GST_PLAYER_VIDEO_OVERLAY_VIDEO_RENDERER_CAST -GST_PLAYER_VIDEO_OVERLAY_VIDEO_RENDERER_CLASS -GST_PLAYER_VIDEO_OVERLAY_VIDEO_RENDERER_GET_CLASS -GST_IS_PLAYER_VIDEO_OVERLAY_VIDEO_RENDERER -GST_IS_PLAYER_VIDEO_OVERLAY_VIDEO_RENDERER_CLASS -GST_TYPE_PLAYER_VIDEO_OVERLAY_VIDEO_RENDERER - -gst_player_video_overlay_video_renderer_get_type - -
- -
-gstplayer-visualization -GstPlayerVisualization -gst_player_visualizations_get -gst_player_visualizations_free -gst_player_visualization_copy -gst_player_visualization_free - - -gst_player_visualization_get_type -
- -
-gstplayer-mediainfo -GstPlayerMediaInfo - -GstPlayerStreamInfo -GstPlayerAudioInfo -GstPlayerVideoInfo -GstPlayerSubtitleInfo - -gst_player_media_info_get_uri -gst_player_media_info_get_duration -gst_player_media_info_get_title -gst_player_media_info_get_container_format -gst_player_media_info_is_seekable -gst_player_media_info_is_live -gst_player_media_info_get_image_sample -gst_player_media_info_get_tags -gst_player_media_info_get_stream_list -gst_player_media_info_get_number_of_streams - -gst_player_media_info_get_audio_streams -gst_player_media_info_get_number_of_audio_streams -gst_player_media_info_get_video_streams -gst_player_media_info_get_number_of_video_streams -gst_player_media_info_get_subtitle_streams -gst_player_media_info_get_number_of_subtitle_streams - -gst_player_stream_info_get_index -gst_player_stream_info_get_caps -gst_player_stream_info_get_tags -gst_player_stream_info_get_codec -gst_player_stream_info_get_stream_type - -gst_player_audio_info_get_bitrate -gst_player_audio_info_get_channels -gst_player_audio_info_get_language -gst_player_audio_info_get_max_bitrate -gst_player_audio_info_get_sample_rate - -gst_player_video_info_get_bitrate -gst_player_video_info_get_height -gst_player_video_info_get_width -gst_player_video_info_get_framerate -gst_player_video_info_get_max_bitrate -gst_player_video_info_get_pixel_aspect_ratio - -gst_player_subtitle_info_get_language - -GST_PLAYER_MEDIA_INFO -GST_IS_PLAYER_MEDIA_INFO -GST_PLAYER_MEDIA_INFO_CLASS -GST_IS_PLAYER_MEDIA_INFO_CLASS -GST_TYPE_PLAYER_MEDIA_INFO -GstPlayerMediaInfoClass -gst_player_media_info_get_type - -GST_PLAYER_STREAM_INFO -GST_IS_PLAYER_STREAM_INFO -GST_PLAYER_STREAM_INFO_CLASS -GST_IS_PLAYER_STREAM_INFO_CLASS -GST_TYPE_PLAYER_STREAM_INFO -GstPlayerStreamInfoClass -gst_player_stream_info_get_type - -GST_PLAYER_AUDIO_INFO -GST_IS_PLAYER_AUDIO_INFO -GST_PLAYER_AUDIO_INFO_CLASS -GST_IS_PLAYER_AUDIO_INFO_CLASS -GST_TYPE_PLAYER_AUDIO_INFO -GstPlayerAudioInfoClass -gst_player_audio_info_get_type - -GST_PLAYER_VIDEO_INFO -GST_IS_PLAYER_VIDEO_INFO -GST_PLAYER_VIDEO_INFO_CLASS -GST_IS_PLAYER_VIDEO_INFO_CLASS -GST_TYPE_PLAYER_VIDEO_INFO -GstPlayerVideoInfoClass -gst_player_video_info_get_type - -GST_IS_PLAYER_SUBTITLE_INFO -GST_PLAYER_SUBTITLE_INFO -GST_PLAYER_SUBTITLE_INFO_CLASS -GST_IS_PLAYER_SUBTITLE_INFO_CLASS -GST_TYPE_PLAYER_SUBTITLE_INFO -GstPlayerSubtitleInfoClass -gst_player_subtitle_info_get_type -
- - -
-gstwebrtc-dtlstransport -GstWebRTCDTLSTransportState - -gst_webrtc_dtls_transport_new - - -GST_TYPE_WEBRTC_DTLS_TRANSPORT -gst_webrtc_dtls_transport_get_type -GstWebRTCDTLSTransport -GST_WEBRTC_DTLS_TRANSPORT -GST_IS_WEBRTC_DTLS_TRANSPORT -GstWebRTCDTLSTransportClass -GST_WEBRTC_DTLS_TRANSPORT_CLASS -GST_WEBRTC_DTLS_TRANSPORT_GET_CLASS -GST_IS_WEBRTC_DTLS_TRANSPORT_CLASS -
- -
-gstwebrtc-icetransport -GstWebRTCIceRole -GstWebRTCICEConnectionState -GstWebRTCICEGatheringState - - - - -GST_TYPE_WEBRTC_ICE_TRANSPORT -gst_webrtc_ice_transport_get_type -GstWebRTCICETransport -GST_WEBRTC_ICE_TRANSPORT -GST_IS_WEBRTC_ICE_TRANSPORT -GstWebRTCICETransportClass -GST_WEBRTC_ICE_TRANSPORT_CLASS -GST_WEBRTC_ICE_TRANSPORT_GET_CLASS -GST_IS_WEBRTC_ICE_TRANSPORT_CLASS -
- -
-gstwebrtc-receiver -gst_webrtc_rtp_receiver_new -gst_webrtc_rtp_receiver_get_parameters -gst_webrtc_rtp_receiver_set_parameters -gst_webrtc_rtp_receiver_set_rtcp_transport -gst_webrtc_rtp_receiver_set_transport - -GST_TYPE_WEBRTC_RTP_RECEIVER -gst_webrtc_rtp_receiver_get_type -GstWebRTCRTPReceiver -GST_WEBRTC_RTP_RECEIVER -GST_IS_WEBRTC_RTP_RECEIVER -GstWebRTCRTPReceiverClass -GST_WEBRTC_RTP_RECEIVER_CLASS -GST_WEBRTC_RTP_RECEIVER_GET_CLASS -GST_IS_WEBRTC_RTP_RECEIVER_CLASS -
- -
-gstwebrtc-sender -gst_webrtc_rtp_sender_new -gst_webrtc_rtp_sender_get_parameters -gst_webrtc_rtp_sender_set_parameters -gst_webrtc_rtp_sender_set_rtcp_transport -gst_webrtc_rtp_sender_set_transport - -GST_TYPE_WEBRTC_RTP_SENDER -gst_webrtc_rtp_sender_get_type -GstWebRTCRTPSender -GST_WEBRTC_RTP_SENDER -GST_IS_WEBRTC_RTP_SENDER -GstWebRTCRTPSenderClass -GST_WEBRTC_RTP_SENDER_CLASS -GST_WEBRTC_RTP_SENDER_GET_CLASS -GST_IS_WEBRTC_RTP_SENDER_CLASS -
- -
-gstwebrtc-sessiondescription -GstWebRTCSessionDescription -gst_webrtc_session_description_new -gst_webrtc_session_description_copy -gst_webrtc_session_description_free - -gst_webrtc_session_description_get_type -GST_TYPE_WEBRTC_SESSION_DESCRIPTION -
- -
-gstwebrtc-transceiver - -GST_TYPE_WEBRTC_RTP_TRANSCEIVER -gst_webrtc_rtp_transceiver_get_type -GstWebRTCRTPTransceiver -GST_WEBRTC_RTP_TRANSCEIVER -GST_IS_WEBRTC_RTP_TRANSCEIVER -GstWebRTCRTPTransceiverClass -GST_WEBRTC_RTP_TRANSCEIVER_CLASS -GST_WEBRTC_RTP_TRANSCEIVER_GET_CLASS -GST_IS_WEBRTC_RTP_TRANSCEIVER_CLASS -
diff --git a/docs/libs/transcoder/index.md b/docs/libs/transcoder/index.md new file mode 100644 index 0000000000..e6c4e6b0f6 --- /dev/null +++ b/docs/libs/transcoder/index.md @@ -0,0 +1,8 @@ +# GstTranscoder + +High level API to transcode streams + +This library should be linked to by getting cflags and libs from +gstreamer-transcoder-{{ gst_api_version.md }} + +> NOTE: This library API is considered *unstable* diff --git a/docs/libs/transcoder/sitemap.txt b/docs/libs/transcoder/sitemap.txt new file mode 100644 index 0000000000..4f91fcd8a3 --- /dev/null +++ b/docs/libs/transcoder/sitemap.txt @@ -0,0 +1 @@ +gi-index diff --git a/docs/meson.build b/docs/meson.build index b3d14d7e19..a4d0cc08d2 100644 --- a/docs/meson.build +++ b/docs/meson.build @@ -103,6 +103,7 @@ if build_gir {'name': 'adaptivedemux', 'lib': gstadaptivedemux_dep}, {'name': 'webrtc', 'gir': webrtc_gir, 'lib': gstwebrtc_dep, 'suffix': 'lib'}, {'name': 'audio', 'gir': audio_gir, 'lib': gstbadaudio_dep, 'prefix': 'bad-'}, + {'name': 'transcoder', 'gir': transcoder_gir, 'lib': gst_transcoder_dep}, ] endif diff --git a/gst-libs/gst/meson.build b/gst-libs/gst/meson.build index daa1bda189..3ee9ad8d35 100644 --- a/gst-libs/gst/meson.build +++ b/gst-libs/gst/meson.build @@ -11,6 +11,7 @@ subdir('mpegts') subdir('opencv') subdir('player') subdir('sctp') +subdir('transcoder') subdir('vulkan') subdir('wayland') subdir('webrtc') diff --git a/gst-libs/gst/transcoder/gsttranscoder.c b/gst-libs/gst/transcoder/gsttranscoder.c new file mode 100644 index 0000000000..7abe3fa44d --- /dev/null +++ b/gst-libs/gst/transcoder/gsttranscoder.c @@ -0,0 +1,1609 @@ +/* GStreamer + * + * Copyright (C) 2014-2015 Sebastian Dröge + * Copyright (C) 2015 Thibault Saunier + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION:gsttranscoder + * @short_description: High level API to transcode media files + * from one format to any other format using the GStreamer framework. + * @symbols: + * - gst_transcoder_error_quark + */ + +#include "gsttranscoder.h" + +GST_DEBUG_CATEGORY_STATIC (gst_transcoder_debug); +#define GST_CAT_DEFAULT gst_transcoder_debug + +#define DEFAULT_URI NULL +#define DEFAULT_POSITION GST_CLOCK_TIME_NONE +#define DEFAULT_DURATION GST_CLOCK_TIME_NONE +#define DEFAULT_POSITION_UPDATE_INTERVAL_MS 100 +#define DEFAULT_AVOID_REENCODING FALSE + +GQuark +gst_transcoder_error_quark (void) +{ + static GQuark quark; + + if (!quark) + quark = g_quark_from_static_string ("gst-transcoder-error-quark"); + + return quark; +} + +enum +{ + PROP_0, + PROP_SIGNAL_DISPATCHER, + PROP_SRC_URI, + PROP_DEST_URI, + PROP_PROFILE, + PROP_POSITION, + PROP_DURATION, + PROP_PIPELINE, + PROP_POSITION_UPDATE_INTERVAL, + PROP_AVOID_REENCODING, + PROP_LAST +}; + +enum +{ + SIGNAL_POSITION_UPDATED, + SIGNAL_DURATION_CHANGED, + SIGNAL_DONE, + SIGNAL_ERROR, + SIGNAL_WARNING, + SIGNAL_LAST +}; + +struct _GstTranscoder +{ + GstObject parent; + + GstTranscoderSignalDispatcher *signal_dispatcher; + + GstEncodingProfile *profile; + gchar *source_uri; + gchar *dest_uri; + + GThread *thread; + GCond cond; + GMainContext *context; + GMainLoop *loop; + + GstElement *transcodebin; + GstBus *bus; + GstState target_state, current_state; + gboolean is_live, is_eos; + GSource *tick_source, *ready_timeout_source; + + guint position_update_interval_ms; + gint wanted_cpu_usage; + + GstClockTime last_duration; +}; + +struct _GstTranscoderClass +{ + GstObjectClass parent_class; +}; + +static void +gst_transcoder_signal_dispatcher_dispatch (GstTranscoderSignalDispatcher * self, + GstTranscoder * transcoder, void (*emitter) (gpointer data), gpointer data, + GDestroyNotify destroy); + +#define parent_class gst_transcoder_parent_class +G_DEFINE_TYPE (GstTranscoder, gst_transcoder, GST_TYPE_OBJECT); + +static guint signals[SIGNAL_LAST] = { 0, }; +static GParamSpec *param_specs[PROP_LAST] = { NULL, }; + +static void gst_transcoder_dispose (GObject * object); +static void gst_transcoder_finalize (GObject * object); +static void gst_transcoder_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_transcoder_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); +static void gst_transcoder_constructed (GObject * object); + +static gpointer gst_transcoder_main (gpointer data); + +static gboolean gst_transcoder_set_position_update_interval_internal (gpointer + user_data); + + +/** + * gst_transcoder_set_cpu_usage: + * @self: The GstTranscoder to limit CPU usage on. + * @cpu_usage: The percentage of the CPU the process running the transcoder + * should try to use. It takes into account the number of cores available. + * + * Sets @cpu_usage as target percentage CPU usage of the process running the + * transcoding task. It will modulate the transcoding speed to reach that target + * usage. + */ +void +gst_transcoder_set_cpu_usage (GstTranscoder * self, gint cpu_usage) +{ + GST_OBJECT_LOCK (self); + self->wanted_cpu_usage = cpu_usage; + if (self->transcodebin) + g_object_set (self->transcodebin, "cpu-usage", cpu_usage, NULL); + GST_OBJECT_UNLOCK (self); +} + +static void +gst_transcoder_init (GstTranscoder * self) +{ + GST_TRACE_OBJECT (self, "Initializing"); + + self = gst_transcoder_get_instance_private (self); + + g_cond_init (&self->cond); + + self->context = g_main_context_new (); + self->loop = g_main_loop_new (self->context, FALSE); + self->wanted_cpu_usage = 100; + + self->position_update_interval_ms = DEFAULT_POSITION_UPDATE_INTERVAL_MS; + + GST_TRACE_OBJECT (self, "Initialized"); +} + +static void +gst_transcoder_class_init (GstTranscoderClass * klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + + gobject_class->set_property = gst_transcoder_set_property; + gobject_class->get_property = gst_transcoder_get_property; + gobject_class->dispose = gst_transcoder_dispose; + gobject_class->finalize = gst_transcoder_finalize; + gobject_class->constructed = gst_transcoder_constructed; + + param_specs[PROP_SIGNAL_DISPATCHER] = + g_param_spec_object ("signal-dispatcher", + "Signal Dispatcher", "Dispatcher for the signals to e.g. event loops", + GST_TYPE_TRANSCODER_SIGNAL_DISPATCHER, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + param_specs[PROP_SRC_URI] = + g_param_spec_string ("src-uri", "URI", "Source URI", DEFAULT_URI, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + param_specs[PROP_DEST_URI] = + g_param_spec_string ("dest-uri", "URI", "Source URI", DEFAULT_URI, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + param_specs[PROP_PROFILE] = + g_param_spec_object ("profile", "Profile", + "The GstEncodingProfile to use", GST_TYPE_ENCODING_PROFILE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + param_specs[PROP_POSITION] = + g_param_spec_uint64 ("position", "Position", "Current Position", + 0, G_MAXUINT64, DEFAULT_POSITION, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + param_specs[PROP_DURATION] = + g_param_spec_uint64 ("duration", "Duration", "Duration", + 0, G_MAXUINT64, DEFAULT_DURATION, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + param_specs[PROP_PIPELINE] = + g_param_spec_object ("pipeline", "Pipeline", + "GStreamer pipeline that is used", + GST_TYPE_ELEMENT, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + param_specs[PROP_POSITION_UPDATE_INTERVAL] = + g_param_spec_uint ("position-update-interval", "Position update interval", + "Interval in milliseconds between two position-updated signals." + "Pass 0 to stop updating the position.", + 0, 10000, DEFAULT_POSITION_UPDATE_INTERVAL_MS, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * GstTranscoder:avoid-reencoding: + * + * See #encodebin:avoid-reencoding + */ + param_specs[PROP_AVOID_REENCODING] = + g_param_spec_boolean ("avoid-reencoding", "Avoid re-encoding", + "Whether to re-encode portions of compatible video streams that lay on segment boundaries", + DEFAULT_AVOID_REENCODING, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (gobject_class, PROP_LAST, param_specs); + + signals[SIGNAL_POSITION_UPDATED] = + g_signal_new ("position-updated", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL, + NULL, NULL, G_TYPE_NONE, 1, GST_TYPE_CLOCK_TIME); + + signals[SIGNAL_DURATION_CHANGED] = + g_signal_new ("duration-changed", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL, + NULL, NULL, G_TYPE_NONE, 1, GST_TYPE_CLOCK_TIME); + + signals[SIGNAL_DONE] = + g_signal_new ("done", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL, + NULL, NULL, G_TYPE_NONE, 0, G_TYPE_INVALID); + + signals[SIGNAL_ERROR] = + g_signal_new ("error", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL, + NULL, NULL, G_TYPE_NONE, 2, G_TYPE_ERROR, GST_TYPE_STRUCTURE); + + signals[SIGNAL_WARNING] = + g_signal_new ("warning", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL, + NULL, NULL, G_TYPE_NONE, 2, G_TYPE_ERROR, GST_TYPE_STRUCTURE); +} + +static void +gst_transcoder_dispose (GObject * object) +{ + GstTranscoder *self = GST_TRANSCODER (object); + + GST_TRACE_OBJECT (self, "Stopping main thread"); + + if (self->loop) { + g_main_loop_quit (self->loop); + + g_thread_join (self->thread); + self->thread = NULL; + + g_main_loop_unref (self->loop); + self->loop = NULL; + + g_main_context_unref (self->context); + self->context = NULL; + + } + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gst_transcoder_finalize (GObject * object) +{ + GstTranscoder *self = GST_TRANSCODER (object); + + GST_TRACE_OBJECT (self, "Finalizing"); + + g_free (self->source_uri); + g_free (self->dest_uri); + if (self->signal_dispatcher) + g_object_unref (self->signal_dispatcher); + g_cond_clear (&self->cond); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gst_transcoder_constructed (GObject * object) +{ + GstTranscoder *self = GST_TRANSCODER (object); + + GST_TRACE_OBJECT (self, "Constructed"); + + self->transcodebin = + gst_element_factory_make ("uritranscodebin", "uritranscodebin"); + + g_object_set (self->transcodebin, "source-uri", self->source_uri, + "dest-uri", self->dest_uri, "profile", self->profile, + "cpu-usage", self->wanted_cpu_usage, NULL); + + GST_OBJECT_LOCK (self); + self->thread = g_thread_new ("GstTranscoder", gst_transcoder_main, self); + while (!self->loop || !g_main_loop_is_running (self->loop)) + g_cond_wait (&self->cond, GST_OBJECT_GET_LOCK (self)); + GST_OBJECT_UNLOCK (self); + + G_OBJECT_CLASS (parent_class)->constructed (object); +} + +static void +gst_transcoder_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstTranscoder *self = GST_TRANSCODER (object); + + switch (prop_id) { + case PROP_SIGNAL_DISPATCHER: + self->signal_dispatcher = g_value_dup_object (value); + break; + case PROP_SRC_URI:{ + GST_OBJECT_LOCK (self); + g_free (self->source_uri); + self->source_uri = g_value_dup_string (value); + GST_DEBUG_OBJECT (self, "Set source_uri=%s", self->source_uri); + GST_OBJECT_UNLOCK (self); + break; + } + case PROP_DEST_URI:{ + GST_OBJECT_LOCK (self); + g_free (self->dest_uri); + self->dest_uri = g_value_dup_string (value); + GST_DEBUG_OBJECT (self, "Set dest_uri=%s", self->dest_uri); + GST_OBJECT_UNLOCK (self); + break; + } + case PROP_POSITION_UPDATE_INTERVAL: + GST_OBJECT_LOCK (self); + self->position_update_interval_ms = g_value_get_uint (value); + GST_DEBUG_OBJECT (self, "Set position update interval=%u ms", + g_value_get_uint (value)); + GST_OBJECT_UNLOCK (self); + + gst_transcoder_set_position_update_interval_internal (self); + break; + case PROP_PROFILE: + GST_OBJECT_LOCK (self); + self->profile = g_value_dup_object (value); + GST_OBJECT_UNLOCK (self); + break; + case PROP_AVOID_REENCODING: + g_object_set (self->transcodebin, "avoid-reencoding", + g_value_get_boolean (value), NULL); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_transcoder_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstTranscoder *self = GST_TRANSCODER (object); + + switch (prop_id) { + case PROP_SRC_URI: + GST_OBJECT_LOCK (self); + g_value_set_string (value, self->source_uri); + GST_OBJECT_UNLOCK (self); + break; + case PROP_DEST_URI: + GST_OBJECT_LOCK (self); + g_value_set_string (value, self->dest_uri); + GST_OBJECT_UNLOCK (self); + break; + case PROP_POSITION:{ + gint64 position = 0; + + if (self->is_eos) + position = self->last_duration; + else + gst_element_query_position (self->transcodebin, GST_FORMAT_TIME, + &position); + g_value_set_uint64 (value, position); + GST_TRACE_OBJECT (self, "Returning position=%" GST_TIME_FORMAT, + GST_TIME_ARGS (g_value_get_uint64 (value))); + break; + } + case PROP_DURATION:{ + gint64 duration = 0; + + gst_element_query_duration (self->transcodebin, GST_FORMAT_TIME, + &duration); + g_value_set_uint64 (value, duration); + GST_TRACE_OBJECT (self, "Returning duration=%" GST_TIME_FORMAT, + GST_TIME_ARGS (g_value_get_uint64 (value))); + break; + } + case PROP_PIPELINE: + g_value_set_object (value, self->transcodebin); + break; + case PROP_POSITION_UPDATE_INTERVAL: + GST_OBJECT_LOCK (self); + g_value_set_uint (value, + gst_transcoder_get_position_update_interval (self)); + GST_OBJECT_UNLOCK (self); + break; + case PROP_PROFILE: + GST_OBJECT_LOCK (self); + g_value_set_object (value, self->profile); + GST_OBJECT_UNLOCK (self); + break; + case PROP_AVOID_REENCODING: + { + gboolean avoid_reencoding; + + g_object_get (self->transcodebin, "avoid-reencoding", &avoid_reencoding, + NULL); + g_value_set_boolean (value, avoid_reencoding); + break; + } + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static gboolean +main_loop_running_cb (gpointer user_data) +{ + GstTranscoder *self = GST_TRANSCODER (user_data); + + GST_TRACE_OBJECT (self, "Main loop running now"); + + GST_OBJECT_LOCK (self); + g_cond_signal (&self->cond); + GST_OBJECT_UNLOCK (self); + + return G_SOURCE_REMOVE; +} + +typedef struct +{ + GstTranscoder *transcoder; + GstClockTime position; +} PositionUpdatedSignalData; + +static void +position_updated_dispatch (gpointer user_data) +{ + PositionUpdatedSignalData *data = user_data; + + if (data->transcoder->target_state >= GST_STATE_PAUSED) { + g_signal_emit (data->transcoder, signals[SIGNAL_POSITION_UPDATED], 0, + data->position); + g_object_notify_by_pspec (G_OBJECT (data->transcoder), + param_specs[PROP_POSITION]); + } +} + +static void +position_updated_signal_data_free (PositionUpdatedSignalData * data) +{ + g_object_unref (data->transcoder); + g_free (data); +} + +static gboolean +tick_cb (gpointer user_data) +{ + GstTranscoder *self = GST_TRANSCODER (user_data); + gint64 position; + + if (self->target_state >= GST_STATE_PAUSED + && gst_element_query_position (self->transcodebin, GST_FORMAT_TIME, + &position)) { + GST_LOG_OBJECT (self, "Position %" GST_TIME_FORMAT, + GST_TIME_ARGS (position)); + + if (g_signal_handler_find (self, G_SIGNAL_MATCH_ID, + signals[SIGNAL_POSITION_UPDATED], 0, NULL, NULL, NULL) != 0) { + PositionUpdatedSignalData *data = g_new0 (PositionUpdatedSignalData, 1); + + data->transcoder = g_object_ref (self); + data->position = position; + gst_transcoder_signal_dispatcher_dispatch (self->signal_dispatcher, self, + position_updated_dispatch, data, + (GDestroyNotify) position_updated_signal_data_free); + } + } + + return G_SOURCE_CONTINUE; +} + +static void +add_tick_source (GstTranscoder * self) +{ + if (self->tick_source) + return; + + if (!self->position_update_interval_ms) + return; + + self->tick_source = g_timeout_source_new (self->position_update_interval_ms); + g_source_set_callback (self->tick_source, (GSourceFunc) tick_cb, self, NULL); + g_source_attach (self->tick_source, self->context); +} + +static void +remove_tick_source (GstTranscoder * self) +{ + if (!self->tick_source) + return; + + g_source_destroy (self->tick_source); + g_source_unref (self->tick_source); + self->tick_source = NULL; +} + +typedef struct +{ + GstTranscoder *transcoder; + GError *err; + GstStructure *details; +} IssueSignalData; + +static void +error_dispatch (gpointer user_data) +{ + IssueSignalData *data = user_data; + + g_signal_emit (data->transcoder, signals[SIGNAL_ERROR], 0, data->err, + data->details); +} + +static void +free_issue_signal_data (IssueSignalData * data) +{ + g_object_unref (data->transcoder); + if (data->details) + gst_structure_free (data->details); + g_clear_error (&data->err); + g_free (data); +} + +static void +emit_error (GstTranscoder * self, GError * err, const GstStructure * details) +{ + if (g_signal_handler_find (self, G_SIGNAL_MATCH_ID, + signals[SIGNAL_ERROR], 0, NULL, NULL, NULL) != 0) { + IssueSignalData *data = g_new0 (IssueSignalData, 1); + + data->transcoder = g_object_ref (self); + data->err = g_error_copy (err); + if (details) + data->details = gst_structure_copy (details); + gst_transcoder_signal_dispatcher_dispatch (self->signal_dispatcher, self, + error_dispatch, data, (GDestroyNotify) free_issue_signal_data); + } + + g_error_free (err); + + remove_tick_source (self); + + self->target_state = GST_STATE_NULL; + self->current_state = GST_STATE_NULL; + self->is_live = FALSE; + self->is_eos = FALSE; + gst_element_set_state (self->transcodebin, GST_STATE_NULL); +} + +static void +dump_dot_file (GstTranscoder * self, const gchar * name) +{ + gchar *full_name; + + full_name = g_strdup_printf ("gst-transcoder.%p.%s", self, name); + + GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (self->transcodebin), + GST_DEBUG_GRAPH_SHOW_VERBOSE, full_name); + + g_free (full_name); +} + +static void +warning_dispatch (gpointer user_data) +{ + IssueSignalData *data = user_data; + + g_signal_emit (data->transcoder, signals[SIGNAL_WARNING], 0, data->err, + data->details); +} + +static void +emit_warning (GstTranscoder * self, GError * err, const GstStructure * details) +{ + if (g_signal_handler_find (self, G_SIGNAL_MATCH_ID, + signals[SIGNAL_WARNING], 0, NULL, NULL, NULL) != 0) { + IssueSignalData *data = g_new0 (IssueSignalData, 1); + + data->transcoder = g_object_ref (self); + data->err = g_error_copy (err); + if (details) + data->details = gst_structure_copy (details); + gst_transcoder_signal_dispatcher_dispatch (self->signal_dispatcher, self, + warning_dispatch, data, (GDestroyNotify) free_issue_signal_data); + } + + g_error_free (err); +} + +static void +error_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg, gpointer user_data) +{ + GError *err; + GstTranscoder *self = GST_TRANSCODER (user_data); + gchar *name, *debug, *message; + GstStructure *details = NULL; + + dump_dot_file (self, "error"); + + gst_message_parse_error (msg, &err, &debug); + gst_message_parse_error_details (msg, (const GstStructure **) &details); + + if (!details) + details = gst_structure_new_empty ("details"); + else + details = gst_structure_copy (details); + + name = gst_object_get_path_string (msg->src); + message = gst_error_get_message (err->domain, err->code); + + gst_structure_set (details, "debug", G_TYPE_STRING, debug, + "msg-source-element-name", G_TYPE_STRING, "name", + "msg-source-type", G_TYPE_GTYPE, G_OBJECT_TYPE (msg->src), + "msg-error", G_TYPE_STRING, message, NULL); + emit_error (self, g_error_copy (err), details); + + gst_structure_free (details); + g_clear_error (&err); + g_free (debug); + g_free (name); + g_free (message); +} + +static void +warning_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg, gpointer user_data) +{ + GstTranscoder *self = GST_TRANSCODER (user_data); + GError *err, *transcoder_err; + gchar *name, *debug, *message, *full_message; + const GstStructure *details = NULL; + + dump_dot_file (self, "warning"); + + gst_message_parse_warning (msg, &err, &debug); + gst_message_parse_warning_details (msg, &details); + + name = gst_object_get_path_string (msg->src); + message = gst_error_get_message (err->domain, err->code); + + if (debug) + full_message = + g_strdup_printf ("Warning from element %s: %s\n%s\n%s", name, message, + err->message, debug); + else + full_message = + g_strdup_printf ("Warning from element %s: %s\n%s", name, message, + err->message); + + GST_WARNING_OBJECT (self, "WARNING: from element %s: %s\n", name, + err->message); + if (debug != NULL) + GST_WARNING_OBJECT (self, "Additional debug info:\n%s\n", debug); + + transcoder_err = + g_error_new_literal (GST_TRANSCODER_ERROR, GST_TRANSCODER_ERROR_FAILED, + full_message); + emit_warning (self, transcoder_err, details); + + g_clear_error (&err); + g_free (debug); + g_free (name); + g_free (full_message); + g_free (message); +} + +static void +eos_dispatch (gpointer user_data) +{ + g_signal_emit (user_data, signals[SIGNAL_DONE], 0); +} + +static void +eos_cb (G_GNUC_UNUSED GstBus * bus, G_GNUC_UNUSED GstMessage * msg, + gpointer user_data) +{ + GstTranscoder *self = GST_TRANSCODER (user_data); + + GST_DEBUG_OBJECT (self, "End of stream"); + + gst_element_query_duration (self->transcodebin, GST_FORMAT_TIME, + (gint64 *) & self->last_duration); + tick_cb (self); + remove_tick_source (self); + + if (g_signal_handler_find (self, G_SIGNAL_MATCH_ID, + signals[SIGNAL_DONE], 0, NULL, NULL, NULL) != 0) { + gst_transcoder_signal_dispatcher_dispatch (self->signal_dispatcher, self, + eos_dispatch, g_object_ref (self), (GDestroyNotify) g_object_unref); + } + self->is_eos = TRUE; +} + +static void +clock_lost_cb (G_GNUC_UNUSED GstBus * bus, G_GNUC_UNUSED GstMessage * msg, + gpointer user_data) +{ + GstTranscoder *self = GST_TRANSCODER (user_data); + GstStateChangeReturn state_ret; + + GST_DEBUG_OBJECT (self, "Clock lost"); + if (self->target_state >= GST_STATE_PLAYING) { + state_ret = gst_element_set_state (self->transcodebin, GST_STATE_PAUSED); + if (state_ret != GST_STATE_CHANGE_FAILURE) + state_ret = gst_element_set_state (self->transcodebin, GST_STATE_PLAYING); + + if (state_ret == GST_STATE_CHANGE_FAILURE) + emit_error (self, g_error_new (GST_TRANSCODER_ERROR, + GST_TRANSCODER_ERROR_FAILED, "Failed to handle clock loss"), + NULL); + } +} + +typedef struct +{ + GstTranscoder *transcoder; + GstClockTime duration; +} DurationChangedSignalData; + +static void +duration_changed_dispatch (gpointer user_data) +{ + DurationChangedSignalData *data = user_data; + + if (data->transcoder->target_state >= GST_STATE_PAUSED) { + g_signal_emit (data->transcoder, signals[SIGNAL_DURATION_CHANGED], 0, + data->duration); + g_object_notify_by_pspec (G_OBJECT (data->transcoder), + param_specs[PROP_DURATION]); + } +} + +static void +duration_changed_signal_data_free (DurationChangedSignalData * data) +{ + g_object_unref (data->transcoder); + g_free (data); +} + +static void +emit_duration_changed (GstTranscoder * self, GstClockTime duration) +{ + GST_DEBUG_OBJECT (self, "Duration changed %" GST_TIME_FORMAT, + GST_TIME_ARGS (duration)); + + if (g_signal_handler_find (self, G_SIGNAL_MATCH_ID, + signals[SIGNAL_DURATION_CHANGED], 0, NULL, NULL, NULL) != 0) { + DurationChangedSignalData *data = g_new0 (DurationChangedSignalData, 1); + + data->transcoder = g_object_ref (self); + data->duration = duration; + gst_transcoder_signal_dispatcher_dispatch (self->signal_dispatcher, self, + duration_changed_dispatch, data, + (GDestroyNotify) duration_changed_signal_data_free); + } +} + +static void +state_changed_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg, + gpointer user_data) +{ + GstTranscoder *self = GST_TRANSCODER (user_data); + GstState old_state, new_state, pending_state; + + gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state); + + if (GST_MESSAGE_SRC (msg) == GST_OBJECT (self->transcodebin)) { + gchar *transition_name; + + GST_DEBUG_OBJECT (self, "Changed state old: %s new: %s pending: %s", + gst_element_state_get_name (old_state), + gst_element_state_get_name (new_state), + gst_element_state_get_name (pending_state)); + + transition_name = g_strdup_printf ("%s_%s", + gst_element_state_get_name (old_state), + gst_element_state_get_name (new_state)); + dump_dot_file (self, transition_name); + g_free (transition_name); + + self->current_state = new_state; + + if (new_state == GST_STATE_PLAYING + && pending_state == GST_STATE_VOID_PENDING) { + add_tick_source (self); + } + } +} + +static void +duration_changed_cb (G_GNUC_UNUSED GstBus * bus, G_GNUC_UNUSED GstMessage * msg, + gpointer user_data) +{ + GstTranscoder *self = GST_TRANSCODER (user_data); + gint64 duration; + + if (gst_element_query_duration (self->transcodebin, GST_FORMAT_TIME, + &duration)) { + emit_duration_changed (self, duration); + } +} + +static void +latency_cb (G_GNUC_UNUSED GstBus * bus, G_GNUC_UNUSED GstMessage * msg, + gpointer user_data) +{ + GstTranscoder *self = GST_TRANSCODER (user_data); + + GST_DEBUG_OBJECT (self, "Latency changed"); + + gst_bin_recalculate_latency (GST_BIN (self->transcodebin)); +} + +static void +request_state_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg, + gpointer user_data) +{ + GstTranscoder *self = GST_TRANSCODER (user_data); + GstState state; + GstStateChangeReturn state_ret; + + gst_message_parse_request_state (msg, &state); + + GST_DEBUG_OBJECT (self, "State %s requested", + gst_element_state_get_name (state)); + + self->target_state = state; + state_ret = gst_element_set_state (self->transcodebin, state); + if (state_ret == GST_STATE_CHANGE_FAILURE) + emit_error (self, g_error_new (GST_TRANSCODER_ERROR, + GST_TRANSCODER_ERROR_FAILED, + "Failed to change to requested state %s", + gst_element_state_get_name (state)), NULL); +} + +static void +element_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg, gpointer user_data) +{ + GstTranscoder *self = GST_TRANSCODER (user_data); + const GstStructure *s; + + s = gst_message_get_structure (msg); + if (gst_structure_has_name (s, "redirect")) { + const gchar *new_location; + + new_location = gst_structure_get_string (s, "new-location"); + if (!new_location) { + const GValue *locations_list, *location_val; + guint i, size; + + locations_list = gst_structure_get_value (s, "locations"); + size = gst_value_list_get_size (locations_list); + for (i = 0; i < size; ++i) { + const GstStructure *location_s; + + location_val = gst_value_list_get_value (locations_list, i); + if (!GST_VALUE_HOLDS_STRUCTURE (location_val)) + continue; + + location_s = (const GstStructure *) g_value_get_boxed (location_val); + if (!gst_structure_has_name (location_s, "redirect")) + continue; + + new_location = gst_structure_get_string (location_s, "new-location"); + if (new_location) + break; + } + } + + if (new_location) { + GST_FIXME_OBJECT (self, "Handle redirection to '%s'", new_location); + } + } +} + + +static gpointer +gst_transcoder_main (gpointer data) +{ + GstTranscoder *self = GST_TRANSCODER (data); + GstBus *bus; + GSource *source; + GSource *bus_source; + + GST_TRACE_OBJECT (self, "Starting main thread"); + + g_main_context_push_thread_default (self->context); + + source = g_idle_source_new (); + g_source_set_callback (source, (GSourceFunc) main_loop_running_cb, self, + NULL); + g_source_attach (source, self->context); + g_source_unref (source); + + self->bus = bus = gst_element_get_bus (self->transcodebin); + bus_source = gst_bus_create_watch (bus); + g_source_set_callback (bus_source, (GSourceFunc) gst_bus_async_signal_func, + NULL, NULL); + g_source_attach (bus_source, self->context); + + g_signal_connect (G_OBJECT (bus), "message::error", G_CALLBACK (error_cb), + self); + g_signal_connect (G_OBJECT (bus), "message::warning", G_CALLBACK (warning_cb), + self); + g_signal_connect (G_OBJECT (bus), "message::eos", G_CALLBACK (eos_cb), self); + g_signal_connect (G_OBJECT (bus), "message::state-changed", + G_CALLBACK (state_changed_cb), self); + g_signal_connect (G_OBJECT (bus), "message::clock-lost", + G_CALLBACK (clock_lost_cb), self); + g_signal_connect (G_OBJECT (bus), "message::duration-changed", + G_CALLBACK (duration_changed_cb), self); + g_signal_connect (G_OBJECT (bus), "message::latency", + G_CALLBACK (latency_cb), self); + g_signal_connect (G_OBJECT (bus), "message::request-state", + G_CALLBACK (request_state_cb), self); + g_signal_connect (G_OBJECT (bus), "message::element", + G_CALLBACK (element_cb), self); + + self->target_state = GST_STATE_NULL; + self->current_state = GST_STATE_NULL; + self->is_eos = FALSE; + self->is_live = FALSE; + + GST_TRACE_OBJECT (self, "Starting main loop"); + g_main_loop_run (self->loop); + GST_TRACE_OBJECT (self, "Stopped main loop"); + + g_source_destroy (bus_source); + g_source_unref (bus_source); + gst_object_unref (bus); + + remove_tick_source (self); + + g_main_context_pop_thread_default (self->context); + + self->target_state = GST_STATE_NULL; + self->current_state = GST_STATE_NULL; + if (self->transcodebin) { + gst_element_set_state (self->transcodebin, GST_STATE_NULL); + g_clear_object (&self->transcodebin); + } + + GST_TRACE_OBJECT (self, "Stopped main thread"); + + return NULL; +} + +static gpointer +gst_transcoder_init_once (G_GNUC_UNUSED gpointer user_data) +{ + gst_init (NULL, NULL); + + GST_DEBUG_CATEGORY_INIT (gst_transcoder_debug, "gst-transcoder", 0, + "GstTranscoder"); + gst_transcoder_error_quark (); + + return NULL; +} + +static GstEncodingProfile * +create_encoding_profile (const gchar * pname) +{ + GstEncodingProfile *profile; + GValue value = G_VALUE_INIT; + + g_value_init (&value, GST_TYPE_ENCODING_PROFILE); + + if (!gst_value_deserialize (&value, pname)) { + g_value_reset (&value); + + return NULL; + } + + profile = g_value_dup_object (&value); + g_value_reset (&value); + + return profile; +} + +/** + * gst_transcoder_new: + * @source_uri: The URI of the media stream to transcode + * @dest_uri: The URI of the destination of the transcoded stream + * @encoding_profile: The serialized #GstEncodingProfile defining the output + * format. Have a look at the #GstEncodingProfile documentation to find more + * about the serialization format. + * + * Returns: a new #GstTranscoder instance + */ +GstTranscoder * +gst_transcoder_new (const gchar * source_uri, + const gchar * dest_uri, const gchar * encoding_profile) +{ + GstEncodingProfile *profile; + + profile = create_encoding_profile (encoding_profile); + + return gst_transcoder_new_full (source_uri, dest_uri, profile, NULL); +} + +/** + * gst_transcoder_new_full: + * @source_uri: The URI of the media stream to transcode + * @dest_uri: The URI of the destination of the transcoded stream + * @profile: The #GstEncodingProfile defining the output format + * have a look at the #GstEncodingProfile documentation to find more + * about the serialization format. + * @signal_dispatcher: The #GstTranscoderSignalDispatcher to be used + * to dispatch the various signals. + * + * Returns: a new #GstTranscoder instance + */ +GstTranscoder * +gst_transcoder_new_full (const gchar * source_uri, + const gchar * dest_uri, GstEncodingProfile * profile, + GstTranscoderSignalDispatcher * signal_dispatcher) +{ + static GOnce once = G_ONCE_INIT; + + g_once (&once, gst_transcoder_init_once, NULL); + + g_return_val_if_fail (source_uri, NULL); + g_return_val_if_fail (dest_uri, NULL); + + return g_object_new (GST_TYPE_TRANSCODER, "src-uri", source_uri, + "dest-uri", dest_uri, "profile", profile, + "signal-dispatcher", signal_dispatcher, NULL); +} + +typedef struct +{ + GError **user_error; + GMutex m; + GCond cond; + + gboolean done; + +} RunSyncData; + +static void +_error_cb (GstTranscoder * self, GError * error, GstStructure * details, + RunSyncData * data) +{ + g_mutex_lock (&data->m); + data->done = TRUE; + if (data->user_error && (*data->user_error) == NULL) + g_propagate_error (data->user_error, error); + g_cond_broadcast (&data->cond); + g_mutex_unlock (&data->m); +} + +static void +_done_cb (GstTranscoder * self, RunSyncData * data) +{ + g_mutex_lock (&data->m); + data->done = TRUE; + g_cond_broadcast (&data->cond); + g_mutex_unlock (&data->m); +} + +/** + * gst_transcoder_run: + * @self: The GstTranscoder to run + * @error: (allow-none): An error to be set if transcoding fails + * + * Run the transcoder task synchonously. You can connect + * to the 'position' signal to get information about the + * progress of the transcoding. + */ +gboolean +gst_transcoder_run (GstTranscoder * self, GError ** error) +{ + RunSyncData data = { 0, }; + + g_mutex_init (&data.m); + g_cond_init (&data.cond); + + g_signal_connect (self, "error", G_CALLBACK (_error_cb), &data); + g_signal_connect (self, "done", G_CALLBACK (_done_cb), &data); + gst_transcoder_run_async (self); + + g_mutex_lock (&data.m); + while (!data.done) { + g_cond_wait (&data.cond, &data.m); + } + g_mutex_unlock (&data.m); + + if (data.user_error) { + g_propagate_error (error, *data.user_error); + + return FALSE; + } + + return TRUE; +} + +/** + * gst_transcoder_run_async: + * @self: The GstTranscoder to run + * + * Run the transcoder task asynchronously. You should connect + * to the 'done' signal to be notified about when the + * transcoding is done, and to the 'error' signal to be + * notified about any error. + */ +void +gst_transcoder_run_async (GstTranscoder * self) +{ + GstStateChangeReturn state_ret; + + GST_DEBUG_OBJECT (self, "Play"); + + if (!self->profile) { + emit_error (self, g_error_new (GST_TRANSCODER_ERROR, + GST_TRANSCODER_ERROR_FAILED, "No \"profile\" provided"), NULL); + + return; + } + + self->target_state = GST_STATE_PLAYING; + state_ret = gst_element_set_state (self->transcodebin, GST_STATE_PLAYING); + + if (state_ret == GST_STATE_CHANGE_FAILURE) { + emit_error (self, g_error_new (GST_TRANSCODER_ERROR, + GST_TRANSCODER_ERROR_FAILED, "Could not start transcoding"), NULL); + return; + } else if (state_ret == GST_STATE_CHANGE_NO_PREROLL) { + self->is_live = TRUE; + GST_DEBUG_OBJECT (self, "Pipeline is live"); + } + + return; +} + +static gboolean +gst_transcoder_set_position_update_interval_internal (gpointer user_data) +{ + GstTranscoder *self = user_data; + + GST_OBJECT_LOCK (self); + + if (self->tick_source) { + remove_tick_source (self); + add_tick_source (self); + } + + GST_OBJECT_UNLOCK (self); + + return G_SOURCE_REMOVE; +} + +/** + * gst_transcoder_set_position_update_interval: + * @self: #GstTranscoder instance + * @interval: interval in ms + * + * Set interval in milliseconds between two position-updated signals. + * Pass 0 to stop updating the position. + */ +void +gst_transcoder_set_position_update_interval (GstTranscoder * self, + guint interval) +{ + g_return_if_fail (GST_IS_TRANSCODER (self)); + g_return_if_fail (interval <= 10000); + + GST_OBJECT_LOCK (self); + self->position_update_interval_ms = interval; + GST_OBJECT_UNLOCK (self); + + gst_transcoder_set_position_update_interval_internal (self); +} + +/** + * gst_transcoder_get_position_update_interval: + * @self: #GstTranscoder instance + * + * Returns: current position update interval in milliseconds + */ +guint +gst_transcoder_get_position_update_interval (GstTranscoder * self) +{ + g_return_val_if_fail (GST_IS_TRANSCODER (self), + DEFAULT_POSITION_UPDATE_INTERVAL_MS); + + return self->position_update_interval_ms; +} + +/** + * gst_transcoder_get_source_uri: + * @self: #GstTranscoder instance + * + * Gets the URI of the currently-transcoding stream. + * + * Returns: (transfer full): a string containing the URI of the + * source stream. g_free() after usage. + */ +gchar * +gst_transcoder_get_source_uri (GstTranscoder * self) +{ + gchar *val; + + g_return_val_if_fail (GST_IS_TRANSCODER (self), DEFAULT_URI); + + g_object_get (self, "src-uri", &val, NULL); + + return val; +} + +/** + * gst_transcoder_get_dest_uri: + * @self: #GstTranscoder instance + * + * Gets the URI of the destination of the transcoded stream. + * + * Returns: (transfer full): a string containing the URI of the + * destination of the transcoded stream. g_free() after usage. + */ +gchar * +gst_transcoder_get_dest_uri (GstTranscoder * self) +{ + gchar *val; + + g_return_val_if_fail (GST_IS_TRANSCODER (self), DEFAULT_URI); + + g_object_get (self, "dest-uri", &val, NULL); + + return val; +} + +/** + * gst_transcoder_get_position: + * @self: #GstTranscoder instance + * + * Returns: the absolute position time, in nanoseconds, of the + * transcoding stream. + */ +GstClockTime +gst_transcoder_get_position (GstTranscoder * self) +{ + GstClockTime val; + + g_return_val_if_fail (GST_IS_TRANSCODER (self), DEFAULT_POSITION); + + g_object_get (self, "position", &val, NULL); + + return val; +} + +/** + * gst_transcoder_get_duration: + * @self: #GstTranscoder instance + * + * Retrieves the duration of the media stream that self represents. + * + * Returns: the duration of the transcoding media stream, in + * nanoseconds. + */ +GstClockTime +gst_transcoder_get_duration (GstTranscoder * self) +{ + GstClockTime val; + + g_return_val_if_fail (GST_IS_TRANSCODER (self), DEFAULT_DURATION); + + g_object_get (self, "duration", &val, NULL); + + return val; +} + +/** + * gst_transcoder_get_pipeline: + * @self: #GstTranscoder instance + * + * Returns: (transfer full): The internal uritranscodebin instance + */ +GstElement * +gst_transcoder_get_pipeline (GstTranscoder * self) +{ + GstElement *val; + + g_return_val_if_fail (GST_IS_TRANSCODER (self), NULL); + + g_object_get (self, "pipeline", &val, NULL); + + return val; +} + +/** + * gst_transcoder_get_avoid_reencoding: + * @self: The #GstTranscoder to check whether reencoding is avoided or not. + * + * Returns: %TRUE if the transcoder tries to avoid reencoding streams where + * reencoding is not strictly needed, %FALSE otherwise. + */ +gboolean +gst_transcoder_get_avoid_reencoding (GstTranscoder * self) +{ + gboolean val; + + g_return_val_if_fail (GST_IS_TRANSCODER (self), FALSE); + + g_object_get (self->transcodebin, "avoid-reencoding", &val, NULL); + + return val; +} + +/** + * gst_transcoder_set_avoid_reencoding: + * @self: The #GstTranscoder to set whether reencoding should be avoided or not. + * @avoid_reencoding: %TRUE if the transcoder should try to avoid reencoding + * streams where * reencoding is not strictly needed, %FALSE otherwise. + */ +void +gst_transcoder_set_avoid_reencoding (GstTranscoder * self, + gboolean avoid_reencoding) +{ + g_return_if_fail (GST_IS_TRANSCODER (self)); + + g_object_set (self->transcodebin, "avoid-reencoding", avoid_reencoding, NULL); +} + +#define C_ENUM(v) ((gint) v) +#define C_FLAGS(v) ((guint) v) + +GType +gst_transcoder_error_get_type (void) +{ + static gsize id = 0; + static const GEnumValue values[] = { + {C_ENUM (GST_TRANSCODER_ERROR_FAILED), "GST_TRANSCODER_ERROR_FAILED", + "failed"}, + {0, NULL, NULL} + }; + + if (g_once_init_enter (&id)) { + GType tmp = g_enum_register_static ("GstTranscoderError", values); + g_once_init_leave (&id, tmp); + } + + return (GType) id; +} + +/** + * gst_transcoder_error_get_name: + * @error: a #GstTranscoderError + * + * Gets a string representing the given error. + * + * Returns: (transfer none): a string with the given error. + */ +const gchar * +gst_transcoder_error_get_name (GstTranscoderError error) +{ + switch (error) { + case GST_TRANSCODER_ERROR_FAILED: + return "failed"; + } + + g_assert_not_reached (); + return NULL; +} + +G_DEFINE_INTERFACE (GstTranscoderSignalDispatcher, + gst_transcoder_signal_dispatcher, G_TYPE_OBJECT); + +static void +gst_transcoder_signal_dispatcher_default_init (G_GNUC_UNUSED + GstTranscoderSignalDispatcherInterface * iface) +{ + +} + +static void +gst_transcoder_signal_dispatcher_dispatch (GstTranscoderSignalDispatcher * self, + GstTranscoder * transcoder, void (*emitter) (gpointer data), gpointer data, + GDestroyNotify destroy) +{ + GstTranscoderSignalDispatcherInterface *iface; + + if (!self) { + emitter (data); + if (destroy) + destroy (data); + return; + } + + g_return_if_fail (GST_IS_TRANSCODER_SIGNAL_DISPATCHER (self)); + iface = GST_TRANSCODER_SIGNAL_DISPATCHER_GET_INTERFACE (self); + g_return_if_fail (iface->dispatch != NULL); + + iface->dispatch (self, transcoder, emitter, data, destroy); +} + +struct _GstTranscoderGMainContextSignalDispatcher +{ + GObject parent; + GMainContext *application_context; +}; + +struct _GstTranscoderGMainContextSignalDispatcherClass +{ + GObjectClass parent_class; +}; + +static void + gst_transcoder_g_main_context_signal_dispatcher_interface_init + (GstTranscoderSignalDispatcherInterface * iface); + +enum +{ + G_MAIN_CONTEXT_SIGNAL_DISPATCHER_PROP_0, + G_MAIN_CONTEXT_SIGNAL_DISPATCHER_PROP_APPLICATION_CONTEXT, + G_MAIN_CONTEXT_SIGNAL_DISPATCHER_PROP_LAST +}; + +G_DEFINE_TYPE_WITH_CODE (GstTranscoderGMainContextSignalDispatcher, + gst_transcoder_g_main_context_signal_dispatcher, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (GST_TYPE_TRANSCODER_SIGNAL_DISPATCHER, + gst_transcoder_g_main_context_signal_dispatcher_interface_init)); + +static GParamSpec + * g_main_context_signal_dispatcher_param_specs + [G_MAIN_CONTEXT_SIGNAL_DISPATCHER_PROP_LAST] = { NULL, }; + +static void +gst_transcoder_g_main_context_signal_dispatcher_finalize (GObject * object) +{ + GstTranscoderGMainContextSignalDispatcher *self = + GST_TRANSCODER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER (object); + + if (self->application_context) + g_main_context_unref (self->application_context); + + G_OBJECT_CLASS + (gst_transcoder_g_main_context_signal_dispatcher_parent_class)->finalize + (object); +} + +static void +gst_transcoder_g_main_context_signal_dispatcher_set_property (GObject * object, + guint prop_id, const GValue * value, GParamSpec * pspec) +{ + GstTranscoderGMainContextSignalDispatcher *self = + GST_TRANSCODER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER (object); + + switch (prop_id) { + case G_MAIN_CONTEXT_SIGNAL_DISPATCHER_PROP_APPLICATION_CONTEXT: + self->application_context = g_value_dup_boxed (value); + if (!self->application_context) + self->application_context = g_main_context_ref_thread_default (); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_transcoder_g_main_context_signal_dispatcher_get_property (GObject * object, + guint prop_id, GValue * value, GParamSpec * pspec) +{ + GstTranscoderGMainContextSignalDispatcher *self = + GST_TRANSCODER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER (object); + + switch (prop_id) { + case G_MAIN_CONTEXT_SIGNAL_DISPATCHER_PROP_APPLICATION_CONTEXT: + g_value_set_boxed (value, self->application_context); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void + gst_transcoder_g_main_context_signal_dispatcher_class_init + (GstTranscoderGMainContextSignalDispatcherClass * klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->finalize = + gst_transcoder_g_main_context_signal_dispatcher_finalize; + gobject_class->set_property = + gst_transcoder_g_main_context_signal_dispatcher_set_property; + gobject_class->get_property = + gst_transcoder_g_main_context_signal_dispatcher_get_property; + + g_main_context_signal_dispatcher_param_specs + [G_MAIN_CONTEXT_SIGNAL_DISPATCHER_PROP_APPLICATION_CONTEXT] = + g_param_spec_boxed ("application-context", "Application Context", + "Application GMainContext to dispatch signals to", G_TYPE_MAIN_CONTEXT, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (gobject_class, + G_MAIN_CONTEXT_SIGNAL_DISPATCHER_PROP_LAST, + g_main_context_signal_dispatcher_param_specs); +} + +static void + gst_transcoder_g_main_context_signal_dispatcher_init + (G_GNUC_UNUSED GstTranscoderGMainContextSignalDispatcher * self) +{ +} + +typedef struct +{ + void (*emitter) (gpointer data); + gpointer data; + GDestroyNotify destroy; +} GMainContextSignalDispatcherData; + +static gboolean +g_main_context_signal_dispatcher_dispatch_gsourcefunc (gpointer user_data) +{ + GMainContextSignalDispatcherData *data = user_data; + + data->emitter (data->data); + + return G_SOURCE_REMOVE; +} + +static void +g_main_context_signal_dispatcher_dispatch_destroy (gpointer user_data) +{ + GMainContextSignalDispatcherData *data = user_data; + + if (data->destroy) + data->destroy (data->data); + g_free (data); +} + +/* *INDENT-OFF* */ +static void +gst_transcoder_g_main_context_signal_dispatcher_dispatch (GstTranscoderSignalDispatcher * iface, + G_GNUC_UNUSED GstTranscoder * transcoder, void (*emitter) (gpointer data), + gpointer data, GDestroyNotify destroy) +{ + GstTranscoderGMainContextSignalDispatcher *self = + GST_TRANSCODER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER (iface); + GMainContextSignalDispatcherData *gsourcefunc_data = + g_new0 (GMainContextSignalDispatcherData, 1); + + gsourcefunc_data->emitter = emitter; + gsourcefunc_data->data = data; + gsourcefunc_data->destroy = destroy; + + g_main_context_invoke_full (self->application_context, + G_PRIORITY_DEFAULT, g_main_context_signal_dispatcher_dispatch_gsourcefunc, + gsourcefunc_data, g_main_context_signal_dispatcher_dispatch_destroy); +} + +static void +gst_transcoder_g_main_context_signal_dispatcher_interface_init (GstTranscoderSignalDispatcherInterface * iface) +{ + iface->dispatch = gst_transcoder_g_main_context_signal_dispatcher_dispatch; +} +/* *INDENT-ON* */ + +/** + * gst_transcoder_g_main_context_signal_dispatcher_new: + * @application_context: (allow-none): GMainContext to use or %NULL + * + * Returns: (transfer full): + */ +GstTranscoderSignalDispatcher * +gst_transcoder_g_main_context_signal_dispatcher_new (GMainContext * + application_context) +{ + return g_object_new (GST_TYPE_TRANSCODER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER, + "application-context", application_context, NULL); +} diff --git a/gst-libs/gst/transcoder/gsttranscoder.h b/gst-libs/gst/transcoder/gsttranscoder.h new file mode 100644 index 0000000000..9cc74a4205 --- /dev/null +++ b/gst-libs/gst/transcoder/gsttranscoder.h @@ -0,0 +1,141 @@ +#ifndef __GST_TRANSCODER_H +#define __GST_TRANSCODER_H + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include "transcoder-prelude.h" + +G_BEGIN_DECLS + +typedef struct _GstTranscoderSignalDispatcher GstTranscoderSignalDispatcher; +typedef struct _GstTranscoderSignalDispatcherInterface GstTranscoderSignalDispatcherInterface; + +/*********** Error definitions ************/ +#define GST_TRANSCODER_ERROR (gst_transcoder_error_quark ()) +#define GST_TYPE_TRANSCODER_ERROR (gst_transcoder_error_get_type ()) + +/** + * GstTranscoderError: + * @GST_TRANSCODER_ERROR_FAILED: generic error. + */ +typedef enum { + GST_TRANSCODER_ERROR_FAILED = 0 +} GstTranscoderError; + +GST_TRANSCODER_API +GQuark gst_transcoder_error_quark (void); +GST_TRANSCODER_API +GType gst_transcoder_error_get_type (void); +GST_TRANSCODER_API +const gchar * gst_transcoder_error_get_name (GstTranscoderError error); + +/*********** GstTranscoder definition ************/ +#define GST_TYPE_TRANSCODER (gst_transcoder_get_type ()) +#define GST_TRANSCODER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_TRANSCODER, GstTranscoder)) +#define GST_TRANSCODER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_TRANSCODER, GstTranscoderClass)) +#define GST_IS_TRANSCODER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_TRANSCODER)) +#define GST_IS_TRANSCODER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_TRANSCODER)) +#define GST_TRANSCODER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_TRANSCODER, GstTranscoderClass)) + +typedef struct _GstTranscoder GstTranscoder; +typedef struct _GstTranscoderClass GstTranscoderClass; +typedef struct _GstTranscoderPrivate GstTranscoderPrivate; + +GST_TRANSCODER_API +GType gst_transcoder_get_type (void); + +GST_TRANSCODER_API +GstTranscoder * gst_transcoder_new (const gchar * source_uri, + const gchar * dest_uri, + const gchar * encoding_profile); + +GST_TRANSCODER_API +GstTranscoder * gst_transcoder_new_full (const gchar * source_uri, + const gchar * dest_uri, + GstEncodingProfile *profile, + GstTranscoderSignalDispatcher *signal_dispatcher); + +GST_TRANSCODER_API +gboolean gst_transcoder_run (GstTranscoder *self, + GError ** error); + +GST_TRANSCODER_API +void gst_transcoder_set_cpu_usage (GstTranscoder *self, + gint cpu_usage); + +GST_TRANSCODER_API +void gst_transcoder_run_async (GstTranscoder *self); + +GST_TRANSCODER_API +void gst_transcoder_set_position_update_interval (GstTranscoder *self, + guint interval); + +GST_TRANSCODER_API +gchar * gst_transcoder_get_source_uri (GstTranscoder * self); + +GST_TRANSCODER_API +gchar * gst_transcoder_get_dest_uri (GstTranscoder * self); + +GST_TRANSCODER_API +guint gst_transcoder_get_position_update_interval (GstTranscoder *self); + +GST_TRANSCODER_API +GstClockTime gst_transcoder_get_position (GstTranscoder * self); + +GST_TRANSCODER_API +GstClockTime gst_transcoder_get_duration (GstTranscoder * self); + +GST_TRANSCODER_API +GstElement * gst_transcoder_get_pipeline (GstTranscoder * self); + +GST_TRANSCODER_API +gboolean gst_transcoder_get_avoid_reencoding (GstTranscoder * self); +GST_TRANSCODER_API +void gst_transcoder_set_avoid_reencoding (GstTranscoder * self, + gboolean avoid_reencoding); + + +/****************** Signal dispatcher *******************************/ + +#define GST_TYPE_TRANSCODER_SIGNAL_DISPATCHER (gst_transcoder_signal_dispatcher_get_type ()) +#define GST_TRANSCODER_SIGNAL_DISPATCHER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_TRANSCODER_SIGNAL_DISPATCHER, GstTranscoderSignalDispatcher)) +#define GST_IS_TRANSCODER_SIGNAL_DISPATCHER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_TRANSCODER_SIGNAL_DISPATCHER)) +#define GST_TRANSCODER_SIGNAL_DISPATCHER_GET_INTERFACE(inst) (G_TYPE_INSTANCE_GET_INTERFACE ((inst), GST_TYPE_TRANSCODER_SIGNAL_DISPATCHER, GstTranscoderSignalDispatcherInterface)) + +struct _GstTranscoderSignalDispatcherInterface { + GTypeInterface parent_iface; + + void (*dispatch) (GstTranscoderSignalDispatcher * self, + GstTranscoder * transcoder, + void (*emitter) (gpointer data), + gpointer data, + GDestroyNotify destroy); +}; + +typedef struct _GstTranscoderGMainContextSignalDispatcher GstTranscoderGMainContextSignalDispatcher; +typedef struct _GstTranscoderGMainContextSignalDispatcherClass GstTranscoderGMainContextSignalDispatcherClass; + +GST_TRANSCODER_API +GType gst_transcoder_signal_dispatcher_get_type (void); + +#define GST_TYPE_TRANSCODER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER (gst_transcoder_g_main_context_signal_dispatcher_get_type ()) +#define GST_IS_TRANSCODER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_TRANSCODER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER)) +#define GST_IS_TRANSCODER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_TRANSCODER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER)) +#define GST_TRANSCODER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_TRANSCODER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER, GstTranscoderGMainContextSignalDispatcherClass)) +#define GST_TRANSCODER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_TRANSCODER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER, GstTranscoderGMainContextSignalDispatcher)) +#define GST_TRANSCODER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_TRANSCODER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER, GstTranscoderGMainContextSignalDispatcherClass)) +#define GST_TRANSCODER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER_CAST(obj) ((GstTranscoderGMainContextSignalDispatcher*)(obj)) + +GST_TRANSCODER_API +GType gst_transcoder_g_main_context_signal_dispatcher_get_type (void); + +GST_TRANSCODER_API +GstTranscoderSignalDispatcher * gst_transcoder_g_main_context_signal_dispatcher_new (GMainContext * application_context); + +G_END_DECLS + +#endif diff --git a/gst-libs/gst/transcoder/meson.build b/gst-libs/gst/transcoder/meson.build new file mode 100644 index 0000000000..47076d417c --- /dev/null +++ b/gst-libs/gst/transcoder/meson.build @@ -0,0 +1,33 @@ +sources = files(['gsttranscoder.c']) +headers = files(['gsttranscoder.h', 'transcoder-prelude.h']) + +install_headers(headers, subdir : 'gstreamer-' + api_version + '/gst/transcoder') + +gst_transcoder = library('gsttranscoder-' + api_version, + sources, + install: true, + include_directories : [configinc, libsinc], + dependencies: [gst_dep, gstpbutils_dep], + c_args: gst_plugins_bad_args + ['-DGST_USE_UNSTABLE_API', '-DBUILDING_GST_TRANSCODER'], + soversion : soversion, +) +if build_gir + transcoder_gir = gnome.generate_gir(gst_transcoder, + sources : sources + headers, + nsversion : api_version, + namespace : 'GstTranscoder', + identifier_prefix : 'Gst', + symbol_prefix : 'gst_', + includes : ['GObject-2.0', + 'Gst-' + api_version, + 'GstPbutils-' + api_version], + dependencies: [gst_dep, gstpbutils_dep], + install : true, + extra_args : ['--add-init-section=extern gboolean gst_init(gint *argc, gchar **argv); gst_init(NULL,NULL);'] + ) +endif + +gst_transcoder_dep = declare_dependency(link_with: gst_transcoder, + dependencies : [gst_dep, gstpbutils_dep], + include_directories : [libsinc] +) \ No newline at end of file diff --git a/gst-libs/gst/transcoder/transcoder-prelude.h b/gst-libs/gst/transcoder/transcoder-prelude.h new file mode 100644 index 0000000000..ba153dd164 --- /dev/null +++ b/gst-libs/gst/transcoder/transcoder-prelude.h @@ -0,0 +1,36 @@ +/* GStreamer Transcoder Library + * + * Copyright (C) 2019 Thibault Saunier + * + * transcoder-prelude.h: prelude include header for the gst-transcoder library + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GST_TRANSCODER_PRELUDE_H__ +#define __GST_TRANSCODER_PRELUDE_H__ + +#include + +#ifndef GST_TRANSCODER_API +# ifdef BUILDING_GST_TRANSCODER +# define GST_TRANSCODER_API GST_API_EXPORT /* from config.h */ +# else +# define GST_TRANSCODER_API GST_API_IMPORT +# endif +#endif + +#endif /* __GST_TRANSCODER_PRELUDE_H__ */ diff --git a/gst/meson.build b/gst/meson.build index 3266c6e203..2197aec954 100644 --- a/gst/meson.build +++ b/gst/meson.build @@ -9,7 +9,7 @@ foreach plugin : ['accurip', 'adpcmdec', 'adpcmenc', 'aiff', 'asfmux', 'midi', 'mpegdemux', 'mpegpsmux', 'mpegtsdemux', 'mpegtsmux', 'mxf', 'netsim', 'onvif', 'pcapparse', 'pnm', 'proxy', 'rawparse', 'removesilence', 'rist', 'rtp', 'sdp', 'segmentclip', - 'siren', 'smooth', 'speed', 'subenc', 'timecode', + 'siren', 'smooth', 'speed', 'subenc', 'timecode', 'transcode', 'videofilters', 'videoframe_audiolevel', 'videoparsers', 'videosignal', 'vmnc', 'y4m', 'yadif'] if not get_option(plugin).disabled() diff --git a/gst/transcode/gst-cpu-throttling-clock.c b/gst/transcode/gst-cpu-throttling-clock.c new file mode 100644 index 0000000000..45bb51a12f --- /dev/null +++ b/gst/transcode/gst-cpu-throttling-clock.c @@ -0,0 +1,220 @@ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_GETRUSAGE +#include "gst-cpu-throttling-clock.h" + +#include +#include + +#include "gst-cpu-throttling-clock.h" + +/** + * SECTION: gst-cpu-throttling-clock + * @title: GstCpuThrottlingClock + * @short_description: TODO + * + * TODO + */ + +/* *INDENT-OFF* */ +GST_DEBUG_CATEGORY_STATIC (gst_cpu_throttling_clock_debug); +#define GST_CAT_DEFAULT gst_cpu_throttling_clock_debug + +struct _GstCpuThrottlingClockPrivate +{ + guint wanted_cpu_usage; + + GstClock *sclock; + GstClockTime current_wait_time; + GstPoll *timer; + struct rusage last_usage; + + GstClockID evaluate_wait_time; + GstClockTime time_between_evals; +}; + +#define parent_class gst_cpu_throttling_clock_parent_class +G_DEFINE_TYPE_WITH_CODE (GstCpuThrottlingClock, gst_cpu_throttling_clock, GST_TYPE_CLOCK, G_ADD_PRIVATE(GstCpuThrottlingClock)) + +enum +{ + PROP_FIRST, + PROP_CPU_USAGE, + PROP_LAST +}; + +static GParamSpec *param_specs[PROP_LAST] = { NULL, }; +/* *INDENT-ON* */ + +static void +gst_cpu_throttling_clock_get_property (GObject * object, + guint property_id, GValue * value, GParamSpec * pspec) +{ + GstCpuThrottlingClock *self = GST_CPU_THROTTLING_CLOCK (object); + + switch (property_id) { + case PROP_CPU_USAGE: + g_value_set_uint (value, self->priv->wanted_cpu_usage); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gst_cpu_throttling_clock_set_property (GObject * object, + guint property_id, const GValue * value, GParamSpec * pspec) +{ + GstCpuThrottlingClock *self = GST_CPU_THROTTLING_CLOCK (object); + + switch (property_id) { + case PROP_CPU_USAGE: + self->priv->wanted_cpu_usage = g_value_get_uint (value); + if (self->priv->wanted_cpu_usage == 0) + self->priv->wanted_cpu_usage = 100; + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static gboolean +gst_transcoder_adjust_wait_time (GstClock * sync_clock, GstClockTime time, + GstClockID id, GstCpuThrottlingClock * self) +{ + struct rusage ru; + float delta_usage, usage, coef; + + GstCpuThrottlingClockPrivate *priv = self->priv; + + getrusage (RUSAGE_SELF, &ru); + delta_usage = GST_TIMEVAL_TO_TIME (ru.ru_utime) - + GST_TIMEVAL_TO_TIME (self->priv->last_usage.ru_utime); + usage = + ((float) delta_usage / self->priv->time_between_evals * 100) / + g_get_num_processors (); + + self->priv->last_usage = ru; + + coef = GST_MSECOND / 10; + if (usage < (gfloat) priv->wanted_cpu_usage) { + coef = -coef; + } + + priv->current_wait_time = CLAMP (0, + (GstClockTime) priv->current_wait_time + coef, GST_SECOND); + + GST_DEBUG_OBJECT (self, + "Avg is %f (wanted %d) => %" GST_TIME_FORMAT, usage, + self->priv->wanted_cpu_usage, GST_TIME_ARGS (priv->current_wait_time)); + + return TRUE; +} + +static GstClockReturn +_wait (GstClock * clock, GstClockEntry * entry, GstClockTimeDiff * jitter) +{ + GstCpuThrottlingClock *self = GST_CPU_THROTTLING_CLOCK (clock); + + if (!self->priv->evaluate_wait_time) { + if (!(self->priv->sclock)) { + GST_ERROR_OBJECT (clock, "Could not find any system clock" + " to start the wait time evaluation task"); + } else { + self->priv->evaluate_wait_time = + gst_clock_new_periodic_id (self->priv->sclock, + gst_clock_get_time (self->priv->sclock), + self->priv->time_between_evals); + + gst_clock_id_wait_async (self->priv->evaluate_wait_time, + (GstClockCallback) gst_transcoder_adjust_wait_time, + (gpointer) self, NULL); + } + } + + if (G_UNLIKELY (GST_CLOCK_ENTRY_STATUS (entry) == GST_CLOCK_UNSCHEDULED)) + return GST_CLOCK_UNSCHEDULED; + + if (gst_poll_wait (self->priv->timer, self->priv->current_wait_time)) { + GST_INFO_OBJECT (self, "Something happened on the poll"); + } + + return GST_CLOCK_ENTRY_STATUS (entry); +} + +static GstClockTime +_get_internal_time (GstClock * clock) +{ + GstCpuThrottlingClock *self = GST_CPU_THROTTLING_CLOCK (clock); + + return gst_clock_get_internal_time (self->priv->sclock); +} + +static void +gst_cpu_throttling_clock_dispose (GObject * object) +{ + GstCpuThrottlingClock *self = GST_CPU_THROTTLING_CLOCK (object); + + if (self->priv->evaluate_wait_time) { + gst_clock_id_unschedule (self->priv->evaluate_wait_time); + gst_clock_id_unref (self->priv->evaluate_wait_time); + self->priv->evaluate_wait_time = 0; + } +} + +static void +gst_cpu_throttling_clock_class_init (GstCpuThrottlingClockClass * klass) +{ + GObjectClass *oclass = G_OBJECT_CLASS (klass); + GstClockClass *clock_klass = GST_CLOCK_CLASS (klass); + + GST_DEBUG_CATEGORY_INIT (gst_cpu_throttling_clock_debug, "cpuclock", 0, + "UriTranscodebin element"); + + oclass->get_property = gst_cpu_throttling_clock_get_property; + oclass->set_property = gst_cpu_throttling_clock_set_property; + oclass->dispose = gst_cpu_throttling_clock_dispose; + + /** + * GstCpuThrottlingClock:cpu-usage: + * + * Since: UNRELEASED + */ + param_specs[PROP_CPU_USAGE] = g_param_spec_uint ("cpu-usage", "cpu-usage", + "The percentage of CPU to try to use with the processus running the " + "pipeline driven by the clock", 0, 100, + 100, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (oclass, PROP_LAST, param_specs); + + clock_klass->wait = GST_DEBUG_FUNCPTR (_wait); + clock_klass->get_internal_time = _get_internal_time; +} + +static void +gst_cpu_throttling_clock_init (GstCpuThrottlingClock * self) +{ + self->priv = gst_cpu_throttling_clock_get_instance_private (self); + + self->priv->current_wait_time = GST_MSECOND; + self->priv->wanted_cpu_usage = 100; + self->priv->timer = gst_poll_new_timer (); + self->priv->time_between_evals = GST_SECOND / 4; + self->priv->sclock = GST_CLOCK (gst_system_clock_obtain ()); + + + getrusage (RUSAGE_SELF, &self->priv->last_usage); +} + +GstCpuThrottlingClock * +gst_cpu_throttling_clock_new (guint cpu_usage) +{ + return g_object_new (GST_TYPE_CPU_THROTTLING_CLOCK, "cpu-usage", + cpu_usage, NULL); +} +#endif diff --git a/gst/transcode/gst-cpu-throttling-clock.h b/gst/transcode/gst-cpu-throttling-clock.h new file mode 100644 index 0000000000..e946c1af24 --- /dev/null +++ b/gst/transcode/gst-cpu-throttling-clock.h @@ -0,0 +1,60 @@ +/* + * gst-cpu-throttling-clock.h + * + * Copyright (C) 2015 Thibault Saunier + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + + + +#ifndef __GST_CPU_THROTTLING_CLOCK_H__ +#define __GST_CPU_THROTTLING_CLOCK_H__ + +#include +#include + +G_BEGIN_DECLS + +typedef struct _GstCpuThrottlingClock GstCpuThrottlingClock; +typedef struct _GstCpuThrottlingClockClass GstCpuThrottlingClockClass; +typedef struct _GstCpuThrottlingClockPrivate GstCpuThrottlingClockPrivate; + +GType gst_cpu_throttling_clock_get_type (void) G_GNUC_CONST; + +#define GST_TYPE_CPU_THROTTLING_CLOCK (gst_cpu_throttling_clock_get_type ()) +#define GST_CPU_THROTTLING_CLOCK(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_CPU_THROTTLING_CLOCK, GstCpuThrottlingClock)) +#define GST_CPU_THROTTLING_CLOCK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_CPU_THROTTLING_CLOCK, GstCpuThrottlingClockClass)) +#define GST_IS_CPU_THROTTLING_CLOCK(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_CPU_THROTTLING_CLOCK)) +#define GST_IS_CPU_THROTTLING_CLOCK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_CPU_THROTTLING_CLOCK)) +#define GST_CPU_THROTTLING_CLOCK_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_CPU_THROTTLING_CLOCK, GstCpuThrottlingClockClass)) + +struct _GstCpuThrottlingClockClass +{ + /**/ + GstClockClass parent_class; +}; + +struct _GstCpuThrottlingClock +{ + /**/ + GstClock parent; + GstCpuThrottlingClockPrivate *priv; +}; + +GstCpuThrottlingClock * gst_cpu_throttling_clock_new (guint cpu_usage); + +G_END_DECLS + +#endif /* #ifndef __GST_CPU_THROTTLING_CLOCK_H__*/ diff --git a/gst/transcode/gsttranscodebin.c b/gst/transcode/gsttranscodebin.c new file mode 100644 index 0000000000..51812f41d2 --- /dev/null +++ b/gst/transcode/gsttranscodebin.c @@ -0,0 +1,614 @@ +/* GStreamer + * Copyright (C) 2019 Thibault Saunier + * + * gsttranscodebin.c: + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "gsttranscoding.h" +#include + +#include + +GST_DEBUG_CATEGORY_STATIC (gst_transcodebin_debug); +#define GST_CAT_DEFAULT gst_transcodebin_debug + +static GstStaticPadTemplate transcode_bin_sink_template = +GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY); + +static GstStaticPadTemplate transcode_bin_src_template = +GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY); + +typedef struct +{ + GstBin parent; + + GstElement *decodebin; + GstElement *encodebin; + + GstEncodingProfile *profile; + gboolean avoid_reencoding; + GstPad *sinkpad; + GstPad *srcpad; + + GstElement *audio_filter; + GstElement *video_filter; +} GstTranscodeBin; + +typedef struct +{ + GstBinClass parent; + +} GstTranscodeBinClass; + +/* *INDENT-OFF* */ +#define GST_TYPE_TRANSCODE_BIN (gst_transcode_bin_get_type ()) +#define GST_TRANSCODE_BIN(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_TRANSCODE_BIN, GstTranscodeBin)) +#define GST_TRANSCODE_BIN_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TRANSCODE_BIN_TYPE, GstTranscodeBinClass)) +#define GST_IS_TRANSCODE_BIN(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TRANSCODE_BIN_TYPE)) +#define GST_IS_TRANSCODE_BIN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TRANSCODE_BIN_TYPE)) +#define GST_TRANSCODE_BIN_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TRANSCODE_BIN_TYPE, GstTranscodeBinClass)) + +#define DEFAULT_AVOID_REENCODING FALSE + +G_DEFINE_TYPE (GstTranscodeBin, gst_transcode_bin, GST_TYPE_BIN) +enum +{ + PROP_0, + PROP_PROFILE, + PROP_AVOID_REENCODING, + PROP_VIDEO_FILTER, + PROP_AUDIO_FILTER, + LAST_PROP +}; + +static void +post_missing_plugin_error (GstElement * dec, const gchar * element_name) +{ + GstMessage *msg; + + msg = gst_missing_element_message_new (dec, element_name); + gst_element_post_message (dec, msg); + + GST_ELEMENT_ERROR (dec, CORE, MISSING_PLUGIN, + ("Missing element '%s' - check your GStreamer installation.", + element_name), (NULL)); +} +/* *INDENT-ON* */ + +static GstPad * +_insert_filter (GstTranscodeBin * self, GstPad * sinkpad, GstPad * pad, + GstCaps * caps) +{ + GstPad *filter_src = NULL, *filter_sink = NULL; + GstElement *filter = NULL; + GstObject *filter_parent; + + if (self->video_filter && + !g_strcmp0 (gst_structure_get_name (gst_caps_get_structure (caps, 0)), + "video/x-raw")) { + filter = self->video_filter; + } else if (self->audio_filter && + !g_strcmp0 (gst_structure_get_name (gst_caps_get_structure (caps, 0)), + "audio/x-raw")) { + filter = self->audio_filter; + } + + if (!filter) + return pad; + + if ((filter_parent = gst_object_get_parent (GST_OBJECT (filter)))) { + GST_WARNING_OBJECT (self, + "Filter already in use (inside %" GST_PTR_FORMAT ").", filter_parent); + GST_FIXME_OBJECT (self, + "Handle transcoding several streams of a same kind."); + gst_object_unref (filter_parent); + + return pad; + } + + /* We are guaranteed filters only have 1 unique sinkpad and srcpad */ + GST_OBJECT_LOCK (filter); + filter_sink = filter->sinkpads->data; + filter_src = filter->srcpads->data; + GST_OBJECT_UNLOCK (filter); + + gst_bin_add (GST_BIN (self), gst_object_ref (filter)); + if (G_UNLIKELY (gst_pad_link (pad, filter_sink) != GST_PAD_LINK_OK)) { + GstCaps *othercaps = gst_pad_get_current_caps (sinkpad); + caps = gst_pad_get_current_caps (pad); + + GST_ELEMENT_ERROR (self, CORE, PAD, + (NULL), + ("Couldn't link pads \n\n%" GST_PTR_FORMAT "\n\n and \n\n %" + GST_PTR_FORMAT "\n\n", caps, othercaps)); + + gst_caps_unref (caps); + gst_caps_unref (othercaps); + } + + gst_element_sync_state_with_parent (filter); + + return filter_src; +} + +static void +pad_added_cb (GstElement * decodebin, GstPad * pad, GstTranscodeBin * self) +{ + GstCaps *caps; + GstPad *sinkpad = NULL; + GstPadLinkReturn lret; + + caps = gst_pad_query_caps (pad, NULL); + + GST_DEBUG_OBJECT (decodebin, "Pad added, caps: %" GST_PTR_FORMAT, caps); + + g_signal_emit_by_name (self->encodebin, "request-pad", caps, &sinkpad); + + if (sinkpad == NULL) { + gchar *stream_id = gst_pad_get_stream_id (pad); + + GST_ELEMENT_WARNING_WITH_DETAILS (self, STREAM, FORMAT, + (NULL), ("Stream with caps: %" GST_PTR_FORMAT " can not be" + " encoded in the defined encoding formats", + caps), + ("can-t-encode-stream", G_TYPE_BOOLEAN, TRUE, + "stream-caps", GST_TYPE_CAPS, caps, + "stream-id", G_TYPE_STRING, stream_id, NULL)); + + g_free (stream_id); + return; + } + + if (caps) + gst_caps_unref (caps); + + pad = _insert_filter (self, sinkpad, pad, caps); + lret = gst_pad_link (pad, sinkpad); + switch (lret) { + case GST_PAD_LINK_OK: + break; + case GST_PAD_LINK_WAS_LINKED: + GST_FIXME_OBJECT (self, "Pad %" GST_PTR_FORMAT " was already linked", + sinkpad); + break; + default: + { + GstCaps *othercaps = gst_pad_query_caps (sinkpad, NULL); + caps = gst_pad_get_current_caps (pad); + + GST_ELEMENT_ERROR_WITH_DETAILS (self, CORE, PAD, + (NULL), + ("Couldn't link pads:\n %" GST_PTR_FORMAT ": %" GST_PTR_FORMAT + "\nand:\n" + " %" GST_PTR_FORMAT ": %" GST_PTR_FORMAT "\n\n", + pad, caps, sinkpad, othercaps), + ("linking-error", GST_TYPE_PAD_LINK_RETURN, lret, + "source-pad", GST_TYPE_PAD, pad, + "source-caps", GST_TYPE_CAPS, caps, + "sink-pad", GST_TYPE_PAD, sinkpad, + "sink-caps", GST_TYPE_CAPS, othercaps, NULL)); + + gst_caps_unref (caps); + if (othercaps) + gst_caps_unref (othercaps); + } + } + + gst_object_unref (sinkpad); +} + +static gboolean +make_encodebin (GstTranscodeBin * self) +{ + GstPad *pad; + GST_INFO_OBJECT (self, "making new encodebin"); + + if (!self->profile) + goto no_profile; + + self->encodebin = gst_element_factory_make ("encodebin", NULL); + if (!self->encodebin) + goto no_encodebin; + + gst_bin_add (GST_BIN (self), self->encodebin); + g_object_set (self->encodebin, "profile", self->profile, NULL); + + pad = gst_element_get_static_pad (self->encodebin, "src"); + if (!gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->srcpad), pad)) { + + gst_object_unref (pad); + GST_ERROR_OBJECT (self, "Could not ghost %" GST_PTR_FORMAT " srcpad", + self->encodebin); + + return FALSE; + } + gst_object_unref (pad); + + return gst_element_sync_state_with_parent (self->encodebin); + + /* ERRORS */ +no_encodebin: + { + post_missing_plugin_error (GST_ELEMENT_CAST (self), "encodebin"); + + GST_ELEMENT_ERROR (self, CORE, MISSING_PLUGIN, (NULL), + ("No encodebin element, check your installation")); + + return FALSE; + } + /* ERRORS */ +no_profile: + { + GST_ELEMENT_ERROR (self, CORE, MISSING_PLUGIN, (NULL), + ("No GstEncodingProfile set, can not run.")); + + return FALSE; + } +} + +static gboolean +make_decodebin (GstTranscodeBin * self) +{ + GstPad *pad; + GST_INFO_OBJECT (self, "making new decodebin"); + + self->decodebin = gst_element_factory_make ("decodebin", NULL); + + if (!self->decodebin) + goto no_decodebin; + + if (self->avoid_reencoding) { + GstCaps *decodecaps; + + g_object_get (self->decodebin, "caps", &decodecaps, NULL); + if (GST_IS_ENCODING_CONTAINER_PROFILE (self->profile)) { + GList *tmp; + + decodecaps = gst_caps_make_writable (decodecaps); + for (tmp = (GList *) + gst_encoding_container_profile_get_profiles + (GST_ENCODING_CONTAINER_PROFILE (self->profile)); tmp; + tmp = tmp->next) { + GstCaps *encodecaps = gst_encoding_profile_get_format (tmp->data); + GstCaps *restrictions = + gst_encoding_profile_get_restriction (tmp->data); + + if (!restrictions) + gst_caps_append (decodecaps, encodecaps); + else + gst_caps_unref (restrictions); + } + } + g_object_set (self->decodebin, "caps", decodecaps, NULL); + gst_caps_unref (decodecaps); + } + + g_signal_connect (self->decodebin, "pad-added", G_CALLBACK (pad_added_cb), + self); + + gst_bin_add (GST_BIN (self), self->decodebin); + pad = gst_element_get_static_pad (self->decodebin, "sink"); + if (!gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->sinkpad), pad)) { + + gst_object_unref (pad); + GST_ERROR_OBJECT (self, "Could not ghost %" GST_PTR_FORMAT " sinkpad", + self->decodebin); + + return FALSE; + } + + gst_object_unref (pad); + return TRUE; + + /* ERRORS */ +no_decodebin: + { + post_missing_plugin_error (GST_ELEMENT_CAST (self), "decodebin"); + GST_ELEMENT_ERROR (self, CORE, MISSING_PLUGIN, (NULL), + ("No decodebin element, check your installation")); + + return FALSE; + } +} + +static void +remove_all_children (GstTranscodeBin * self) +{ + if (self->encodebin) { + gst_element_set_state (self->encodebin, GST_STATE_NULL); + gst_bin_remove (GST_BIN (self), self->encodebin); + self->encodebin = NULL; + } + + if (self->video_filter && GST_OBJECT_PARENT (self->video_filter)) { + gst_element_set_state (self->video_filter, GST_STATE_NULL); + gst_bin_remove (GST_BIN (self), self->video_filter); + } + + if (self->audio_filter && GST_OBJECT_PARENT (self->audio_filter)) { + gst_element_set_state (self->audio_filter, GST_STATE_NULL); + gst_bin_remove (GST_BIN (self), self->audio_filter); + } + + if (self->decodebin) { + gst_element_set_state (self->decodebin, GST_STATE_NULL); + gst_bin_remove (GST_BIN (self), self->decodebin); + self->decodebin = NULL; + } +} + +static GstStateChangeReturn +gst_transcode_bin_change_state (GstElement * element, GstStateChange transition) +{ + GstStateChangeReturn ret; + GstTranscodeBin *self = GST_TRANSCODE_BIN (element); + + switch (transition) { + case GST_STATE_CHANGE_READY_TO_PAUSED: + + if (!make_encodebin (self)) + goto setup_failed; + + if (!make_decodebin (self)) + goto setup_failed; + + break; + default: + break; + } + + ret = + GST_ELEMENT_CLASS (gst_transcode_bin_parent_class)->change_state (element, + transition); + if (ret == GST_STATE_CHANGE_FAILURE) + goto beach; + + switch (transition) { + case GST_STATE_CHANGE_PAUSED_TO_READY: + remove_all_children (self); + break; + default: + break; + } + +beach: + return ret; + +setup_failed: + remove_all_children (self); + return GST_STATE_CHANGE_FAILURE; +} + +static void +gst_transcode_bin_dispose (GObject * object) +{ + GstTranscodeBin *self = (GstTranscodeBin *) object; + + g_clear_object (&self->video_filter); + g_clear_object (&self->audio_filter); + + G_OBJECT_CLASS (gst_transcode_bin_parent_class)->dispose (object); +} + +static void +gst_transcode_bin_get_property (GObject * object, + guint prop_id, GValue * value, GParamSpec * pspec) +{ + GstTranscodeBin *self = GST_TRANSCODE_BIN (object); + + switch (prop_id) { + case PROP_PROFILE: + GST_OBJECT_LOCK (self); + g_value_set_object (value, self->profile); + GST_OBJECT_UNLOCK (self); + break; + case PROP_AVOID_REENCODING: + GST_OBJECT_LOCK (self); + g_value_set_boolean (value, self->avoid_reencoding); + GST_OBJECT_UNLOCK (self); + break; + case PROP_AUDIO_FILTER: + GST_OBJECT_LOCK (self); + g_value_set_object (value, self->audio_filter); + GST_OBJECT_UNLOCK (self); + break; + case PROP_VIDEO_FILTER: + GST_OBJECT_LOCK (self); + g_value_set_object (value, self->video_filter); + GST_OBJECT_UNLOCK (self); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +_set_filter (GstTranscodeBin * self, GstElement * filter, GstElement ** mfilter) +{ + if (filter) { + GST_OBJECT_LOCK (filter); + if (filter->numsinkpads != 1) { + GST_ERROR_OBJECT (self, "Can not use %" GST_PTR_FORMAT + " as filter as it does not have " + " one and only one sinkpad", filter); + goto bail_out; + } else if (filter->numsrcpads != 1) { + GST_ERROR_OBJECT (self, "Can not use %" GST_PTR_FORMAT + " as filter as it does not have " " one and only one srcpad", filter); + goto bail_out; + } + GST_OBJECT_UNLOCK (filter); + } + + GST_OBJECT_LOCK (self); + *mfilter = filter; + GST_OBJECT_UNLOCK (self); + + return; + +bail_out: + GST_OBJECT_UNLOCK (filter); +} + +static void +gst_transcode_bin_set_property (GObject * object, + guint prop_id, const GValue * value, GParamSpec * pspec) +{ + GstTranscodeBin *self = GST_TRANSCODE_BIN (object); + + switch (prop_id) { + case PROP_PROFILE: + GST_OBJECT_LOCK (self); + self->profile = g_value_dup_object (value); + GST_OBJECT_UNLOCK (self); + break; + case PROP_AVOID_REENCODING: + GST_OBJECT_LOCK (self); + self->avoid_reencoding = g_value_get_boolean (value); + GST_OBJECT_UNLOCK (self); + break; + case PROP_AUDIO_FILTER: + _set_filter (self, g_value_dup_object (value), &self->audio_filter); + break; + case PROP_VIDEO_FILTER: + _set_filter (self, g_value_dup_object (value), &self->video_filter); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gst_transcode_bin_class_init (GstTranscodeBinClass * klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GstElementClass *gstelement_klass; + + object_class->dispose = gst_transcode_bin_dispose; + object_class->get_property = gst_transcode_bin_get_property; + object_class->set_property = gst_transcode_bin_set_property; + + gstelement_klass = (GstElementClass *) klass; + gstelement_klass->change_state = + GST_DEBUG_FUNCPTR (gst_transcode_bin_change_state); + + gst_element_class_add_pad_template (gstelement_klass, + gst_static_pad_template_get (&transcode_bin_sink_template)); + gst_element_class_add_pad_template (gstelement_klass, + gst_static_pad_template_get (&transcode_bin_src_template)); + + gst_element_class_set_static_metadata (gstelement_klass, + "Transcode Bin", "Generic/Bin/Encoding", + "Autoplug and transcoder a stream", + "Thibault Saunier "); + + /** + * GstTranscodeBin:profile: + * + * The #GstEncodingProfile to use. This property must be set before going + * to %GST_STATE_PAUSED or higher. + */ + g_object_class_install_property (object_class, PROP_PROFILE, + g_param_spec_object ("profile", "Profile", + "The GstEncodingProfile to use", GST_TYPE_ENCODING_PROFILE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstTranscodeBin:avoid-reencoding: + * + * See #encodebin:avoid-reencoding + */ + g_object_class_install_property (object_class, PROP_AVOID_REENCODING, + g_param_spec_boolean ("avoid-reencoding", "Avoid re-encoding", + "Whether to re-encode portions of compatible video streams that lay on segment boundaries", + DEFAULT_AVOID_REENCODING, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstTranscodeBin:video-filter: + * + * Set the video filter element/bin to use. + */ + g_object_class_install_property (object_class, PROP_VIDEO_FILTER, + g_param_spec_object ("video-filter", "Video filter", + "the video filter(s) to apply, if possible", + GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + /** + * GstTranscodeBin:audio-filter: + * + * Set the audio filter element/bin to use. + */ + g_object_class_install_property (object_class, PROP_AUDIO_FILTER, + g_param_spec_object ("audio-filter", "Audio filter", + "the audio filter(s) to apply, if possible", + GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); +} + +static void +gst_transcode_bin_init (GstTranscodeBin * self) +{ + GstPadTemplate *pad_tmpl; + + pad_tmpl = gst_static_pad_template_get (&transcode_bin_sink_template); + self->sinkpad = gst_ghost_pad_new_no_target_from_template ("sink", pad_tmpl); + gst_pad_set_active (self->sinkpad, TRUE); + gst_element_add_pad (GST_ELEMENT (self), self->sinkpad); + + gst_object_unref (pad_tmpl); + + pad_tmpl = gst_static_pad_template_get (&transcode_bin_src_template); + + self->srcpad = gst_ghost_pad_new_no_target_from_template ("src", pad_tmpl); + gst_pad_set_active (self->srcpad, TRUE); + gst_element_add_pad (GST_ELEMENT (self), self->srcpad); + + gst_object_unref (pad_tmpl); +} + +static gboolean +plugin_init (GstPlugin * plugin) +{ + gboolean res = TRUE; + gst_pb_utils_init (); + + GST_DEBUG_CATEGORY_INIT (gst_transcodebin_debug, "transcodebin", 0, + "Transcodebin element"); + + res &= gst_element_register (plugin, "transcodebin", GST_RANK_NONE, + GST_TYPE_TRANSCODE_BIN); + + res &= gst_element_register (plugin, "uritranscodebin", GST_RANK_NONE, + gst_uri_transcode_bin_get_type ()); + + return res; +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + transcode, + "A plugin containing elements for transcoding", plugin_init, VERSION, + GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN) diff --git a/gst/transcode/gsttranscoding.h b/gst/transcode/gsttranscoding.h new file mode 100644 index 0000000000..0e7f29c709 --- /dev/null +++ b/gst/transcode/gsttranscoding.h @@ -0,0 +1,31 @@ +/* GStreamer + * Copyright (C) 2015 Thibault Saunier + * + * gsttranscodebin.c: + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GST_TRANSCODING_H__ +#define __GST_TRANSCODING_H__ + + +#include + +GType gst_transcode_bin_get_type (void); +GType gst_uri_transcode_bin_get_type (void); + +#endif /* __GST_TRANSCODING_H__ */ diff --git a/gst/transcode/gsturitranscodebin.c b/gst/transcode/gsturitranscodebin.c new file mode 100644 index 0000000000..9b74ae469d --- /dev/null +++ b/gst/transcode/gsturitranscodebin.c @@ -0,0 +1,562 @@ +/* GStreamer + * Copyright (C) 2019 Thibault Saunier + * + * gsturitranscodebin.c: + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "gsttranscoding.h" +#if HAVE_GETRUSAGE +#include "gst-cpu-throttling-clock.h" +#endif +#include + +#include + +GST_DEBUG_CATEGORY_STATIC (gst_uri_transcodebin_debug); +#define GST_CAT_DEFAULT gst_uri_transcodebin_debug + +typedef struct +{ + GstPipeline parent; + + GstElement *src; + gchar *source_uri; + + GstElement *transcodebin; + + GstElement *audio_filter; + GstElement *video_filter; + + GstEncodingProfile *profile; + gboolean avoid_reencoding; + guint wanted_cpu_usage; + + GstElement *sink; + gchar *dest_uri; + + GstClock *cpu_clock; + +} GstUriTranscodeBin; + +typedef struct +{ + GstPipelineClass parent; + +} GstUriTranscodeBinClass; + +/* *INDENT-OFF* */ +#define parent_class gst_uri_transcode_bin_parent_class +#define GST_TYPE_URI_TRANSCODE_BIN (gst_uri_transcode_bin_get_type ()) +#define GST_URI_TRANSCODE_BIN(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_URI_TRANSCODE_BIN, GstUriTranscodeBin)) +#define GST_URI_TRANSCODE_BIN_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_URI_TRANSCODE_BIN_TYPE, GstUriTranscodeBinClass)) +#define GST_IS_TRANSCODE_BIN(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_URI_TRANSCODE_BIN_TYPE)) +#define GST_IS_TRANSCODE_BIN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_URI_TRANSCODE_BIN_TYPE)) +#define GST_URI_TRANSCODE_BIN_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_URI_TRANSCODE_BIN_TYPE, GstUriTranscodeBinClass)) + +#define DEFAULT_AVOID_REENCODING FALSE + +G_DEFINE_TYPE (GstUriTranscodeBin, gst_uri_transcode_bin, GST_TYPE_PIPELINE) +enum +{ + PROP_0, + PROP_PROFILE, + PROP_SOURCE_URI, + PROP_DEST_URI, + PROP_AVOID_REENCODING, + PROP_SINK, + PROP_SRC, + PROP_CPU_USAGE, + PROP_VIDEO_FILTER, + PROP_AUDIO_FILTER, + LAST_PROP +}; + +static void +post_missing_plugin_error (GstElement * dec, const gchar * element_name) +{ + GstMessage *msg; + + msg = gst_missing_element_message_new (dec, element_name); + gst_element_post_message (dec, msg); + + GST_ELEMENT_ERROR (dec, CORE, MISSING_PLUGIN, + ("Missing element '%s' - check your GStreamer installation.", + element_name), (NULL)); +} +/* *INDENT-ON* */ + +static gboolean +make_transcodebin (GstUriTranscodeBin * self) +{ + GST_INFO_OBJECT (self, "making new transcodebin"); + + self->transcodebin = gst_element_factory_make ("transcodebin", NULL); + if (!self->transcodebin) + goto no_decodebin; + + g_object_set (self->transcodebin, "profile", self->profile, + "video-filter", self->video_filter, + "audio-filter", self->audio_filter, + "avoid-reencoding", self->avoid_reencoding, NULL); + + gst_bin_add (GST_BIN (self), self->transcodebin); + if (!gst_element_link (self->transcodebin, self->sink)) + return FALSE; + + return TRUE; + + /* ERRORS */ +no_decodebin: + { + post_missing_plugin_error (GST_ELEMENT_CAST (self), "transcodebin"); + + GST_ELEMENT_ERROR (self, CORE, MISSING_PLUGIN, (NULL), + ("No transcodebin element, check your installation")); + + return FALSE; + } +} + +static gboolean +make_dest (GstUriTranscodeBin * self) +{ + GError *err = NULL; + + if (!gst_uri_is_valid (self->dest_uri)) + goto invalid_uri; + + self->sink = gst_element_make_from_uri (GST_URI_SINK, self->dest_uri, + "sink", &err); + if (!self->sink) + goto no_sink; + + gst_bin_add (GST_BIN (self), self->sink); + g_object_set (self->sink, "sync", TRUE, "max-lateness", GST_CLOCK_TIME_NONE, + NULL); + return TRUE; + +invalid_uri: + { + GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND, + ("Invalid URI \"%s\".", self->dest_uri), (NULL)); + g_clear_error (&err); + return FALSE; + } + +no_sink: + { + /* whoops, could not create the source element, dig a little deeper to + * figure out what might be wrong. */ + if (err != NULL && err->code == GST_URI_ERROR_UNSUPPORTED_PROTOCOL) { + gchar *prot; + + prot = gst_uri_get_protocol (self->dest_uri); + if (prot == NULL) + goto invalid_uri; + + gst_element_post_message (GST_ELEMENT_CAST (self), + gst_missing_uri_source_message_new (GST_ELEMENT (self), prot)); + + GST_ELEMENT_ERROR (self, CORE, MISSING_PLUGIN, + ("No URI handler implemented for \"%s\".", prot), (NULL)); + + g_free (prot); + } else { + GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND, + ("%s", (err) ? err->message : "URI was not accepted by any element"), + ("No element accepted URI '%s'", self->dest_uri)); + } + + g_clear_error (&err); + + return FALSE; + } +} + +static gboolean +make_source (GstUriTranscodeBin * self) +{ + GError *err = NULL; + + if (!gst_uri_is_valid (self->source_uri)) + goto invalid_uri; + + self->src = gst_element_make_from_uri (GST_URI_SRC, self->source_uri, + "src", &err); + if (!self->src) + goto no_sink; + + gst_bin_add (GST_BIN (self), self->src); + + if (!gst_element_link (self->src, self->transcodebin)) + return FALSE; + + return TRUE; + +invalid_uri: + { + GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND, + ("Invalid URI \"%s\".", self->source_uri), (NULL)); + g_clear_error (&err); + return FALSE; + } + +no_sink: + { + /* whoops, could not create the source element, dig a little deeper to + * figure out what might be wrong. */ + if (err != NULL && err->code == GST_URI_ERROR_UNSUPPORTED_PROTOCOL) { + gchar *prot; + + prot = gst_uri_get_protocol (self->source_uri); + if (prot == NULL) + goto invalid_uri; + + gst_element_post_message (GST_ELEMENT_CAST (self), + gst_missing_uri_source_message_new (GST_ELEMENT (self), prot)); + + GST_ELEMENT_ERROR (self, CORE, MISSING_PLUGIN, + ("No URI handler implemented for \"%s\".", prot), (NULL)); + + g_free (prot); + } else { + GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND, + ("%s", (err) ? err->message : "URI was not accepted by any element"), + ("No element accepted URI '%s'", self->dest_uri)); + } + + g_clear_error (&err); + + return FALSE; + } +} + +static void +remove_all_children (GstUriTranscodeBin * self) +{ + if (self->sink) { + gst_element_set_state (self->sink, GST_STATE_NULL); + gst_bin_remove (GST_BIN (self), self->sink); + self->sink = NULL; + } + + if (self->transcodebin) { + gst_element_set_state (self->transcodebin, GST_STATE_NULL); + gst_bin_remove (GST_BIN (self), self->transcodebin); + self->transcodebin = NULL; + } + + if (self->src) { + gst_element_set_state (self->src, GST_STATE_NULL); + gst_bin_remove (GST_BIN (self), self->src); + self->src = NULL; + } +} + +static GstStateChangeReturn +gst_uri_transcode_bin_change_state (GstElement * element, + GstStateChange transition) +{ + GstStateChangeReturn ret; + GstUriTranscodeBin *self = GST_URI_TRANSCODE_BIN (element); + + switch (transition) { + case GST_STATE_CHANGE_READY_TO_PAUSED: + + if (!make_dest (self)) + goto setup_failed; + + if (!make_transcodebin (self)) + goto setup_failed; + + if (!make_source (self)) + goto setup_failed; + + if (gst_element_set_state (self->sink, + GST_STATE_PAUSED) == GST_STATE_CHANGE_FAILURE) { + GST_ERROR_OBJECT (self, + "Could not set %" GST_PTR_FORMAT " state to PAUSED", self->sink); + goto setup_failed; + } + + if (gst_element_set_state (self->transcodebin, + GST_STATE_PAUSED) == GST_STATE_CHANGE_FAILURE) { + GST_ERROR_OBJECT (self, + "Could not set %" GST_PTR_FORMAT " state to PAUSED", + self->transcodebin); + goto setup_failed; + } + + if (gst_element_set_state (self->src, + GST_STATE_PAUSED) == GST_STATE_CHANGE_FAILURE) { + GST_ERROR_OBJECT (self, + "Could not set %" GST_PTR_FORMAT " state to PAUSED", self->src); + goto setup_failed; + } + + break; + default: + break; + } + + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + + if (ret == GST_STATE_CHANGE_FAILURE) + goto beach; + + switch (transition) { + case GST_STATE_CHANGE_PAUSED_TO_READY: + remove_all_children (self); + break; + default: + break; + } + +beach: + return ret; + +setup_failed: + remove_all_children (self); + return GST_STATE_CHANGE_FAILURE; +} + +static void +gst_uri_transcode_bin_constructed (GObject * object) +{ +#if HAVE_GETRUSAGE + GstUriTranscodeBin *self = GST_URI_TRANSCODE_BIN (object); + + self->cpu_clock = + GST_CLOCK (gst_cpu_throttling_clock_new (self->wanted_cpu_usage)); + gst_pipeline_use_clock (GST_PIPELINE (self), self->cpu_clock); +#endif + + ((GObjectClass *) parent_class)->constructed (object); +} + +static void +gst_uri_transcode_bin_dispose (GObject * object) +{ + GstUriTranscodeBin *self = (GstUriTranscodeBin *) object; + + g_clear_object (&self->video_filter); + g_clear_object (&self->audio_filter); + g_clear_object (&self->cpu_clock); + + G_OBJECT_CLASS (gst_uri_transcode_bin_parent_class)->dispose (object); +} + +static void +gst_uri_transcode_bin_get_property (GObject * object, + guint prop_id, GValue * value, GParamSpec * pspec) +{ + GstUriTranscodeBin *self = GST_URI_TRANSCODE_BIN (object); + + switch (prop_id) { + case PROP_PROFILE: + GST_OBJECT_LOCK (self); + g_value_set_object (value, self->profile); + GST_OBJECT_UNLOCK (self); + break; + case PROP_DEST_URI: + GST_OBJECT_LOCK (self); + g_value_set_string (value, self->dest_uri); + GST_OBJECT_UNLOCK (self); + break; + case PROP_SOURCE_URI: + GST_OBJECT_LOCK (self); + g_value_set_string (value, self->source_uri); + GST_OBJECT_UNLOCK (self); + break; + case PROP_AVOID_REENCODING: + GST_OBJECT_LOCK (self); + g_value_set_boolean (value, self->avoid_reencoding); + GST_OBJECT_UNLOCK (self); + break; + case PROP_CPU_USAGE: + GST_OBJECT_LOCK (self); + g_value_set_uint (value, self->wanted_cpu_usage); + GST_OBJECT_UNLOCK (self); + break; + case PROP_VIDEO_FILTER: + GST_OBJECT_LOCK (self); + g_value_set_object (value, self->video_filter); + GST_OBJECT_UNLOCK (self); + break; + case PROP_AUDIO_FILTER: + GST_OBJECT_LOCK (self); + g_value_set_object (value, self->audio_filter); + GST_OBJECT_UNLOCK (self); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gst_uri_transcode_bin_set_property (GObject * object, + guint prop_id, const GValue * value, GParamSpec * pspec) +{ + GstUriTranscodeBin *self = GST_URI_TRANSCODE_BIN (object); + + switch (prop_id) { + case PROP_PROFILE: + GST_OBJECT_LOCK (self); + self->profile = g_value_dup_object (value); + GST_OBJECT_UNLOCK (self); + break; + case PROP_DEST_URI: + GST_OBJECT_LOCK (self); + g_free (self->dest_uri); + self->dest_uri = g_value_dup_string (value); + GST_OBJECT_UNLOCK (self); + break; + case PROP_SOURCE_URI: + GST_OBJECT_LOCK (self); + g_free (self->source_uri); + self->source_uri = g_value_dup_string (value); + GST_OBJECT_UNLOCK (self); + break; + case PROP_AVOID_REENCODING: + GST_OBJECT_LOCK (self); + self->avoid_reencoding = g_value_get_boolean (value); + GST_OBJECT_UNLOCK (self); + break; + case PROP_CPU_USAGE: +#if HAVE_GETRUSAGE + GST_OBJECT_LOCK (self); + self->wanted_cpu_usage = g_value_get_uint (value); + g_object_set (self->cpu_clock, "cpu-usage", self->wanted_cpu_usage, NULL); + GST_OBJECT_UNLOCK (self); +#else + GST_ERROR_OBJECT (self, + "No CPU usage throttling support for that platform"); +#endif + break; + case PROP_AUDIO_FILTER: + GST_OBJECT_LOCK (self); + self->audio_filter = g_value_dup_object (value); + GST_OBJECT_UNLOCK (self); + break; + case PROP_VIDEO_FILTER: + GST_OBJECT_LOCK (self); + self->video_filter = g_value_dup_object (value); + GST_OBJECT_UNLOCK (self); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gst_uri_transcode_bin_class_init (GstUriTranscodeBinClass * klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GstElementClass *gstelement_klass; + + object_class->get_property = gst_uri_transcode_bin_get_property; + object_class->set_property = gst_uri_transcode_bin_set_property; + object_class->constructed = gst_uri_transcode_bin_constructed; + object_class->dispose = gst_uri_transcode_bin_dispose; + + gstelement_klass = (GstElementClass *) klass; + gstelement_klass->change_state = + GST_DEBUG_FUNCPTR (gst_uri_transcode_bin_change_state); + + GST_DEBUG_CATEGORY_INIT (gst_uri_transcodebin_debug, "uritranscodebin", 0, + "UriTranscodebin element"); + + gst_element_class_set_static_metadata (gstelement_klass, + "URITranscode Bin", "Generic/Bin/Encoding", + "Autoplug and transcoder media from uris", + "Thibault Saunier "); + + /** + * GstUriTranscodeBin:profile: + * + * The #GstEncodingProfile to use. This property must be set before going + * to %GST_STATE_PAUSED or higher. + */ + g_object_class_install_property (object_class, PROP_PROFILE, + g_param_spec_object ("profile", "Profile", + "The GstEncodingProfile to use", GST_TYPE_ENCODING_PROFILE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstUriTranscodeBin:source-uri: + * + * The URI of the stream to encode + */ + g_object_class_install_property (object_class, PROP_SOURCE_URI, + g_param_spec_string ("source-uri", "Source URI", "URI to decode", + NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstUriTranscodeBin:dest-uri: + * + * The destination URI to which the stream should be encoded. + */ + g_object_class_install_property (object_class, PROP_DEST_URI, + g_param_spec_string ("dest-uri", "URI", "URI to put output stream", + NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstUriTranscodeBin:avoid-reencoding: + * + * See #encodebin:avoid-reencoding + */ + g_object_class_install_property (object_class, PROP_AVOID_REENCODING, + g_param_spec_boolean ("avoid-reencoding", "Avoid re-encoding", + "Whether to re-encode portions of compatible video streams that lay on segment boundaries", + DEFAULT_AVOID_REENCODING, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, PROP_CPU_USAGE, + g_param_spec_uint ("cpu-usage", "cpu-usage", + "The percentage of CPU to try to use with the processus running the " + "pipeline driven by the clock", 0, 100, + 100, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstUriTranscodeBin:video-filter: + * + * Set the video filter element/bin to use. + */ + g_object_class_install_property (object_class, PROP_VIDEO_FILTER, + g_param_spec_object ("video-filter", "Video filter", + "the video filter(s) to apply, if possible", + GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + /** + * GstUriTranscodeBin:audio-filter: + * + * Set the audio filter element/bin to use. + */ + g_object_class_install_property (object_class, PROP_AUDIO_FILTER, + g_param_spec_object ("audio-filter", "Audio filter", + "the audio filter(s) to apply, if possible", + GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); +} + +static void +gst_uri_transcode_bin_init (GstUriTranscodeBin * self) +{ + self->wanted_cpu_usage = 100; +} diff --git a/gst/transcode/meson.build b/gst/transcode/meson.build new file mode 100644 index 0000000000..e3814b06ee --- /dev/null +++ b/gst/transcode/meson.build @@ -0,0 +1,13 @@ +gsttranscoder_plugin = library('gsttranscode', + 'gsttranscodebin.c', + 'gst-cpu-throttling-clock.c', + 'gsturitranscodebin.c', + install : true, + dependencies : [gst_dep, gstpbutils_dep], + c_args : gst_plugins_bad_args, + include_directories : [configinc], + install_dir : plugins_install_dir, +) + +pkgconfig.generate(gsttranscoder_plugin, install_dir : plugins_pkgconfig_install_dir) +plugins += [gsttranscoder_plugin] \ No newline at end of file diff --git a/meson.build b/meson.build index 6a2ba6bd32..67aeeb4d03 100644 --- a/meson.build +++ b/meson.build @@ -147,10 +147,15 @@ check_functions = [ ['HAVE_GMTIME_R', 'gmtime_r'], ['HAVE_MMAP', 'mmap'], ['HAVE_PIPE2', 'pipe2'], + ['HAVE_GETRUSAGE', 'getrusage', '#include'], ] foreach f : check_functions - if cc.has_function(f.get(1)) + prefix = '' + if f.length() == 3 + prefix = f.get(2) + endif + if cc.has_function(f.get(1), prefix: prefix) cdata.set(f.get(0), 1) endif endforeach @@ -427,6 +432,8 @@ subdir('gst') subdir('sys') subdir('ext') subdir('tests') +subdir('data') +subdir('tools') subdir('pkgconfig') # xgettext is optional (on Windows for instance) diff --git a/meson_options.txt b/meson_options.txt index 607e1e27f4..ab9801679b 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -143,6 +143,7 @@ option('srt', type : 'feature', value : 'auto', description : 'Secure, Reliable, option('srtp', type : 'feature', value : 'auto', description : 'Secure RTP codec plugin') option('teletext', type : 'feature', value : 'auto', description : 'Teletext plugin') option('tinyalsa', type : 'feature', value : 'auto', description : 'TinyALSA plugin') +option('transcode', type : 'feature', value : 'auto', description : 'Transcode plugin') option('ttml', type : 'feature', value : 'auto', description : 'TTML subtitle parser and renderer plugin') option('uvch264', type : 'feature', value : 'auto', description : 'UVC compliant H.264 camera source plugin') option('voaacenc', type : 'feature', value : 'auto', description : 'AAC audio encoder plugin') diff --git a/pkgconfig/gstreamer-bad-transcoder-uninstalled.pc.in b/pkgconfig/gstreamer-bad-transcoder-uninstalled.pc.in new file mode 100644 index 0000000000..5ae1f82e65 --- /dev/null +++ b/pkgconfig/gstreamer-bad-transcoder-uninstalled.pc.in @@ -0,0 +1,12 @@ +prefix= +exec_prefix= +libdir=@transcoderlibdir@ +includedir=@abs_top_srcdir@/gst-libs + +Name: GStreamer bad transcoder library, uninstalled +Description: High level API for transcoding using GStreamer, uninstalled +Version: @VERSION@ +Requires: gstreamer-@GST_API_VERSION@ gstreamer-base-@GST_API_VERSION@ + +Libs: -L${libdir} -lgsttranscoder-@GST_API_VERSION@ +Cflags: -I@abs_top_srcdir@/gst-libs -I@abs_top_builddir@/gst-libs diff --git a/pkgconfig/gstreamer-bad-transcoder.pc.in b/pkgconfig/gstreamer-bad-transcoder.pc.in new file mode 100644 index 0000000000..474bee70f6 --- /dev/null +++ b/pkgconfig/gstreamer-bad-transcoder.pc.in @@ -0,0 +1,13 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@/gstreamer-@GST_API_VERSION@ +pluginsdir=@libdir@/gstreamer-@GST_API_VERSION@ + +Name: GStreamer bad transcoder library, uninstalled +Description: High level API for transcoding using GStreamer +Version: @VERSION@ +Requires: gstreamer-@GST_API_VERSION@ gstreamer-base-@GST_API_VERSION@ + +Libs: -L${libdir} -lgsttranscoder-@GST_API_VERSION@ +Cflags: -I${includedir} diff --git a/pkgconfig/meson.build b/pkgconfig/meson.build index 5bebe2f976..19ad0b5486 100644 --- a/pkgconfig/meson.build +++ b/pkgconfig/meson.build @@ -11,6 +11,7 @@ pkgconf.set('VERSION', gst_version) pkgconf.set('abs_top_builddir', join_paths(meson.current_build_dir(), '..')) pkgconf.set('abs_top_srcdir', join_paths(meson.current_source_dir(), '..')) pkgconf.set('audiolibdir', join_paths(meson.build_root(), gstbadaudio.outdir())) +pkgconf.set('transcoderlibdir', join_paths(meson.build_root(), gst_transcoder.outdir())) pkgconf.set('codecparserslibdir', join_paths(meson.build_root(), gstcodecparsers.outdir())) pkgconf.set('insertbinlibdir', join_paths(meson.build_root(), gstinsertbin.outdir())) pkgconf.set('mpegtslibdir', join_paths(meson.build_root(), gstmpegts.outdir())) diff --git a/tools/gst-transcoder.c b/tools/gst-transcoder.c new file mode 100644 index 0000000000..553aea2106 --- /dev/null +++ b/tools/gst-transcoder.c @@ -0,0 +1,401 @@ +/* GStreamer + * + * Copyright (C) 2015 Thibault Saunier + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include + +#include "utils.h" +#include + +static const gchar *HELP_SUMMARY = + "gst-transcoder-1.0 transcodes a stream defined by its first \n" + "argument to the place defined by its second argument\n" + "into the format described in its third argument,\n" + "or using the given file extension.\n" + "\n" + "The argument:\n" + "===============================\n" + "\n" + "If the encoding format is not defined, it will be guessed with\n" + "the given file extension." + "\n" + " describe the media format into which the\n" + "input stream is going to be transcoded. We have two different\n" + "ways of describing the format:\n" + "\n" + "GstEncodingProfile serialization format\n" + "---------------------------------------\n" + "\n" + "GStreamer encoding profiles can be descibed with a quite extensive\n" + "syntax which is descibed in the GstEncodingProfile documentation.\n" + "\n" + "The simple case looks like:\n" + "\n" + " muxer_source_caps:videoencoder_source_caps:audioencoder_source_caps\n" + "\n" + "Name and category of serialized GstEncodingTarget\n" + "-------------------------------------------------\n" + "\n" + "Encoding targets describe well known formats which\n" + "those are provided in '.gep' files. You can list\n" + "available ones using the `--list-targets` argument.\n"; + +typedef struct +{ + gint cpu_usage, rate; + gboolean list; + GstEncodingProfile *profile; + gchar *src_uri, *dest_uri, *encoding_format, *size; + gchar *framerate; +} Settings; + +static void +position_updated_cb (GstTranscoder * transcoder, GstClockTime pos) +{ + GstClockTime dur = -1; + gchar status[64] = { 0, }; + + g_object_get (transcoder, "duration", &dur, NULL); + + memset (status, ' ', sizeof (status) - 1); + + if (pos != -1 && dur > 0 && dur != -1) { + gchar dstr[32], pstr[32]; + + /* FIXME: pretty print in nicer format */ + g_snprintf (pstr, 32, "%" GST_TIME_FORMAT, GST_TIME_ARGS (pos)); + pstr[9] = '\0'; + g_snprintf (dstr, 32, "%" GST_TIME_FORMAT, GST_TIME_ARGS (dur)); + dstr[9] = '\0'; + g_print ("%s / %s %s\r", pstr, dstr, status); + } +} + +static GList * +get_profiles_of_type (GstEncodingProfile * profile, GType profile_type) +{ + GList *tmp, *profiles = NULL; + + if (GST_IS_ENCODING_CONTAINER_PROFILE (profile)) { + for (tmp = (GList *) + gst_encoding_container_profile_get_profiles + (GST_ENCODING_CONTAINER_PROFILE (profile)); tmp; tmp = tmp->next) { + if (G_OBJECT_TYPE (tmp->data) == profile_type) + profiles = g_list_prepend (profiles, tmp->data); + } + } else if (GST_IS_ENCODING_VIDEO_PROFILE (profile)) { + profiles = g_list_prepend (profiles, profile); + } + + return profiles; +} + +static gboolean +set_video_settings (Settings * settings) +{ + GList *video_profiles, *tmp; + gchar *p, *tmpstr, **vsize; + gint width = 0, height = 0; + GValue framerate = G_VALUE_INIT; + + if (!settings->size && !settings->framerate) + return TRUE; + + if (settings->size) { + p = tmpstr = g_strdup (settings->size); + + for (; *p; ++p) + *p = g_ascii_tolower (*p); + + vsize = g_strsplit (tmpstr, "x", -1); + g_free (tmpstr); + + if (!vsize[1] || vsize[2]) { + g_strfreev (vsize); + error ("Video size should be in the form: WxH, got %s", settings->size); + + return FALSE; + } + + width = g_ascii_strtoull (vsize[0], NULL, 0); + height = g_ascii_strtoull (vsize[1], NULL, 10); + g_strfreev (vsize); + } + + if (settings->framerate) { + g_value_init (&framerate, GST_TYPE_FRACTION); + if (!gst_value_deserialize (&framerate, settings->framerate)) { + error ("Video framerate should be either a fraction or an integer" + " not: %s", settings->framerate); + return FALSE; + } + } + + video_profiles = get_profiles_of_type (settings->profile, + GST_TYPE_ENCODING_VIDEO_PROFILE); + for (tmp = video_profiles; tmp; tmp = tmp->next) { + GstCaps *rest = gst_encoding_profile_get_restriction (tmp->data); + + if (!rest) + rest = gst_caps_new_empty_simple ("video/x-raw"); + else + rest = gst_caps_copy (rest); + + if (settings->size) { + gst_caps_set_simple (rest, "width", G_TYPE_INT, width, + "height", G_TYPE_INT, height, NULL); + } + if (settings->framerate) + gst_caps_set_value (rest, "framerate", &framerate); + + gst_encoding_profile_set_restriction (tmp->data, rest); + } + + return TRUE; +} + +static gboolean +set_audio_settings (Settings * settings) +{ + GList *audio_profiles, *tmp; + + if (settings->rate < 0) + return TRUE; + + audio_profiles = + get_profiles_of_type (settings->profile, GST_TYPE_ENCODING_AUDIO_PROFILE); + for (tmp = audio_profiles; tmp; tmp = tmp->next) { + GstCaps *rest = gst_encoding_profile_get_restriction (tmp->data); + + if (!rest) + rest = gst_caps_new_empty_simple ("audio/x-raw"); + else + rest = gst_caps_copy (rest); + + gst_caps_set_simple (rest, "rate", G_TYPE_INT, settings->rate, NULL); + gst_encoding_profile_set_restriction (tmp->data, rest); + } + + return TRUE; +} + +static void +list_encoding_targets (void) +{ + GList *tmp, *targets = gst_encoding_list_all_targets (NULL); + + for (tmp = targets; tmp; tmp = tmp->next) { + GstEncodingTarget *target = tmp->data; + GList *usable_profiles = get_usable_profiles (target); + + if (usable_profiles) { + GList *tmpprof; + + g_print ("\n%s (%s): %s\n * Profiles:\n", + gst_encoding_target_get_name (target), + gst_encoding_target_get_category (target), + gst_encoding_target_get_description (target)); + + for (tmpprof = usable_profiles; tmpprof; tmpprof = tmpprof->next) + g_print (" - %s: %s", + gst_encoding_profile_get_name (tmpprof->data), + gst_encoding_profile_get_description (tmpprof->data)); + + g_print ("\n"); + g_list_free (usable_profiles); + } + } + + g_list_free_full (targets, (GDestroyNotify) g_object_unref); +} + +static void +_error_cb (GstTranscoder * transcoder, GError * err, GstStructure * details) +{ + if (g_error_matches (err, GST_CORE_ERROR, GST_CORE_ERROR_PAD)) { + GstPadLinkReturn lret; + GType type; + + if (details && gst_structure_get (details, "linking-error", + GST_TYPE_PAD_LINK_RETURN, &lret, + "msg-source-type", G_TYPE_GTYPE, &type, NULL) && + type == g_type_from_name ("GstTranscodeBin")) { + error ("\nCould not setup transcoding pipeline," + " make sure that you transcoding format parametters" + " are compatible with the input stream."); + + return; + } + } + + error ("\nFAILURE: %s", err->message); +} + +static void +_warning_cb (GstTranscoder * transcoder, GError * error, GstStructure * details) +{ + gboolean cant_encode; + GstCaps *caps = NULL; + gchar *stream_id = NULL; + + if (details && gst_structure_get (details, "can-t-encode-stream", + G_TYPE_BOOLEAN, &cant_encode, "stream-caps", GST_TYPE_CAPS, + &caps, "stream-id", G_TYPE_STRING, &stream_id, NULL)) { + gchar *source_uri = gst_transcoder_get_source_uri (transcoder); + + warn ("WARNING: Input stream %s: WON'T BE ENCODED.\n" + "Make sure the encoding settings are valid and that" + " any preset you set actually exists.\n" + "For more information about that stream, you can inspect" + " the source stream with:\n\n" + " gst-discoverer-1.0 -v %s\n", stream_id, source_uri); + gst_caps_unref (caps); + g_free (stream_id);; + g_free (source_uri);; + + return; + } + warn ("Got warning: %s", error->message); +} + +int +main (int argc, char *argv[]) +{ + gint res = 0; + GError *err = NULL; + GstTranscoder *transcoder; + GOptionContext *ctx; + Settings settings = { + .cpu_usage = 100, + .rate = -1, + .encoding_format = NULL, + .size = NULL, + .framerate = NULL, + }; + GOptionEntry options[] = { + {"cpu-usage", 'c', 0, G_OPTION_ARG_INT, &settings.cpu_usage, + "The CPU usage to target in the transcoding process", NULL}, + {"list-targets", 'l', G_OPTION_ARG_NONE, 0, &settings.list, + "List all encoding targets", NULL}, + {"size", 's', 0, G_OPTION_ARG_STRING, &settings.size, + "set frame size (WxH or abbreviation)", NULL}, + {"audio-rate", 'r', 0, G_OPTION_ARG_INT, &settings.rate, + "set audio sampling rate (in Hz)", NULL}, + {"framerate", 'f', 0, G_OPTION_ARG_STRING, &settings.framerate, + "set video framerate as a fraction (24/1 for 24fps)" + " or a single number (24 for 24fps))", NULL}, + {"video-encoder", 'v', 0, G_OPTION_ARG_STRING, &settings.size, + "The video encoder to use.", NULL}, + {NULL} + }; + + g_set_prgname ("gst-transcoder"); + + ctx = g_option_context_new (" " + "[[/]]"); + g_option_context_set_summary (ctx, HELP_SUMMARY); + + g_option_context_add_main_entries (ctx, options, NULL); + g_option_context_add_group (ctx, gst_init_get_option_group ()); + + if (!g_option_context_parse (ctx, &argc, &argv, &err)) { + g_print ("Error initializing: %s\n", GST_STR_NULL (err->message)); + g_clear_error (&err); + g_option_context_free (ctx); + return 1; + } + gst_pb_utils_init (); + + if (settings.list) { + list_encoding_targets (); + return 0; + } + + if (argc < 3 || argc > 4) { + g_print ("%s", g_option_context_get_help (ctx, TRUE, NULL)); + g_option_context_free (ctx); + + return -1; + } + g_option_context_free (ctx); + + settings.src_uri = ensure_uri (argv[1]); + settings.dest_uri = ensure_uri (argv[2]); + + if (argc == 3) { + settings.encoding_format = get_file_extension (settings.dest_uri); + + if (!settings.encoding_format) + goto no_extension; + } else { + settings.encoding_format = argv[3]; + } + + settings.profile = create_encoding_profile (settings.encoding_format); + + if (!settings.profile) { + error ("Could not find any encoding format for %s\n", + settings.encoding_format); + warn ("You can list available targets using %s --list-targets", argv[0]); + res = 1; + goto done; + } + + g_print ("Encoding to:\n\n"); + describe_encoding_profile (settings.profile); + if (!set_video_settings (&settings)) { + res = -1; + goto done; + } + + if (!set_audio_settings (&settings)) { + res = -1; + goto done; + } + + transcoder = gst_transcoder_new_full (settings.src_uri, settings.dest_uri, + settings.profile, NULL); + gst_transcoder_set_avoid_reencoding (transcoder, TRUE); + + gst_transcoder_set_cpu_usage (transcoder, settings.cpu_usage); + g_signal_connect (transcoder, "position-updated", + G_CALLBACK (position_updated_cb), NULL); + g_signal_connect (transcoder, "warning", G_CALLBACK (_warning_cb), NULL); + g_signal_connect (transcoder, "error", G_CALLBACK (_error_cb), NULL); + + g_assert (transcoder); + + ok ("Starting transcoding..."); + gst_transcoder_run (transcoder, &err); + if (!err) + ok ("\nDONE."); + +done: + g_free (settings.dest_uri); + g_free (settings.src_uri); + + return res; + +no_extension: + error ("No specified and no extension" + " available in the output target: %s", settings.dest_uri); + res = 1; + + goto done; +} diff --git a/tools/meson.build b/tools/meson.build new file mode 100644 index 0000000000..3fda1f6376 --- /dev/null +++ b/tools/meson.build @@ -0,0 +1,5 @@ +executable('gst-transcoder-' + api_version, + 'gst-transcoder.c', 'utils.c', + install : true, + dependencies : [gst_dep, gstpbutils_dep, gst_transcoder_dep], +) diff --git a/tools/utils.c b/tools/utils.c new file mode 100644 index 0000000000..4f855c565a --- /dev/null +++ b/tools/utils.c @@ -0,0 +1,208 @@ +#include + +#include "utils.h" +#include + +void +print (GstDebugColorFlags c, gboolean err, gboolean nline, const gchar * format, + va_list var_args) +{ + GString *str = g_string_new (NULL); + GstDebugColorMode color_mode; + gchar *color = NULL; + const gchar *clear = NULL; + + color_mode = gst_debug_get_color_mode (); +#ifdef G_OS_WIN32 + if (color_mode == GST_DEBUG_COLOR_MODE_UNIX) { +#else + if (color_mode != GST_DEBUG_COLOR_MODE_OFF) { +#endif + clear = "\033[00m"; + color = gst_debug_construct_term_color (c); + } + + if (color) { + g_string_append (str, color); + g_free (color); + } + + g_string_append_vprintf (str, format, var_args); + + if (nline) + g_string_append_c (str, '\n'); + + if (clear) + g_string_append (str, clear); + + if (err) + g_printerr ("%s", str->str); + else + g_print ("%s", str->str); + + g_string_free (str, TRUE); +} + +void +ok (const gchar * format, ...) +{ + va_list var_args; + + va_start (var_args, format); + print (GST_DEBUG_FG_GREEN, FALSE, TRUE, format, var_args); + va_end (var_args); +} + +void +warn (const gchar * format, ...) +{ + va_list var_args; + + va_start (var_args, format); + print (GST_DEBUG_FG_YELLOW, TRUE, TRUE, format, var_args); + va_end (var_args); +} + +void +error (const gchar * format, ...) +{ + va_list var_args; + + va_start (var_args, format); + print (GST_DEBUG_FG_RED, TRUE, TRUE, format, var_args); + va_end (var_args); +} + +gchar * +ensure_uri (const gchar * location) +{ + if (gst_uri_is_valid (location)) + return g_strdup (location); + else + return gst_filename_to_uri (location, NULL); +} + +gchar * +get_file_extension (gchar * uri) +{ + size_t len; + gint find; + + len = strlen (uri); + find = len - 1; + + while (find >= 0) { + if (uri[find] == '.') + break; + find--; + } + + if (find < 0) + return NULL; + + return &uri[find + 1]; +} + +GList * +get_usable_profiles (GstEncodingTarget * target) +{ + GList *tmpprof, *usable_profiles = NULL; + + for (tmpprof = (GList *) gst_encoding_target_get_profiles (target); + tmpprof; tmpprof = tmpprof->next) { + GstEncodingProfile *profile = tmpprof->data; + GstElement *tmpencodebin = gst_element_factory_make ("encodebin", NULL); + + gst_encoding_profile_set_presence (profile, 1); + if (GST_IS_ENCODING_CONTAINER_PROFILE (profile)) { + GList *tmpsubprof; + for (tmpsubprof = (GList *) + gst_encoding_container_profile_get_profiles + (GST_ENCODING_CONTAINER_PROFILE (profile)); tmpsubprof; + tmpsubprof = tmpsubprof->next) + gst_encoding_profile_set_presence (tmpsubprof->data, 1); + } + + g_object_set (tmpencodebin, "profile", gst_object_ref (profile), NULL); + GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (tmpencodebin), + GST_DEBUG_GRAPH_SHOW_ALL, gst_encoding_profile_get_name (profile)); + + /* The profile could be expended */ + if (GST_BIN (tmpencodebin)->children) + usable_profiles = g_list_prepend (usable_profiles, profile); + + gst_object_unref (tmpencodebin); + } + + return usable_profiles; +} + +GstEncodingProfile * +create_encoding_profile (const gchar * pname) +{ + GstEncodingProfile *profile; + GValue value = G_VALUE_INIT; + + g_value_init (&value, GST_TYPE_ENCODING_PROFILE); + + if (!gst_value_deserialize (&value, pname)) { + g_value_reset (&value); + + return NULL; + } + + profile = g_value_dup_object (&value); + g_value_reset (&value); + + return profile; +} + +static const gchar * +get_profile_type (GstEncodingProfile * profile) +{ + if (GST_IS_ENCODING_CONTAINER_PROFILE (profile)) + return "Container"; + else if (GST_IS_ENCODING_AUDIO_PROFILE (profile)) + return "Audio"; + else if (GST_IS_ENCODING_VIDEO_PROFILE (profile)) + return "Video"; + else + return "Unkonwn"; +} + +static void +print_profile (GstEncodingProfile * profile, const gchar * prefix) +{ + const gchar *name = gst_encoding_profile_get_name (profile); + const gchar *desc = gst_encoding_profile_get_description (profile); + GstCaps *format = gst_encoding_profile_get_format (profile); + gchar *capsdesc; + + if (gst_caps_is_fixed (format)) + capsdesc = gst_pb_utils_get_codec_description (format); + else + capsdesc = gst_caps_to_string (format); + + g_print ("%s%s: %s%s%s%s%s%s\n", prefix, get_profile_type (profile), + name ? name : capsdesc, desc ? ": " : "", desc ? desc : "", + name ? " (" : "", name ? capsdesc : "", name ? ")" : ""); + + g_free (capsdesc); +} + +void +describe_encoding_profile (GstEncodingProfile * profile) +{ + g_return_if_fail (GST_IS_ENCODING_PROFILE (profile)); + + print_profile (profile, " "); + if (GST_IS_ENCODING_CONTAINER_PROFILE (profile)) { + const GList *tmp; + + for (tmp = + gst_encoding_container_profile_get_profiles + (GST_ENCODING_CONTAINER_PROFILE (profile)); tmp; tmp = tmp->next) + print_profile (tmp->data, " - "); + } + +} diff --git a/tools/utils.h b/tools/utils.h new file mode 100644 index 0000000000..106ec45ec2 --- /dev/null +++ b/tools/utils.h @@ -0,0 +1,21 @@ +#ifndef __GST_TRANSCODER_UTILS_H +#define __GST_TRANSCODER_UTILS_H + +#include "utils.h" +#include +#include +#include + +void print (GstDebugColorFlags c, gboolean err, gboolean nline, const gchar * format, va_list var_args); +void ok (const gchar * format, ...); +void warn (const gchar * format, ...); +void error (const gchar * format, ...); + +gchar * ensure_uri (const gchar * location); +gchar * get_file_extension (gchar * uri); + +GList * get_usable_profiles (GstEncodingTarget * target); +GstEncodingProfile * create_encoding_profile (const gchar * pname); +void describe_encoding_profile (GstEncodingProfile *profile); + +#endif /*__GST_TRANSCODER_UTILS_H*/