Merge pull request #627 from hpdevx/master
Adding GStreamer plugin for Multi-Media redirection
This commit is contained in:
commit
e407c8dd52
@ -4,6 +4,7 @@
|
||||
# Copyright 2011 O.S. Systems Software Ltda.
|
||||
# Copyright 2011 Otavio Salvador <otavio@ossystems.com.br>
|
||||
# Copyright 2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
|
||||
# Copyright 2012 HP Development Company, LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@ -117,6 +118,7 @@ if(NOT WIN32)
|
||||
|
||||
if(NOT APPLE)
|
||||
find_suggested_package(FFmpeg)
|
||||
find_suggested_package(Gstreamer)
|
||||
find_suggested_package(ALSA)
|
||||
else(NOT APPLE)
|
||||
find_optional_package(FFmpeg)
|
||||
|
@ -4,6 +4,7 @@
|
||||
# Copyright 2011 O.S. Systems Software Ltda.
|
||||
# Copyright 2011 Otavio Salvador <otavio@ossystems.com.br>
|
||||
# Copyright 2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
|
||||
# Copyright 2012 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@ -47,6 +48,10 @@ if(WITH_FFMPEG)
|
||||
add_subdirectory(ffmpeg)
|
||||
endif()
|
||||
|
||||
if(GSTREAMER_FOUND)
|
||||
add_subdirectory(gstreamer)
|
||||
endif()
|
||||
|
||||
if(WITH_ALSA)
|
||||
add_subdirectory(alsa)
|
||||
endif()
|
||||
|
34
channels/drdynvc/tsmf/gstreamer/CMakeLists.txt
Normal file
34
channels/drdynvc/tsmf/gstreamer/CMakeLists.txt
Normal file
@ -0,0 +1,34 @@
|
||||
# FreeRDP: A Remote Desktop Protocol Client
|
||||
# FreeRDP cmake build script for gstreamer plugin
|
||||
#
|
||||
# (C) Copyright 2012 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
|
||||
set(TSMF_GSTREAMER_SRCS
|
||||
tsmf_gstreamer.c
|
||||
)
|
||||
|
||||
include_directories(..)
|
||||
include_directories(${GSTREAMER_INCLUDE_DIRS})
|
||||
|
||||
add_library(tsmf_gstreamer ${TSMF_GSTREAMER_SRCS})
|
||||
set_target_properties(tsmf_gstreamer PROPERTIES PREFIX "")
|
||||
|
||||
target_link_libraries(tsmf_gstreamer freerdp-utils)
|
||||
target_link_libraries(tsmf_gstreamer ${GSTREAMER_LIBRARIES} gstapp-0.10 gstinterfaces-0.10 Xrandr X11 Xext)
|
||||
|
||||
install(TARGETS tsmf_gstreamer DESTINATION ${FREERDP_PLUGIN_PATH})
|
||||
|
1595
channels/drdynvc/tsmf/gstreamer/tsmf_gstreamer.c
Normal file
1595
channels/drdynvc/tsmf/gstreamer/tsmf_gstreamer.c
Normal file
File diff suppressed because it is too large
Load Diff
@ -3,6 +3,7 @@
|
||||
* Video Redirection Virtual Channel - Codec
|
||||
*
|
||||
* Copyright 2010-2011 Vic Lee
|
||||
* Copyright 2012 Hewlett-Packard Development Company, L.P.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -103,6 +104,20 @@ static const TSMFMediaTypeMap tsmf_sub_type_map[] =
|
||||
TSMF_SUB_TYPE_MP2V
|
||||
},
|
||||
|
||||
/* 31564D57-0000-0010-8000-00AA00389B71 */
|
||||
{
|
||||
{ 0x57, 0x4D, 0x56, 0x31, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71 },
|
||||
"MEDIASUBTYPE_WMV1",
|
||||
TSMF_SUB_TYPE_WMV1
|
||||
},
|
||||
|
||||
/* 32564D57-0000-0010-8000-00AA00389B71 */
|
||||
{
|
||||
{ 0x57, 0x4D, 0x56, 0x32, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71 },
|
||||
"MEDIASUBTYPE_WMV2",
|
||||
TSMF_SUB_TYPE_WMV2
|
||||
},
|
||||
|
||||
/* 33564D57-0000-0010-8000-00AA00389B71 */
|
||||
{
|
||||
{ 0x57, 0x4D, 0x56, 0x33, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71 },
|
||||
@ -131,12 +146,62 @@ static const TSMFMediaTypeMap tsmf_sub_type_map[] =
|
||||
TSMF_SUB_TYPE_AVC1
|
||||
},
|
||||
|
||||
/* 3334504D-0000-0010-8000-00AA00389B71 */
|
||||
{
|
||||
{ 0x4D, 0x50, 0x34, 0x33, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71 },
|
||||
"MEDIASUBTYPE_MP43",
|
||||
TSMF_SUB_TYPE_MP43
|
||||
},
|
||||
|
||||
/* 5634504D-0000-0010-8000-00AA00389B71 */
|
||||
{
|
||||
{ 0x4D, 0x50, 0x34, 0x56, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71 },
|
||||
"MEDIASUBTYPE_MP4S",
|
||||
TSMF_SUB_TYPE_MP4S
|
||||
},
|
||||
|
||||
/* 3234504D-0000-0010-8000-00AA00389B71 */
|
||||
{
|
||||
{ 0x4D, 0x50, 0x34, 0x32, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71 },
|
||||
"MEDIASUBTYPE_MP42",
|
||||
TSMF_SUB_TYPE_MP42
|
||||
},
|
||||
|
||||
/* E436EB81-524F-11CE-9F53-0020AF0BA770 */
|
||||
/*
|
||||
{
|
||||
{ 0x81, 0xEB, 0x36, 0xE4, 0x4F, 0x52, 0xCE, 0x11, 0x9F, 0x53, 0x00, 0x20, 0xAF, 0x0B, 0xA7, 0x70 },
|
||||
"MEDIASUBTYPE_MP1V",
|
||||
TSMF_SUB_TYPE_MP1V
|
||||
},
|
||||
*/
|
||||
|
||||
/* 00000050-0000-0010-8000-00AA00389B71 */
|
||||
/*
|
||||
{
|
||||
{ 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71 },
|
||||
"MEDIASUBTYPE_MP1A",
|
||||
TSMF_SUB_TYPE_MP1A
|
||||
},
|
||||
*/
|
||||
|
||||
/* E06D802C-DB46-11CF-B4D1-00805F6CBBEA */
|
||||
/*
|
||||
{
|
||||
{ 0x2C, 0x80, 0x6D, 0xE0, 0x46, 0xDB, 0xCF, 0x11, 0xB4, 0xD1, 0x00, 0x80, 0x5F, 0x6C, 0xBB, 0xEA },
|
||||
"MEDIASUBTYPE_DOLBY_AC3",
|
||||
TSMF_SUB_TYPE_AC3
|
||||
},
|
||||
*/
|
||||
|
||||
/* 32595559-0000-0010-8000-00AA00389B71 */
|
||||
/*
|
||||
{
|
||||
{ 0x59, 0x55, 0x59, 0x32, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71 },
|
||||
"MEDIASUBTYPE_YUY2",
|
||||
TSMF_SUB_TYPE_YUY2
|
||||
},
|
||||
*/
|
||||
|
||||
{
|
||||
{ 0 },
|
||||
@ -176,6 +241,13 @@ static const TSMFMediaTypeMap tsmf_format_type_map[] =
|
||||
TSMF_FORMAT_TYPE_VIDEOINFO2
|
||||
},
|
||||
|
||||
/* 05589F82-C356-11CE-BF01-00AA0055595A */
|
||||
{
|
||||
{ 0x82, 0x9F, 0x58, 0x05, 0x56, 0xC3, 0xCE, 0x11, 0xBF, 0x01, 0x00, 0xAA, 0x00, 0x55, 0x59, 0x5A },
|
||||
"FORMAT_MPEG1_VIDEO",
|
||||
TSMF_FORMAT_TYPE_MPEG1VIDEOINFO
|
||||
},
|
||||
|
||||
{
|
||||
{ 0 },
|
||||
"Unknown",
|
||||
@ -257,6 +329,40 @@ static uint32 tsmf_codec_parse_VIDEOINFOHEADER2(TS_AM_MEDIA_TYPE* mediatype, STR
|
||||
return 72;
|
||||
}
|
||||
|
||||
/* http://msdn.microsoft.com/en-us/library/dd390700.aspx */
|
||||
static uint32 tsmf_codec_parse_VIDEOINFOHEADER(TS_AM_MEDIA_TYPE* mediatype, STREAM* s)
|
||||
{
|
||||
/*
|
||||
typedef struct tagVIDEOINFOHEADER {
|
||||
RECT rcSource; //16
|
||||
RECT rcTarget; //16 32
|
||||
DWORD dwBitRate; //4 36
|
||||
DWORD dwBitErrorRate; //4 40
|
||||
REFERENCE_TIME AvgTimePerFrame; //8 48
|
||||
BITMAPINFOHEADER bmiHeader;
|
||||
} VIDEOINFOHEADER;
|
||||
*/
|
||||
uint64 AvgTimePerFrame;
|
||||
|
||||
/* VIDEOINFOHEADER.rcSource, RECT(LONG left, LONG top, LONG right, LONG bottom) */
|
||||
stream_seek_uint32(s);
|
||||
stream_seek_uint32(s);
|
||||
stream_read_uint32(s, mediatype->Width);
|
||||
stream_read_uint32(s, mediatype->Height);
|
||||
/* VIDEOINFOHEADER.rcTarget */
|
||||
stream_seek(s, 16);
|
||||
/* VIDEOINFOHEADER.dwBitRate */
|
||||
stream_read_uint32(s, mediatype->BitRate);
|
||||
/* VIDEOINFOHEADER.dwBitErrorRate */
|
||||
stream_seek_uint32(s);
|
||||
/* VIDEOINFOHEADER.AvgTimePerFrame */
|
||||
stream_read_uint64(s, AvgTimePerFrame);
|
||||
mediatype->SamplesPerSecond.Numerator = 1000000;
|
||||
mediatype->SamplesPerSecond.Denominator = (int)(AvgTimePerFrame / 10LL);
|
||||
|
||||
return 48;
|
||||
}
|
||||
|
||||
boolean tsmf_codec_parse_media_type(TS_AM_MEDIA_TYPE* mediatype, STREAM* s)
|
||||
{
|
||||
int i;
|
||||
@ -358,6 +464,18 @@ boolean tsmf_codec_parse_media_type(TS_AM_MEDIA_TYPE* mediatype, STREAM* s)
|
||||
|
||||
break;
|
||||
|
||||
case TSMF_FORMAT_TYPE_MPEG1VIDEOINFO:
|
||||
/* http://msdn.microsoft.com/en-us/library/dd390700.aspx */
|
||||
|
||||
i = tsmf_codec_parse_VIDEOINFOHEADER(mediatype, s);
|
||||
i += tsmf_codec_parse_BITMAPINFOHEADER(mediatype, s, true);
|
||||
if (cbFormat > i)
|
||||
{
|
||||
mediatype->ExtraDataSize = cbFormat - i;
|
||||
mediatype->ExtraData = stream_get_tail(s);
|
||||
}
|
||||
break;
|
||||
|
||||
case TSMF_FORMAT_TYPE_MPEG2VIDEOINFO:
|
||||
/* http://msdn.microsoft.com/en-us/library/dd390707.aspx */
|
||||
|
||||
|
@ -3,6 +3,7 @@
|
||||
* Video Redirection Virtual Channel - Constants
|
||||
*
|
||||
* Copyright 2010-2011 Vic Lee
|
||||
* Copyright 2012 Hewlett-Packard Development Company, L.P.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -108,6 +109,14 @@
|
||||
#define TSMF_SUB_TYPE_H264 9
|
||||
#define TSMF_SUB_TYPE_AVC1 10
|
||||
#define TSMF_SUB_TYPE_AC3 11
|
||||
#define TSMF_SUB_TYPE_WMV2 12
|
||||
#define TSMF_SUB_TYPE_WMV1 13
|
||||
#define TSMF_SUB_TYPE_MP1V 14
|
||||
#define TSMF_SUB_TYPE_MP1A 15
|
||||
#define TSMF_SUB_TYPE_YUY2 16
|
||||
#define TSMF_SUB_TYPE_MP43 17
|
||||
#define TSMF_SUB_TYPE_MP4S 18
|
||||
#define TSMF_SUB_TYPE_MP42 19
|
||||
|
||||
/* FormatType */
|
||||
#define TSMF_FORMAT_TYPE_UNKNOWN 0
|
||||
@ -115,6 +124,7 @@
|
||||
#define TSMF_FORMAT_TYPE_WAVEFORMATEX 2
|
||||
#define TSMF_FORMAT_TYPE_MPEG2VIDEOINFO 3
|
||||
#define TSMF_FORMAT_TYPE_VIDEOINFO2 4
|
||||
#define TSMF_FORMAT_TYPE_MPEG1VIDEOINFO 5
|
||||
|
||||
#endif
|
||||
|
||||
|
@ -3,6 +3,7 @@
|
||||
* Video Redirection Virtual Channel - Decoder
|
||||
*
|
||||
* Copyright 2010-2011 Vic Lee
|
||||
* Copyright 2012 Hewlett-Packard Development Company, L.P.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -23,6 +24,14 @@
|
||||
#include "drdynvc_types.h"
|
||||
#include "tsmf_types.h"
|
||||
|
||||
typedef enum _ITSMFControlMsg
|
||||
{
|
||||
Control_Pause,
|
||||
Control_Restart,
|
||||
Control_Flush,
|
||||
Control_EndOfStream
|
||||
} ITSMFControlMsg;
|
||||
|
||||
typedef struct _ITSMFDecoder ITSMFDecoder;
|
||||
|
||||
struct _ITSMFDecoder
|
||||
@ -38,7 +47,20 @@ struct _ITSMFDecoder
|
||||
/* Get the width and height of decoded video frame */
|
||||
boolean (*GetDecodedDimension) (ITSMFDecoder* decoder, uint32* width, uint32* height);
|
||||
/* Free the decoder */
|
||||
void (*Free) (ITSMFDecoder* decoder);
|
||||
void (*Free) (ITSMFDecoder * decoder);
|
||||
/* Optional Contol function */
|
||||
void (*Control) (ITSMFDecoder * decoder, ITSMFControlMsg control_msg, uint32 *arg);
|
||||
/* Decode a sample with extended interface. */
|
||||
int (*DecodeEx) (ITSMFDecoder * decoder, const uint8 * data, uint32 data_size, uint32 extensions,
|
||||
uint64 start_time, uint64 end_time, uint64 duration);
|
||||
/* Get current play time */
|
||||
uint64 (*GetRunningTime) (ITSMFDecoder * decoder);
|
||||
/* Update Gstreamer Rendering Area */
|
||||
void (*UpdateRenderingArea) (ITSMFDecoder * decoder, int newX, int newY, int newWidth, int newHeight, int numRectangles, RDP_RECT *rectangles);
|
||||
/* Change Gstreamer Audio Volume */
|
||||
void (*ChangeVolume) (ITSMFDecoder * decoder, uint32 newVolume, uint32 muted);
|
||||
/* Check buffer level */
|
||||
uint32 (*BufferLevel) (ITSMFDecoder * decoder);
|
||||
};
|
||||
|
||||
#define TSMF_DECODER_EXPORT_FUNC_NAME "TSMFDecoderEntry"
|
||||
|
@ -3,6 +3,7 @@
|
||||
* Video Redirection Virtual Channel - Interface Manipulation
|
||||
*
|
||||
* Copyright 2010-2011 Vic Lee
|
||||
* Copyright 2012 Hewlett-Packard Development Company, L.P.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -118,14 +119,23 @@ int tsmf_ifman_check_format_support_request(TSMF_IFMAN* ifman)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static TSMF_PRESENTATION* pexisted = 0;
|
||||
|
||||
int tsmf_ifman_on_new_presentation(TSMF_IFMAN* ifman)
|
||||
{
|
||||
int error = 0;
|
||||
TSMF_PRESENTATION* presentation;
|
||||
|
||||
DEBUG_DVC("");
|
||||
if (pexisted)
|
||||
{
|
||||
ifman->output_pending = false;
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
presentation = tsmf_presentation_new(stream_get_tail(ifman->input), ifman->channel_callback);
|
||||
pexisted = presentation;
|
||||
if (presentation == NULL)
|
||||
error = 1;
|
||||
else
|
||||
@ -208,6 +218,8 @@ int tsmf_ifman_shutdown_presentation(TSMF_IFMAN* ifman)
|
||||
if (presentation)
|
||||
tsmf_presentation_free(presentation);
|
||||
|
||||
pexisted = 0;
|
||||
|
||||
stream_check_size(ifman->output, 4);
|
||||
stream_write_uint32(ifman->output, 0); /* Result */
|
||||
ifman->output_interface_id = TSMF_INTERFACE_DEFAULT | STREAM_ID_STUB;
|
||||
@ -216,14 +228,42 @@ int tsmf_ifman_shutdown_presentation(TSMF_IFMAN* ifman)
|
||||
|
||||
int tsmf_ifman_on_stream_volume(TSMF_IFMAN* ifman)
|
||||
{
|
||||
DEBUG_DVC("");
|
||||
DEBUG_DVC("on stream volume");
|
||||
TSMF_PRESENTATION* presentation;
|
||||
presentation = tsmf_presentation_find_by_id(stream_get_tail(ifman->input));
|
||||
if (presentation)
|
||||
{
|
||||
stream_seek(ifman->input, 16);
|
||||
uint32 newVolume;
|
||||
uint32 muted;
|
||||
stream_read_uint32(ifman->input, newVolume);
|
||||
DEBUG_DVC("on stream volume: new volume=[%d]", newVolume);
|
||||
stream_read_uint32(ifman->input, muted);
|
||||
DEBUG_DVC("on stream volume: muted=[%d]", muted);
|
||||
tsmf_presentation_volume_changed(presentation, newVolume, muted);
|
||||
}
|
||||
else
|
||||
DEBUG_WARN("unknown presentation id");
|
||||
|
||||
ifman->output_pending = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int tsmf_ifman_on_channel_volume(TSMF_IFMAN* ifman)
|
||||
{
|
||||
DEBUG_DVC("");
|
||||
DEBUG_DVC("on channel volume");
|
||||
TSMF_PRESENTATION* presentation;
|
||||
presentation = tsmf_presentation_find_by_id(stream_get_tail(ifman->input));
|
||||
if (presentation)
|
||||
{
|
||||
stream_seek(ifman->input, 16);
|
||||
uint32 channelVolume;
|
||||
uint32 changedChannel;
|
||||
stream_read_uint32(ifman->input, channelVolume);
|
||||
DEBUG_DVC("on channel volume: channel volume=[%d]", channelVolume);
|
||||
stream_read_uint32(ifman->input, changedChannel);
|
||||
DEBUG_DVC("on stream volume: changed channel=[%d]", changedChannel);
|
||||
}
|
||||
ifman->output_pending = true;
|
||||
return 0;
|
||||
}
|
||||
@ -434,6 +474,14 @@ int tsmf_ifman_on_playback_paused(TSMF_IFMAN* ifman)
|
||||
{
|
||||
DEBUG_DVC("");
|
||||
ifman->output_pending = true;
|
||||
|
||||
/* Added pause control so gstreamer pipeline can be paused accordingly */
|
||||
TSMF_PRESENTATION* presentation;
|
||||
presentation = tsmf_presentation_find_by_id(stream_get_tail(ifman->input));
|
||||
if (presentation)
|
||||
tsmf_presentation_paused(presentation);
|
||||
else
|
||||
DEBUG_WARN("unknown presentation id");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -441,6 +489,14 @@ int tsmf_ifman_on_playback_restarted(TSMF_IFMAN* ifman)
|
||||
{
|
||||
DEBUG_DVC("");
|
||||
ifman->output_pending = true;
|
||||
|
||||
/* Added restart control so gstreamer pipeline can be resumed accordingly */
|
||||
TSMF_PRESENTATION* presentation;
|
||||
presentation = tsmf_presentation_find_by_id(stream_get_tail(ifman->input));
|
||||
if (presentation)
|
||||
tsmf_presentation_restarted(presentation);
|
||||
else
|
||||
DEBUG_WARN("unknown presentation id");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,7 @@
|
||||
* Video Redirection Virtual Channel - Media Container
|
||||
*
|
||||
* Copyright 2010-2011 Vic Lee
|
||||
* Copyright 2012 Hewlett-Packard Development Company, L.P.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -20,6 +21,8 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <signal.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/time.h>
|
||||
#include <freerdp/utils/memory.h>
|
||||
#include <freerdp/utils/stream.h>
|
||||
@ -123,6 +126,9 @@ struct _TSMF_SAMPLE
|
||||
};
|
||||
|
||||
static LIST* presentation_list = NULL;
|
||||
static uint64 last_played_audio_time = 0;
|
||||
static pthread_mutex_t tsmf_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
static int TERMINATING = 0;
|
||||
|
||||
static uint64 get_current_time(void)
|
||||
{
|
||||
@ -145,33 +151,41 @@ static TSMF_SAMPLE* tsmf_stream_pop_sample(TSMF_STREAM* stream, int sync)
|
||||
|
||||
if (sync)
|
||||
{
|
||||
if (stream->major_type == TSMF_MAJOR_TYPE_AUDIO)
|
||||
if (stream->decoder)
|
||||
{
|
||||
/* Check if some other stream has earlier sample that needs to be played first */
|
||||
if (stream->last_end_time > AUDIO_TOLERANCE)
|
||||
if (stream->decoder->GetDecodedData)
|
||||
{
|
||||
freerdp_mutex_lock(presentation->mutex);
|
||||
for (item = presentation->stream_list->head; item; item = item->next)
|
||||
if (stream->major_type == TSMF_MAJOR_TYPE_AUDIO)
|
||||
{
|
||||
s = (TSMF_STREAM*) item->data;
|
||||
if (s != stream && !s->eos && s->last_end_time &&
|
||||
s->last_end_time < stream->last_end_time - AUDIO_TOLERANCE)
|
||||
/* Check if some other stream has earlier sample that needs to be played first */
|
||||
if (stream->last_end_time > AUDIO_TOLERANCE)
|
||||
{
|
||||
pending = true;
|
||||
break;
|
||||
freerdp_mutex_lock(presentation->mutex);
|
||||
for (item = presentation->stream_list->head; item; item = item->next)
|
||||
{
|
||||
s = (TSMF_STREAM*) item->data;
|
||||
if (s != stream && !s->eos && s->last_end_time &&
|
||||
s->last_end_time < stream->last_end_time - AUDIO_TOLERANCE)
|
||||
{
|
||||
pending = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
freerdp_mutex_unlock(presentation->mutex);
|
||||
}
|
||||
}
|
||||
freerdp_mutex_unlock(presentation->mutex);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (stream->last_end_time > presentation->audio_end_time)
|
||||
{
|
||||
pending = true;
|
||||
else
|
||||
{
|
||||
if (stream->last_end_time > presentation->audio_end_time)
|
||||
{
|
||||
pending = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (pending)
|
||||
return NULL;
|
||||
|
||||
@ -224,6 +238,15 @@ static void tsmf_stream_process_ack(TSMF_STREAM* stream)
|
||||
|
||||
TSMF_PRESENTATION* tsmf_presentation_new(const uint8* guid, IWTSVirtualChannelCallback* pChannelCallback)
|
||||
{
|
||||
pthread_t thid = pthread_self();
|
||||
FILE* fout = NULL;
|
||||
fout = fopen("/tmp/tsmf.tid", "wt");
|
||||
if (fout)
|
||||
{
|
||||
fprintf(fout, "%d\n", (int) thid);
|
||||
fclose(fout);
|
||||
}
|
||||
|
||||
TSMF_PRESENTATION* presentation;
|
||||
|
||||
presentation = tsmf_presentation_find_by_id(guid);
|
||||
@ -423,7 +446,14 @@ static void tsmf_sample_playback(TSMF_SAMPLE* sample)
|
||||
TSMF_STREAM* stream = sample->stream;
|
||||
|
||||
if (stream->decoder)
|
||||
ret = stream->decoder->Decode(stream->decoder, sample->data, sample->data_size, sample->extensions);
|
||||
{
|
||||
if (stream->decoder->DecodeEx)
|
||||
ret = stream->decoder->DecodeEx(stream->decoder, sample->data, sample->data_size, sample->extensions,
|
||||
sample->start_time, sample->end_time, sample->duration);
|
||||
else
|
||||
ret = stream->decoder->Decode(stream->decoder, sample->data, sample->data_size, sample->extensions);
|
||||
}
|
||||
|
||||
if (!ret)
|
||||
{
|
||||
tsmf_sample_ack(sample);
|
||||
@ -450,32 +480,139 @@ static void tsmf_sample_playback(TSMF_SAMPLE* sample)
|
||||
|
||||
ret = false ;
|
||||
if (stream->decoder->GetDecodedDimension)
|
||||
ret = stream->decoder->GetDecodedDimension(stream->decoder, &width, &height);
|
||||
if (ret && (width != stream->width || height != stream->height))
|
||||
{
|
||||
DEBUG_DVC("video dimension changed to %d x %d", width, height);
|
||||
stream->width = width;
|
||||
stream->height = height;
|
||||
ret = stream->decoder->GetDecodedDimension(stream->decoder, &width, &height);
|
||||
if (ret && (width != stream->width || height != stream->height))
|
||||
{
|
||||
DEBUG_DVC("video dimension changed to %d x %d", width, height);
|
||||
stream->width = width;
|
||||
stream->height = height;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (stream->decoder->GetDecodedData)
|
||||
{
|
||||
sample->data = stream->decoder->GetDecodedData(stream->decoder, &sample->decoded_size);
|
||||
switch (sample->stream->major_type)
|
||||
{
|
||||
case TSMF_MAJOR_TYPE_VIDEO:
|
||||
tsmf_sample_playback_video(sample);
|
||||
tsmf_sample_ack(sample);
|
||||
tsmf_sample_free(sample);
|
||||
break;
|
||||
case TSMF_MAJOR_TYPE_AUDIO:
|
||||
tsmf_sample_playback_audio(sample);
|
||||
tsmf_sample_queue_ack(sample);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch (sample->stream->major_type)
|
||||
else
|
||||
{
|
||||
case TSMF_MAJOR_TYPE_VIDEO:
|
||||
tsmf_sample_playback_video(sample);
|
||||
tsmf_sample_ack(sample);
|
||||
tsmf_sample_free(sample);
|
||||
break;
|
||||
case TSMF_MAJOR_TYPE_AUDIO:
|
||||
tsmf_sample_playback_audio(sample);
|
||||
tsmf_sample_queue_ack(sample);
|
||||
break;
|
||||
}
|
||||
TSMF_STREAM * stream = sample->stream;
|
||||
uint64 ack_anticipation_time = get_current_time();
|
||||
uint64 currentRunningTime = sample->start_time;
|
||||
uint32 bufferLevel = 0;
|
||||
if (stream->decoder->GetRunningTime)
|
||||
{
|
||||
currentRunningTime = stream->decoder->GetRunningTime(stream->decoder);
|
||||
}
|
||||
if (stream->decoder->BufferLevel)
|
||||
{
|
||||
bufferLevel = stream->decoder->BufferLevel(stream->decoder);
|
||||
}
|
||||
switch (sample->stream->major_type)
|
||||
{
|
||||
case TSMF_MAJOR_TYPE_VIDEO:
|
||||
{
|
||||
TSMF_PRESENTATION * presentation = sample->stream->presentation;
|
||||
/*
|
||||
* Tell gstreamer that presentation screen area has moved.
|
||||
* So it can render on the new area.
|
||||
*/
|
||||
if (presentation->last_x != presentation->output_x || presentation->last_y != presentation->output_y ||
|
||||
presentation->last_width != presentation->output_width || presentation->last_height != presentation->output_height)
|
||||
{
|
||||
presentation->last_x = presentation->output_x;
|
||||
presentation->last_y = presentation->output_y;
|
||||
presentation->last_width = presentation->output_width;
|
||||
presentation->last_height = presentation->output_height;
|
||||
if(stream->decoder->UpdateRenderingArea)
|
||||
{
|
||||
stream->decoder->UpdateRenderingArea(stream->decoder, presentation->output_x, presentation->output_y,
|
||||
presentation->output_width, presentation->output_height, presentation->output_num_rects, presentation->output_rects);
|
||||
}
|
||||
}
|
||||
if ( presentation->last_num_rects != presentation->output_num_rects || (presentation->last_rects && presentation->output_rects &&
|
||||
memcmp(presentation->last_rects, presentation->output_rects, presentation->last_num_rects * sizeof(RDP_RECT)) != 0))
|
||||
{
|
||||
if (presentation->last_rects)
|
||||
{
|
||||
xfree(presentation->last_rects);
|
||||
presentation->last_rects = NULL;
|
||||
}
|
||||
presentation->last_num_rects = presentation->output_num_rects;
|
||||
if (presentation->last_num_rects > 0)
|
||||
{
|
||||
presentation->last_rects = xzalloc(presentation->last_num_rects * sizeof(RDP_RECT));
|
||||
memcpy(presentation->last_rects, presentation->output_rects, presentation->last_num_rects * sizeof(RDP_RECT));
|
||||
}
|
||||
if(stream->decoder->UpdateRenderingArea)
|
||||
{
|
||||
stream->decoder->UpdateRenderingArea(stream->decoder, presentation->output_x, presentation->output_y,
|
||||
presentation->output_width, presentation->output_height, presentation->output_num_rects, presentation->output_rects);
|
||||
}
|
||||
}
|
||||
|
||||
if (bufferLevel < 24)
|
||||
{
|
||||
ack_anticipation_time += sample->duration;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (currentRunningTime > sample->start_time)
|
||||
{
|
||||
ack_anticipation_time += sample->duration;
|
||||
}
|
||||
else if(currentRunningTime == 0)
|
||||
{
|
||||
ack_anticipation_time += sample->duration;
|
||||
}
|
||||
else
|
||||
{
|
||||
ack_anticipation_time += (sample->start_time - currentRunningTime);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TSMF_MAJOR_TYPE_AUDIO:
|
||||
{
|
||||
last_played_audio_time = currentRunningTime;
|
||||
if (bufferLevel < 2)
|
||||
{
|
||||
ack_anticipation_time += sample->duration;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (currentRunningTime > sample->start_time)
|
||||
{
|
||||
ack_anticipation_time += sample->duration;
|
||||
}
|
||||
else if(currentRunningTime == 0)
|
||||
{
|
||||
ack_anticipation_time += sample->duration;
|
||||
}
|
||||
else
|
||||
{
|
||||
ack_anticipation_time += (sample->start_time - currentRunningTime);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
sample->ack_time = ack_anticipation_time;
|
||||
tsmf_sample_queue_ack(sample);
|
||||
}
|
||||
}
|
||||
|
||||
static void* tsmf_stream_playback_func(void* arg)
|
||||
@ -489,13 +626,19 @@ static void* tsmf_stream_playback_func(void* arg)
|
||||
if (stream->major_type == TSMF_MAJOR_TYPE_AUDIO &&
|
||||
stream->sample_rate && stream->channels && stream->bits_per_sample)
|
||||
{
|
||||
stream->audio = tsmf_load_audio_device(
|
||||
presentation->audio_name && presentation->audio_name[0] ? presentation->audio_name : NULL,
|
||||
presentation->audio_device && presentation->audio_device[0] ? presentation->audio_device : NULL);
|
||||
if (stream->audio)
|
||||
if (stream->decoder)
|
||||
{
|
||||
stream->audio->SetFormat(stream->audio,
|
||||
stream->sample_rate, stream->channels, stream->bits_per_sample);
|
||||
if (stream->decoder->GetDecodedData)
|
||||
{
|
||||
stream->audio = tsmf_load_audio_device(
|
||||
presentation->audio_name && presentation->audio_name[0] ? presentation->audio_name : NULL,
|
||||
presentation->audio_device && presentation->audio_device[0] ? presentation->audio_device : NULL);
|
||||
if (stream->audio)
|
||||
{
|
||||
stream->audio->SetFormat(stream->audio,
|
||||
stream->sample_rate, stream->channels, stream->bits_per_sample);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
while (!freerdp_thread_is_stopped(stream->thread))
|
||||
@ -535,10 +678,99 @@ static void tsmf_stream_start(TSMF_STREAM* stream)
|
||||
|
||||
static void tsmf_stream_stop(TSMF_STREAM* stream)
|
||||
{
|
||||
if (!stream)
|
||||
return;
|
||||
|
||||
if (!stream->decoder)
|
||||
return;
|
||||
|
||||
if (freerdp_thread_is_running(stream->thread))
|
||||
{
|
||||
freerdp_thread_stop(stream->thread);
|
||||
}
|
||||
if (stream->decoder->Control)
|
||||
{
|
||||
stream->decoder->Control(stream->decoder, Control_Flush, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static void tsmf_stream_pause(TSMF_STREAM* stream)
|
||||
{
|
||||
if (!stream)
|
||||
return;
|
||||
|
||||
if (!stream->decoder)
|
||||
return;
|
||||
|
||||
if (stream->decoder->Control)
|
||||
{
|
||||
stream->decoder->Control(stream->decoder, Control_Pause, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static void tsmf_stream_restart(TSMF_STREAM* stream)
|
||||
{
|
||||
if (!stream)
|
||||
return;
|
||||
|
||||
if (!stream->decoder)
|
||||
return;
|
||||
|
||||
if (stream->decoder->Control)
|
||||
{
|
||||
stream->decoder->Control(stream->decoder, Control_Restart, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static void tsmf_stream_change_volume(TSMF_STREAM* stream, uint32 newVolume, uint32 muted)
|
||||
{
|
||||
if (!stream)
|
||||
return;
|
||||
|
||||
if (!stream->decoder)
|
||||
return;
|
||||
|
||||
if (stream->decoder->ChangeVolume)
|
||||
{
|
||||
stream->decoder->ChangeVolume(stream->decoder, newVolume, muted);
|
||||
}
|
||||
}
|
||||
|
||||
void tsmf_presentation_volume_changed(TSMF_PRESENTATION* presentation, uint32 newVolume, uint32 muted)
|
||||
{
|
||||
LIST_ITEM* item;
|
||||
TSMF_STREAM* stream;
|
||||
|
||||
for (item = presentation->stream_list->head; item; item = item->next)
|
||||
{
|
||||
stream = (TSMF_STREAM*) item->data;
|
||||
tsmf_stream_change_volume(stream, newVolume, muted);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void tsmf_presentation_paused(TSMF_PRESENTATION* presentation)
|
||||
{
|
||||
LIST_ITEM* item;
|
||||
TSMF_STREAM* stream;
|
||||
|
||||
for (item = presentation->stream_list->head; item; item = item->next)
|
||||
{
|
||||
stream = (TSMF_STREAM*) item->data;
|
||||
tsmf_stream_pause(stream);
|
||||
}
|
||||
}
|
||||
|
||||
void tsmf_presentation_restarted(TSMF_PRESENTATION* presentation)
|
||||
{
|
||||
LIST_ITEM* item;
|
||||
TSMF_STREAM* stream;
|
||||
|
||||
for (item = presentation->stream_list->head; item; item = item->next)
|
||||
{
|
||||
stream = (TSMF_STREAM*) item->data;
|
||||
tsmf_stream_restart(stream);
|
||||
}
|
||||
}
|
||||
|
||||
void tsmf_presentation_start(TSMF_PRESENTATION* presentation)
|
||||
@ -645,7 +877,9 @@ void tsmf_presentation_free(TSMF_PRESENTATION* presentation)
|
||||
TSMF_STREAM* stream;
|
||||
|
||||
tsmf_presentation_stop(presentation);
|
||||
freerdp_mutex_lock(presentation->mutex);
|
||||
list_remove(presentation_list, presentation);
|
||||
freerdp_mutex_unlock(presentation->mutex);
|
||||
|
||||
while (list_size(presentation->stream_list) > 0)
|
||||
{
|
||||
@ -757,17 +991,28 @@ void tsmf_stream_free(TSMF_STREAM* stream)
|
||||
list_free(stream->sample_ack_list);
|
||||
|
||||
if (stream->decoder)
|
||||
{
|
||||
stream->decoder->Free(stream->decoder);
|
||||
stream->decoder = 0;
|
||||
}
|
||||
|
||||
freerdp_thread_free(stream->thread);
|
||||
|
||||
xfree(stream);
|
||||
stream = 0;
|
||||
}
|
||||
|
||||
void tsmf_stream_push_sample(TSMF_STREAM* stream, IWTSVirtualChannelCallback* pChannelCallback,
|
||||
uint32 sample_id, uint64 start_time, uint64 end_time, uint64 duration, uint32 extensions,
|
||||
uint32 data_size, uint8* data)
|
||||
{
|
||||
pthread_mutex_lock(&tsmf_mutex);
|
||||
if (TERMINATING)
|
||||
{
|
||||
pthread_mutex_unlock(&tsmf_mutex);
|
||||
return;
|
||||
}
|
||||
pthread_mutex_unlock(&tsmf_mutex);
|
||||
TSMF_SAMPLE* sample;
|
||||
|
||||
sample = xnew(TSMF_SAMPLE);
|
||||
@ -788,8 +1033,51 @@ void tsmf_stream_push_sample(TSMF_STREAM* stream, IWTSVirtualChannelCallback* pC
|
||||
freerdp_thread_unlock(stream->thread);
|
||||
}
|
||||
|
||||
static void tsmf_signal_handler(int s)
|
||||
{
|
||||
pthread_mutex_lock(&tsmf_mutex);
|
||||
TERMINATING = 1;
|
||||
pthread_mutex_unlock(&tsmf_mutex);
|
||||
LIST_ITEM* p_item;
|
||||
TSMF_PRESENTATION* presentation;
|
||||
LIST_ITEM* s_item;
|
||||
TSMF_STREAM* _stream;
|
||||
if (presentation_list)
|
||||
{
|
||||
for (p_item = presentation_list->head; p_item; p_item = p_item->next)
|
||||
{
|
||||
presentation = (TSMF_PRESENTATION*) p_item->data;
|
||||
for (s_item = presentation->stream_list->head; s_item; s_item = s_item->next)
|
||||
{
|
||||
_stream = (TSMF_STREAM*) s_item->data;
|
||||
tsmf_stream_free(_stream);
|
||||
}
|
||||
tsmf_presentation_free(presentation);
|
||||
}
|
||||
}
|
||||
|
||||
unlink("/tmp/tsmf.tid");
|
||||
|
||||
if (s == SIGINT)
|
||||
{
|
||||
signal(s, SIG_DFL);
|
||||
kill(getpid(), s);
|
||||
}
|
||||
else if (s == SIGUSR1)
|
||||
{
|
||||
signal(s, SIG_DFL);
|
||||
}
|
||||
}
|
||||
|
||||
void tsmf_media_init(void)
|
||||
{
|
||||
struct sigaction sigtrap;
|
||||
sigtrap.sa_handler = tsmf_signal_handler;
|
||||
sigemptyset(&sigtrap.sa_mask);
|
||||
sigtrap.sa_flags = 0;
|
||||
sigaction(SIGINT, &sigtrap, 0);
|
||||
sigaction(SIGUSR1, &sigtrap, 0);
|
||||
|
||||
if (presentation_list == NULL)
|
||||
presentation_list = list_new();
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
* Video Redirection Virtual Channel - Media Container
|
||||
*
|
||||
* Copyright 2010-2011 Vic Lee
|
||||
* Copyright 2012 Hewlett-Packard Development Company, L.P.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -35,6 +36,9 @@ TSMF_PRESENTATION* tsmf_presentation_new(const uint8* guid, IWTSVirtualChannelCa
|
||||
TSMF_PRESENTATION* tsmf_presentation_find_by_id(const uint8* guid);
|
||||
void tsmf_presentation_start(TSMF_PRESENTATION* presentation);
|
||||
void tsmf_presentation_stop(TSMF_PRESENTATION* presentation);
|
||||
void tsmf_presentation_paused(TSMF_PRESENTATION* presentation);
|
||||
void tsmf_presentation_restarted(TSMF_PRESENTATION* presentation);
|
||||
void tsmf_presentation_volume_changed(TSMF_PRESENTATION* presentation, uint32 newVolume, uint32 muted);
|
||||
void tsmf_presentation_set_geometry_info(TSMF_PRESENTATION* presentation,
|
||||
uint32 x, uint32 y, uint32 width, uint32 height,
|
||||
int num_rects, RDP_RECT* rects);
|
||||
|
@ -3,6 +3,7 @@
|
||||
* X11 Windows
|
||||
*
|
||||
* Copyright 2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
|
||||
* Copyright 2012 HP Development Company, LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -21,6 +22,9 @@
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/Xutil.h>
|
||||
#include <X11/Xatom.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/shm.h>
|
||||
#include <sys/ipc.h>
|
||||
|
||||
#include <freerdp/rail.h>
|
||||
#include <freerdp/utils/rail.h>
|
||||
@ -61,6 +65,9 @@
|
||||
|
||||
#define PROP_MOTIF_WM_HINTS_ELEMENTS 5
|
||||
|
||||
/*to be accessed by gstreamer plugin*/
|
||||
#define SHARED_MEM_KEY 7777
|
||||
|
||||
struct _PropMotifWmHints
|
||||
{
|
||||
unsigned long flags;
|
||||
@ -294,6 +301,24 @@ xfWindow* xf_CreateDesktopWindow(xfInfo* xfi, char* name, int width, int height,
|
||||
CWBackPixel | CWBackingStore | CWOverrideRedirect | CWColormap |
|
||||
CWBorderPixel | CWWinGravity | CWBitGravity, &xfi->attribs);
|
||||
|
||||
int shmid = shmget(SHARED_MEM_KEY, sizeof(int), IPC_CREAT | 0666);
|
||||
if (shmid < 0)
|
||||
{
|
||||
DEBUG_X11("xf_CreateDesktopWindow: failed to get access to shared memory - shmget()\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
int *xfwin = shmat(shmid, NULL, 0);
|
||||
if (xfwin == (int *) -1)
|
||||
{
|
||||
DEBUG_X11("xf_CreateDesktopWindow: failed to assign pointer to the memory address - shmat()\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
*xfwin = (int)window->handle;
|
||||
}
|
||||
}
|
||||
|
||||
class_hints = XAllocClassHint();
|
||||
|
||||
if (class_hints != NULL)
|
||||
|
@ -3,6 +3,7 @@
|
||||
* X11 Client
|
||||
*
|
||||
* Copyright 2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
|
||||
* Copyright 2012 HP Development Company, LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -1156,6 +1157,31 @@ int xfreerdp_run(freerdp* instance)
|
||||
xf_process_channel_event(channels, instance);
|
||||
}
|
||||
|
||||
FILE *fin = fopen("/tmp/tsmf.tid", "rt");
|
||||
if(fin)
|
||||
{
|
||||
int thid = 0;
|
||||
fscanf(fin, "%d", &thid);
|
||||
fclose(fin);
|
||||
pthread_kill((pthread_t) thid, SIGUSR1);
|
||||
|
||||
FILE *fin1 = fopen("/tmp/tsmf.tid", "rt");
|
||||
int timeout = 5;
|
||||
while (fin1)
|
||||
{
|
||||
fclose(fin1);
|
||||
sleep(1);
|
||||
timeout--;
|
||||
if (timeout <= 0)
|
||||
{
|
||||
unlink("/tmp/tsmf.tid");
|
||||
pthread_kill((pthread_t) thid, SIGKILL);
|
||||
break;
|
||||
}
|
||||
fin1 = fopen("/tmp/tsmf.tid", "rt");
|
||||
}
|
||||
}
|
||||
|
||||
if (!ret)
|
||||
ret = freerdp_error_info(instance);
|
||||
|
||||
|
2
cmake/FindGstreamer.cmake
Normal file
2
cmake/FindGstreamer.cmake
Normal file
@ -0,0 +1,2 @@
|
||||
pkg_check_modules(GSTREAMER gstreamer-plugins-base-0.10 gstreamer-0.10)
|
||||
|
Loading…
Reference in New Issue
Block a user