1 /*
2 mediastreamer2 library - modular sound and video processing and streaming
3 Copyright (C) 2006 Simon MORLAT (simon.morlat@linphone.org)
4
5 This program is free software; you can redistribute it and/or
6 modify it under the terms of the GNU General Public License
7 as published by the Free Software Foundation; either version 2
8 of the License, or (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 */
19
20 #include <math.h>
21
22 #include "mediastreamer2/mediastream.h"
23 #include "mediastreamer2/msfilter.h"
24 #include "mediastreamer2/msinterfaces.h"
25 #include "mediastreamer2/msvideo.h"
26 #include "mediastreamer2/msrtp.h"
27 #include "mediastreamer2/msvideoout.h"
28 #include "mediastreamer2/msextdisplay.h"
29 #include "mediastreamer2/msitc.h"
30 #include "mediastreamer2/zrtp.h"
31 #include "mediastreamer2/msvideopresets.h"
32 #include "mediastreamer2/mseventqueue.h"
33 #include "private.h"
34
35 #if __APPLE__
36 #include "TargetConditionals.h"
37 #endif
38
39 static void configure_recorder_output(VideoStream *stream);
40 static int video_stream_start_with_source_and_output(VideoStream *stream, RtpProfile *profile, const char *rem_rtp_ip, int rem_rtp_port,
41 const char *rem_rtcp_ip, int rem_rtcp_port, int payload, int jitt_comp, MSWebCam *cam, MSFilter *source, MSFilter *output);
42
video_stream_free(VideoStream * stream)43 void video_stream_free(VideoStream *stream) {
44 bool_t rtp_source = FALSE;
45 bool_t rtp_output = FALSE;
46
47 if ((stream->source != NULL) && (ms_filter_get_id(stream->source) == MS_RTP_RECV_ID))
48 rtp_source = TRUE;
49 if ((stream->output != NULL) && (ms_filter_get_id(stream->output) == MS_RTP_SEND_ID))
50 rtp_output = TRUE;
51
52 /* Prevent filters from being destroyed two times */
53 if ((stream->source_performs_encoding == TRUE) || (rtp_source == TRUE)) {
54 stream->ms.encoder = NULL;
55 }
56 if ((stream->output_performs_decoding == TRUE) || (rtp_output == TRUE)) {
57 stream->ms.decoder = NULL;
58 }
59
60 media_stream_free(&stream->ms);
61
62 if (stream->void_source != NULL)
63 ms_filter_destroy(stream->void_source);
64 if (stream->source != NULL)
65 ms_filter_destroy (stream->source);
66 if (stream->output != NULL)
67 ms_filter_destroy (stream->output);
68 if (stream->sizeconv != NULL)
69 ms_filter_destroy (stream->sizeconv);
70 if (stream->pixconv!=NULL)
71 ms_filter_destroy(stream->pixconv);
72 if (stream->tee!=NULL)
73 ms_filter_destroy(stream->tee);
74 if (stream->tee2!=NULL)
75 ms_filter_destroy(stream->tee2);
76 if (stream->jpegwriter!=NULL)
77 ms_filter_destroy(stream->jpegwriter);
78 if (stream->output2!=NULL)
79 ms_filter_destroy(stream->output2);
80 if (stream->tee3)
81 ms_filter_destroy(stream->tee3);
82 if (stream->recorder_output)
83 ms_filter_destroy(stream->recorder_output);
84 if (stream->local_jpegwriter)
85 ms_filter_destroy(stream->local_jpegwriter);
86 if (stream->rtp_io_session)
87 rtp_session_destroy(stream->rtp_io_session);
88
89 if (stream->display_name!=NULL)
90 ms_free(stream->display_name);
91 if (stream->preset != NULL) ms_free(stream->preset);
92
93 ms_free(stream);
94 }
95
event_cb(void * ud,MSFilter * f,unsigned int event,void * eventdata)96 static void event_cb(void *ud, MSFilter* f, unsigned int event, void *eventdata){
97 VideoStream *st=(VideoStream*)ud;
98 if (st->eventcb!=NULL){
99 st->eventcb(st->event_pointer,f,event,eventdata);
100 }
101 }
102
internal_event_cb(void * ud,MSFilter * f,unsigned int event,void * eventdata)103 static void internal_event_cb(void *ud, MSFilter *f, unsigned int event, void *eventdata) {
104 VideoStream *stream = (VideoStream *)ud;
105 const MSVideoCodecSLI *sli;
106 const MSVideoCodecRPSI *rpsi;
107
108 switch (event) {
109 case MS_VIDEO_DECODER_SEND_PLI:
110 ms_message("Request sending of PLI on videostream [%p]", stream);
111 video_stream_send_pli(stream);
112 break;
113 case MS_VIDEO_DECODER_SEND_SLI:
114 sli = (const MSVideoCodecSLI *)eventdata;
115 ms_message("Request sending of SLI on videostream [%p]", stream);
116 video_stream_send_sli(stream, sli->first, sli->number, sli->picture_id);
117 break;
118 case MS_VIDEO_DECODER_SEND_RPSI:
119 rpsi = (const MSVideoCodecRPSI *)eventdata;
120 ms_message("Request sending of RPSI on videostream [%p]", stream);
121 video_stream_send_rpsi(stream, rpsi->bit_string, rpsi->bit_string_len);
122 break;
123 case MS_FILTER_OUTPUT_FMT_CHANGED:
124 if (stream->recorder_output) configure_recorder_output(stream);
125 break;
126 }
127 }
128
video_stream_process_rtcp(MediaStream * media_stream,mblk_t * m)129 static void video_stream_process_rtcp(MediaStream *media_stream, mblk_t *m){
130 VideoStream *stream = (VideoStream *)media_stream;
131 int i;
132
133 if (rtcp_is_PSFB(m) && (stream->ms.encoder != NULL)) {
134 /* The PSFB messages are to be notified to the encoder, so if we have no encoder simply ignore them. */
135 if (rtcp_PSFB_get_media_source_ssrc(m) == rtp_session_get_send_ssrc(stream->ms.sessions.rtp_session)) {
136 switch (rtcp_PSFB_get_type(m)) {
137 case RTCP_PSFB_FIR:
138 for (i = 0; ; i++) {
139 rtcp_fb_fir_fci_t *fci = rtcp_PSFB_fir_get_fci(m, i);
140 if (fci == NULL) break;
141 if (rtcp_fb_fir_fci_get_ssrc(fci) == rtp_session_get_recv_ssrc(stream->ms.sessions.rtp_session)) {
142 uint8_t seq_nr = rtcp_fb_fir_fci_get_seq_nr(fci);
143 ms_filter_call_method(stream->ms.encoder, MS_VIDEO_ENCODER_NOTIFY_FIR, &seq_nr);
144 stream->ms_video_stat.counter_rcvd_fir++;
145 ms_message("video_stream_process_rtcp stream [%p] FIR count %d", stream, stream->ms_video_stat.counter_rcvd_fir);
146
147 break;
148 }
149 }
150 break;
151 case RTCP_PSFB_PLI:
152
153 stream->ms_video_stat.counter_rcvd_pli++;
154 ms_filter_call_method_noarg(stream->ms.encoder, MS_VIDEO_ENCODER_NOTIFY_PLI);
155 ms_message("video_stream_process_rtcp stream [%p] PLI count %d", stream, stream->ms_video_stat.counter_rcvd_pli);
156
157 break;
158 case RTCP_PSFB_SLI:
159 for (i = 0; ; i++) {
160
161 rtcp_fb_sli_fci_t *fci = rtcp_PSFB_sli_get_fci(m, i);
162 MSVideoCodecSLI sli;
163 if (fci == NULL) break;
164 sli.first = rtcp_fb_sli_fci_get_first(fci);
165 sli.number = rtcp_fb_sli_fci_get_number(fci);
166 sli.picture_id = rtcp_fb_sli_fci_get_picture_id(fci);
167 ms_filter_call_method(stream->ms.encoder, MS_VIDEO_ENCODER_NOTIFY_SLI, &sli);
168 stream->ms_video_stat.counter_rcvd_sli++;
169 ms_message("video_stream_process_rtcp stream [%p] SLI count %d", stream, stream->ms_video_stat.counter_rcvd_sli);
170
171 }
172 break;
173 case RTCP_PSFB_RPSI:
174 {
175 rtcp_fb_rpsi_fci_t *fci = rtcp_PSFB_rpsi_get_fci(m);
176 MSVideoCodecRPSI rpsi;
177 rpsi.bit_string = rtcp_fb_rpsi_fci_get_bit_string(fci);
178 rpsi.bit_string_len = rtcp_PSFB_rpsi_get_fci_bit_string_len(m);
179 ms_filter_call_method(stream->ms.encoder, MS_VIDEO_ENCODER_NOTIFY_RPSI, &rpsi);
180 stream->ms_video_stat.counter_rcvd_rpsi++;
181 ms_message("video_stream_process_rtcp stream [%p] RPSI count %d", stream, stream->ms_video_stat.counter_rcvd_rpsi);
182 }
183 break;
184 default:
185 break;
186 }
187 }
188 }
189 }
190
stop_preload_graph(VideoStream * stream)191 static void stop_preload_graph(VideoStream *stream){
192 ms_ticker_detach(stream->ms.sessions.ticker,stream->ms.rtprecv);
193 ms_filter_unlink(stream->ms.rtprecv,0,stream->ms.voidsink,0);
194 ms_filter_destroy(stream->ms.voidsink);
195 ms_filter_destroy(stream->ms.rtprecv);
196 stream->ms.voidsink=stream->ms.rtprecv=NULL;
197 }
198
video_stream_track_fps_changes(VideoStream * stream)199 static void video_stream_track_fps_changes(VideoStream *stream){
200 uint64_t curtime=ortp_get_cur_time_ms();
201 if (stream->last_fps_check==(uint64_t)-1){
202 stream->last_fps_check=curtime;
203 return;
204 }
205 if (curtime-stream->last_fps_check>=2000 && stream->configured_fps>0 && stream->ms.sessions.ticker){
206 MSTickerLateEvent late_ev={0};
207 /*we must check that no late tick occured during the last 2 seconds, otherwise the fps measurement is severely biased.*/
208 ms_ticker_get_last_late_tick(stream->ms.sessions.ticker,&late_ev);
209
210 if (curtime > late_ev.time + 2000){
211 if (stream->source && stream->ms.encoder &&
212 ms_filter_has_method(stream->source,MS_FILTER_GET_FPS) &&
213 ms_filter_has_method(stream->ms.encoder,MS_FILTER_SET_FPS)){
214 float fps=0;
215
216 if (ms_filter_call_method(stream->source,MS_FILTER_GET_FPS,&fps)==0 && fps!=0){
217 if (fabsf(fps-stream->configured_fps)/stream->configured_fps>0.2){
218 ms_warning("Measured and target fps significantly different (%f<->%f), updating encoder.",
219 fps,stream->configured_fps);
220 stream->real_fps=fps;
221 ms_filter_call_method(stream->ms.encoder,MS_FILTER_SET_FPS,&stream->real_fps);
222 }
223 }
224 }
225 stream->last_fps_check=curtime;
226 }
227 }
228 }
229
video_stream_iterate(VideoStream * stream)230 void video_stream_iterate(VideoStream *stream){
231 media_stream_iterate(&stream->ms);
232 video_stream_track_fps_changes(stream);
233 }
234
video_stream_get_default_video_renderer(void)235 const char *video_stream_get_default_video_renderer(void){
236 #if defined(MS2_WINDOWS_UNIVERSAL)
237 return "MSWinRTBackgroundDis";
238 #elif defined(MS2_WINDOWS_PHONE)
239 return "MSWP8Dis";
240 #elif defined(MS2_WINDOWS_DESKTOP)
241 return "MSDrawDibDisplay";
242 #elif defined(__ANDROID__)
243 return "MSAndroidDisplay";
244 #elif __APPLE__ && !TARGET_OS_IPHONE
245 return "MSOSXGLDisplay";
246 #elif defined (HAVE_XV)
247 return "MSX11Video";
248 #elif defined(HAVE_GLX)
249 return "MSGLXVideo";
250 #elif TARGET_OS_IPHONE
251 return "IOSDisplay";
252 #elif defined(__QNX__)
253 return "MSBB10Display";
254 #else
255 return "MSVideoOut";
256 #endif
257 }
258
choose_display_name(VideoStream * stream)259 static void choose_display_name(VideoStream *stream){
260 stream->display_name=ms_strdup(video_stream_get_default_video_renderer());
261 }
262
video_stream_get_rtcp_xr_average_quality_rating(void * userdata)263 static float video_stream_get_rtcp_xr_average_quality_rating(void *userdata) {
264 VideoStream *stream = (VideoStream *)userdata;
265 return stream ? media_stream_get_average_quality_rating(&stream->ms) : -1;
266 }
267
video_stream_get_rtcp_xr_average_lq_quality_rating(void * userdata)268 static float video_stream_get_rtcp_xr_average_lq_quality_rating(void *userdata) {
269 VideoStream *stream = (VideoStream *)userdata;
270 return stream ? media_stream_get_average_lq_quality_rating(&stream->ms) : -1;
271 }
272
273
video_stream_new(MSFactory * factory,int loc_rtp_port,int loc_rtcp_port,bool_t use_ipv6)274 VideoStream *video_stream_new(MSFactory* factory, int loc_rtp_port, int loc_rtcp_port, bool_t use_ipv6){
275 return video_stream_new2(factory, use_ipv6 ? "::" : "0.0.0.0", loc_rtp_port, loc_rtcp_port);
276 }
277
video_stream_new2(MSFactory * factory,const char * ip,int loc_rtp_port,int loc_rtcp_port)278 VideoStream *video_stream_new2(MSFactory* factory, const char* ip, int loc_rtp_port, int loc_rtcp_port) {
279 MSMediaStreamSessions sessions={0};
280 VideoStream *obj;
281 sessions.rtp_session=ms_create_duplex_rtp_session(ip,loc_rtp_port,loc_rtcp_port, ms_factory_get_mtu(factory));
282 obj=video_stream_new_with_sessions(factory, &sessions);
283 obj->ms.owns_sessions=TRUE;
284 return obj;
285 }
286
287
video_stream_new_with_sessions(MSFactory * factory,const MSMediaStreamSessions * sessions)288 VideoStream *video_stream_new_with_sessions(MSFactory* factory, const MSMediaStreamSessions *sessions){
289 VideoStream *stream = (VideoStream *)ms_new0 (VideoStream, 1);
290 const OrtpRtcpXrMediaCallbacks rtcp_xr_media_cbs = {
291 NULL,
292 NULL,
293 NULL,
294 video_stream_get_rtcp_xr_average_quality_rating,
295 video_stream_get_rtcp_xr_average_lq_quality_rating,
296 stream
297 };
298
299 stream->ms.type = MSVideo;
300 stream->ms.sessions=*sessions;
301
302 media_stream_init(&stream->ms, factory, sessions);
303
304 rtp_session_resync(stream->ms.sessions.rtp_session);
305 stream->ms.qi=ms_quality_indicator_new(stream->ms.sessions.rtp_session);
306 ms_quality_indicator_set_label(stream->ms.qi,"video");
307
308 stream->ms.rtpsend=ms_factory_create_filter(stream->ms.factory, MS_RTP_SEND_ID);
309
310 stream->ms.ice_check_list=NULL;
311 MS_VIDEO_SIZE_ASSIGN(stream->sent_vsize, CIF);
312 stream->forced_fps=0;
313 stream->real_fps=0;
314 stream->dir=MediaStreamSendRecv;
315 stream->display_filter_auto_rotate_enabled=0;
316 stream->freeze_on_error = FALSE;
317 stream->source_performs_encoding = FALSE;
318 stream->output_performs_decoding = FALSE;
319 choose_display_name(stream);
320 stream->ms.process_rtcp=video_stream_process_rtcp;
321 /*
322 * In practice, these filters are needed only for audio+video recording.
323 */
324 if (ms_factory_lookup_filter_by_id(stream->ms.factory, MS_MKV_RECORDER_ID)){
325
326 stream->tee3=ms_factory_create_filter(stream->ms.factory, MS_TEE_ID);
327 stream->recorder_output=ms_factory_create_filter(stream->ms.factory, MS_ITC_SINK_ID);
328
329 }
330
331 rtp_session_set_rtcp_xr_media_callbacks(stream->ms.sessions.rtp_session, &rtcp_xr_media_cbs);
332
333 stream->staticimage_webcam_fps_optimization = TRUE;
334
335 return stream;
336 }
337
video_stream_set_sent_video_size(VideoStream * stream,MSVideoSize vsize)338 void video_stream_set_sent_video_size(VideoStream *stream, MSVideoSize vsize){
339 ms_message("Setting video size %dx%d on stream [%p]", vsize.width, vsize.height,stream);
340 stream->sent_vsize=vsize;
341 }
342
video_stream_set_preview_size(VideoStream * stream,MSVideoSize vsize)343 void video_stream_set_preview_size(VideoStream *stream, MSVideoSize vsize){
344 ms_message("Setting preview video size %dx%d", vsize.width, vsize.height);
345 stream->preview_vsize=vsize;
346 }
347
video_stream_set_fps(VideoStream * stream,float fps)348 void video_stream_set_fps(VideoStream *stream, float fps){
349 stream->forced_fps=fps;
350 }
351
video_stream_get_sent_video_size(const VideoStream * stream)352 MSVideoSize video_stream_get_sent_video_size(const VideoStream *stream) {
353 MSVideoSize vsize;
354 MS_VIDEO_SIZE_ASSIGN(vsize, UNKNOWN);
355 if (stream->ms.encoder != NULL) {
356 ms_filter_call_method(stream->ms.encoder, MS_FILTER_GET_VIDEO_SIZE, &vsize);
357 }
358 return vsize;
359 }
360
video_stream_get_received_video_size(const VideoStream * stream)361 MSVideoSize video_stream_get_received_video_size(const VideoStream *stream) {
362 MSVideoSize vsize;
363 MS_VIDEO_SIZE_ASSIGN(vsize, UNKNOWN);
364 if (stream->ms.decoder != NULL) {
365 ms_filter_call_method(stream->ms.decoder, MS_FILTER_GET_VIDEO_SIZE, &vsize);
366 }
367 return vsize;
368 }
369
video_stream_get_sent_framerate(const VideoStream * stream)370 float video_stream_get_sent_framerate(const VideoStream *stream){
371 float fps=0;
372 if (stream->source){
373 if (ms_filter_has_method(stream->source, MS_FILTER_GET_FPS)){
374 ms_filter_call_method(stream->source,MS_FILTER_GET_FPS,&fps);
375 }else if (stream->pixconv && ms_filter_has_method(stream->pixconv, MS_FILTER_GET_FPS)){
376 ms_filter_call_method(stream->pixconv,MS_FILTER_GET_FPS,&fps);
377 }
378 }
379 return fps;
380 }
381
video_stream_get_received_framerate(const VideoStream * stream)382 float video_stream_get_received_framerate(const VideoStream *stream){
383 float fps=0;
384 if (stream->ms.decoder != NULL && ms_filter_has_method(stream->ms.decoder, MS_FILTER_GET_FPS)) {
385 ms_filter_call_method(stream->ms.decoder, MS_FILTER_GET_FPS, &fps);
386 }
387 return fps;
388 }
389
video_stream_set_relay_session_id(VideoStream * stream,const char * id)390 void video_stream_set_relay_session_id(VideoStream *stream, const char *id){
391 ms_filter_call_method(stream->ms.rtpsend, MS_RTP_SEND_SET_RELAY_SESSION_ID,(void*)id);
392 }
393
video_stream_enable_self_view(VideoStream * stream,bool_t val)394 void video_stream_enable_self_view(VideoStream *stream, bool_t val){
395 MSFilter *out=stream->output;
396 stream->corner=val ? 0 : -1;
397 if (out){
398 ms_filter_call_method(out,MS_VIDEO_DISPLAY_SET_LOCAL_VIEW_MODE,&stream->corner);
399 }
400 }
401
video_stream_set_render_callback(VideoStream * s,VideoStreamRenderCallback cb,void * user_pointer)402 void video_stream_set_render_callback (VideoStream *s, VideoStreamRenderCallback cb, void *user_pointer){
403 s->rendercb=cb;
404 s->render_pointer=user_pointer;
405 }
406
video_stream_set_event_callback(VideoStream * s,VideoStreamEventCallback cb,void * user_pointer)407 void video_stream_set_event_callback (VideoStream *s, VideoStreamEventCallback cb, void *user_pointer){
408 s->eventcb=cb;
409 s->event_pointer=user_pointer;
410 }
411
video_stream_set_display_filter_name(VideoStream * s,const char * fname)412 void video_stream_set_display_filter_name(VideoStream *s, const char *fname){
413 if (s->display_name!=NULL){
414 ms_free(s->display_name);
415 s->display_name=NULL;
416 }
417 if (fname!=NULL)
418 s->display_name=ms_strdup(fname);
419 }
420
421
ext_display_cb(void * ud,MSFilter * f,unsigned int event,void * eventdata)422 static void ext_display_cb(void *ud, MSFilter* f, unsigned int event, void *eventdata){
423 MSExtDisplayOutput *output=(MSExtDisplayOutput*)eventdata;
424 VideoStream *st=(VideoStream*)ud;
425 if (st->rendercb!=NULL){
426 st->rendercb(st->render_pointer,
427 output->local_view.w!=0 ? &output->local_view : NULL,
428 output->remote_view.w!=0 ? &output->remote_view : NULL);
429 }
430 }
431
video_stream_set_direction(VideoStream * vs,MediaStreamDir dir)432 void video_stream_set_direction(VideoStream *vs, MediaStreamDir dir){
433 vs->dir=dir;
434 }
435
get_compatible_size(MSVideoSize maxsize,MSVideoSize wished_size)436 static MSVideoSize get_compatible_size(MSVideoSize maxsize, MSVideoSize wished_size){
437 int max_area=maxsize.width*maxsize.height;
438 int whished_area=wished_size.width*wished_size.height;
439 if (whished_area>max_area){
440 return maxsize;
441 }
442 return wished_size;
443 }
444
445 #if !TARGET_IPHONE_SIMULATOR && !defined(MS_HAS_ARM)
get_with_same_orientation_and_ratio(MSVideoSize size,MSVideoSize refsize)446 static MSVideoSize get_with_same_orientation_and_ratio(MSVideoSize size, MSVideoSize refsize){
447 if (ms_video_size_get_orientation(refsize)!=ms_video_size_get_orientation(size)){
448 int tmp;
449 tmp=size.width;
450 size.width=size.height;
451 size.height=tmp;
452 }
453 size.height=(size.width*refsize.height)/refsize.width;
454 return size;
455 }
456 #endif
457
configure_video_source(VideoStream * stream)458 static void configure_video_source(VideoStream *stream){
459 MSVideoSize vsize,cam_vsize;
460 float fps=15;
461 int bitrate;
462 MSPixFmt format=MS_PIX_FMT_UNKNOWN;
463 MSVideoEncoderPixFmt encoder_supports_source_format;
464 int ret;
465 MSVideoSize preview_vsize;
466 MSPinFormat pf={0};
467 bool_t is_player=ms_filter_get_id(stream->source)==MS_ITC_SOURCE_ID || ms_filter_get_id(stream->source)==MS_MKV_PLAYER_ID;
468
469
470 /* transmit orientation to source filter */
471 if (ms_filter_has_method(stream->source, MS_VIDEO_CAPTURE_SET_DEVICE_ORIENTATION))
472 ms_filter_call_method(stream->source,MS_VIDEO_CAPTURE_SET_DEVICE_ORIENTATION,&stream->device_orientation);
473 /* initialize the capture device orientation for preview */
474 if (!stream->display_filter_auto_rotate_enabled && ms_filter_has_method(stream->source, MS_VIDEO_DISPLAY_SET_DEVICE_ORIENTATION))
475 ms_filter_call_method(stream->source,MS_VIDEO_DISPLAY_SET_DEVICE_ORIENTATION,&stream->device_orientation);
476
477 /* transmit its preview window id if any to source filter*/
478 if (stream->preview_window_id!=0){
479 video_stream_set_native_preview_window_id(stream, stream->preview_window_id);
480 }
481
482 ms_filter_call_method(stream->ms.encoder, MS_FILTER_GET_BITRATE, &bitrate);
483 if (bitrate == 0) {
484 bitrate = ms_factory_get_expected_bandwidth(stream->ms.factory);
485 ms_message("Encoder current bitrate is 0, using expected bandwidth %i", bitrate);
486 ms_filter_call_method(stream->ms.encoder, MS_FILTER_SET_BITRATE, &bitrate);
487 }
488
489 ms_filter_call_method(stream->ms.encoder,MS_FILTER_GET_VIDEO_SIZE,&vsize);
490 vsize=get_compatible_size(vsize,stream->sent_vsize);
491 if (stream->preview_vsize.width!=0){
492 preview_vsize=stream->preview_vsize;
493 }else{
494 preview_vsize=vsize;
495 }
496
497 if (is_player){
498 ms_filter_call_method(stream->source,MS_FILTER_GET_OUTPUT_FMT,&pf);
499 if (pf.fmt==NULL || pf.fmt->vsize.width==0){
500 MSVideoSize vsize={640,480};
501 ms_error("Player does not give its format correctly [%s]",ms_fmt_descriptor_to_string(pf.fmt));
502 /*put a default format as the error handling is complicated here*/
503 pf.fmt=ms_factory_get_video_format(stream->ms.factory,"VP8",vsize,0,NULL);
504 }
505 cam_vsize=pf.fmt->vsize;
506 }else{
507 ms_filter_call_method(stream->source,MS_FILTER_SET_VIDEO_SIZE,&preview_vsize);
508 /*the camera may not support the target size and suggest a one close to the target */
509 ms_filter_call_method(stream->source,MS_FILTER_GET_VIDEO_SIZE,&cam_vsize);
510 }
511
512 if (cam_vsize.width*cam_vsize.height<=vsize.width*vsize.height){
513 vsize=cam_vsize;
514 ms_message("Output video size adjusted to match camera resolution (%ix%i)",vsize.width,vsize.height);
515 } else {
516 #if TARGET_IPHONE_SIMULATOR || defined(MS_HAS_ARM) || defined(MS2_WINDOWS_UNIVERSAL)
517 ms_error("Camera is proposing a size bigger than encoder's suggested size (%ix%i > %ix%i) "
518 "Using the camera size as fallback because cropping or resizing is not implemented for this device.",
519 cam_vsize.width,cam_vsize.height,vsize.width,vsize.height);
520 vsize=cam_vsize;
521 #else
522 MSVideoSize resized=get_with_same_orientation_and_ratio(vsize,cam_vsize);
523 if (resized.width & 0x1 || resized.height & 0x1){
524 ms_warning("Resizing avoided because downsizing to an odd number of pixels (%ix%i)",resized.width,resized.height);
525 vsize=cam_vsize;
526 }else{
527 vsize=resized;
528 ms_warning("Camera video size greater than encoder one. A scaling filter will be used!");
529 }
530 #endif
531 }
532 ms_filter_call_method(stream->ms.encoder,MS_FILTER_SET_VIDEO_SIZE,&vsize);
533 ms_filter_call_method(stream->ms.encoder,MS_FILTER_GET_FPS,&fps);
534
535 if (is_player){
536 fps=pf.fmt->fps;
537 if (fps==0) fps=15;
538 ms_filter_call_method(stream->ms.encoder,MS_FILTER_SET_FPS,&fps);
539 }else{
540 if (stream->forced_fps!=0)
541 fps=stream->forced_fps;
542 ms_message("Setting sent vsize=%ix%i, fps=%f",vsize.width,vsize.height,fps);
543 /* configure the filters */
544 if (ms_filter_get_id(stream->source)!=MS_STATIC_IMAGE_ID || !stream->staticimage_webcam_fps_optimization) {
545 ms_filter_call_method(stream->source,MS_FILTER_SET_FPS,&fps);
546 }
547 ms_filter_call_method(stream->ms.encoder,MS_FILTER_SET_FPS,&fps);
548 /* get the output format for webcam reader */
549 ms_filter_call_method(stream->source,MS_FILTER_GET_PIX_FMT,&format);
550 }
551 stream->configured_fps=fps;
552
553 encoder_supports_source_format.supported = FALSE;
554 encoder_supports_source_format.pixfmt = format;
555
556 if (ms_filter_has_method(stream->ms.encoder, MS_VIDEO_ENCODER_SUPPORTS_PIXFMT) == TRUE) {
557 ret = ms_filter_call_method(stream->ms.encoder, MS_VIDEO_ENCODER_SUPPORTS_PIXFMT, &encoder_supports_source_format);
558 } else {
559 ret = -1;
560 }
561 if (ret == -1) {
562 /*encoder doesn't have MS_VIDEO_ENCODER_SUPPORTS_PIXFMT method*/
563 /*we prefer in this case consider that it is not required to get the optimization of not going through pixconv and sizeconv*/
564 encoder_supports_source_format.supported = FALSE;
565 }
566
567 if ((encoder_supports_source_format.supported == TRUE) || (stream->source_performs_encoding == TRUE)) {
568 ms_filter_call_method(stream->ms.encoder, MS_FILTER_SET_PIX_FMT, &format);
569 } else {
570 if (format==MS_MJPEG){
571 stream->pixconv=ms_factory_create_filter(stream->ms.factory, MS_MJPEG_DEC_ID);
572 if (stream->pixconv == NULL){
573 ms_error("Could not create mjpeg decoder, check your build options.");
574 }
575 }else if (format==MS_PIX_FMT_UNKNOWN){
576 stream->pixconv = ms_factory_create_decoder(stream->ms.factory, pf.fmt->encoding);
577 }else{
578 stream->pixconv = ms_factory_create_filter(stream->ms.factory, MS_PIX_CONV_ID);
579 /*set it to the pixconv */
580 ms_filter_call_method(stream->pixconv,MS_FILTER_SET_PIX_FMT,&format);
581 ms_filter_call_method(stream->pixconv,MS_FILTER_SET_VIDEO_SIZE,&cam_vsize);
582 }
583 stream->sizeconv=ms_factory_create_filter(stream->ms.factory, MS_SIZE_CONV_ID);
584 ms_filter_call_method(stream->sizeconv,MS_FILTER_SET_VIDEO_SIZE,&vsize);
585 }
586 if (stream->ms.rc){
587 ms_bitrate_controller_destroy(stream->ms.rc);
588 stream->ms.rc=NULL;
589 }
590 if (stream->ms.rc_enable){
591 switch (stream->ms.rc_algorithm){
592 case MSQosAnalyzerAlgorithmSimple:
593 stream->ms.rc=ms_av_bitrate_controller_new(NULL,NULL,stream->ms.sessions.rtp_session,stream->ms.encoder);
594 break;
595 case MSQosAnalyzerAlgorithmStateful:
596 stream->ms.rc=ms_bandwidth_bitrate_controller_new(NULL, NULL, stream->ms.sessions.rtp_session,stream->ms.encoder);
597 break;
598 }
599 }
600 }
601
602
603
configure_recorder_output(VideoStream * stream)604 static void configure_recorder_output(VideoStream *stream){
605 if (stream->recorder_output){
606 MSPinFormat pf={0};
607 ms_filter_call_method(stream->ms.decoder,MS_FILTER_GET_OUTPUT_FMT,&pf);
608 if (pf.fmt){
609 MSPinFormat pinfmt={0};
610 RtpSession *session=stream->ms.sessions.rtp_session;
611 PayloadType *pt=rtp_profile_get_payload(rtp_session_get_profile(session),rtp_session_get_recv_payload_type(session));
612 if (!pt) pt=rtp_profile_get_payload(rtp_session_get_profile(session),rtp_session_get_send_payload_type(session));
613 if (pt){
614 MSFmtDescriptor tmp=*pf.fmt;
615 tmp.encoding=pt->mime_type;
616 tmp.rate=pt->clock_rate;
617 pinfmt.pin=0;
618 pinfmt.fmt=ms_factory_get_format(stream->ms.factory,&tmp);
619 ms_filter_call_method(stream->recorder_output,MS_FILTER_SET_INPUT_FMT,&pinfmt);
620 ms_message("configure_itc(): format set to %s",ms_fmt_descriptor_to_string(pinfmt.fmt));
621 }
622 }else ms_warning("configure_itc(): video decoder doesn't give output format.");
623 }
624 }
625
configure_decoder(VideoStream * stream,PayloadType * pt)626 static void configure_decoder(VideoStream *stream, PayloadType *pt){
627 bool_t avpf_enabled=!!(pt->flags & PAYLOAD_TYPE_RTCP_FEEDBACK_ENABLED);
628 ms_filter_call_method(stream->ms.decoder, MS_VIDEO_DECODER_ENABLE_AVPF, &avpf_enabled);
629 ms_filter_call_method(stream->ms.decoder, MS_VIDEO_DECODER_FREEZE_ON_ERROR, &stream->freeze_on_error);
630 ms_filter_add_notify_callback(stream->ms.decoder, event_cb, stream, FALSE);
631 /* It is important that the internal_event_cb is called synchronously! */
632 ms_filter_add_notify_callback(stream->ms.decoder, internal_event_cb, stream, TRUE);
633 }
634
video_stream_payload_type_changed(RtpSession * session,void * data)635 static void video_stream_payload_type_changed(RtpSession *session, void *data){
636 VideoStream *stream = (VideoStream *)data;
637 RtpProfile *prof = rtp_session_get_profile(session);
638 int payload = rtp_session_get_recv_payload_type(session);
639 PayloadType *pt = rtp_profile_get_payload(prof, payload);
640
641 if (stream->ms.decoder == NULL){
642 ms_message("video_stream_payload_type_changed(): no decoder!");
643 return;
644 }
645
646 if (pt != NULL){
647 MSFilter *dec;
648
649 /* Q: why only video ? A: because an audio format can be used at different rates: ex: speex/16000 speex/8000*/
650 if ((stream->ms.decoder != NULL) && (stream->ms.decoder->desc->enc_fmt != NULL)
651 && (strcasecmp(pt->mime_type, stream->ms.decoder->desc->enc_fmt) == 0)) {
652 /* Same formats behind different numbers, nothing to do. */
653 return;
654 }
655
656 // dec = ms_filter_create_decoder(pt->mime_type);
657 dec = ms_factory_create_decoder(stream->ms.factory,pt->mime_type);
658 if (dec != NULL) {
659 MSFilter *prevFilter = stream->ms.decoder->inputs[0]->prev.filter;
660 MSFilter *nextFilter = stream->ms.decoder->outputs[0]->next.filter;
661
662 ms_filter_unlink(prevFilter, 0, stream->ms.decoder, 0);
663 ms_filter_unlink(stream->ms.decoder, 0, nextFilter, 0);
664 ms_filter_postprocess(stream->ms.decoder);
665 ms_filter_destroy(stream->ms.decoder);
666 stream->ms.decoder = dec;
667 if (pt->recv_fmtp != NULL)
668 ms_filter_call_method(stream->ms.decoder, MS_FILTER_ADD_FMTP, (void *)pt->recv_fmtp);
669 ms_filter_link(prevFilter, 0, stream->ms.decoder, 0);
670 ms_filter_link(stream->ms.decoder, 0, nextFilter, 0);
671 ms_filter_preprocess(stream->ms.decoder, stream->ms.sessions.ticker);
672
673 configure_decoder(stream, pt);
674 } else {
675 ms_warning("No decoder found for %s", pt->mime_type);
676 }
677 } else {
678 ms_warning("No payload defined with number %i", payload);
679 }
680
681 configure_recorder_output(stream);
682 }
683
video_stream_start(VideoStream * stream,RtpProfile * profile,const char * rem_rtp_ip,int rem_rtp_port,const char * rem_rtcp_ip,int rem_rtcp_port,int payload,int jitt_comp,MSWebCam * cam)684 int video_stream_start (VideoStream *stream, RtpProfile *profile, const char *rem_rtp_ip, int rem_rtp_port,
685 const char *rem_rtcp_ip, int rem_rtcp_port, int payload, int jitt_comp, MSWebCam *cam){
686 MSMediaStreamIO io = MS_MEDIA_STREAM_IO_INITIALIZER;
687 if (cam == NULL){
688 cam = ms_web_cam_manager_get_default_cam(ms_factory_get_web_cam_manager(stream->ms.factory));
689 }
690 io.input.type = MSResourceCamera;
691 io.input.camera = cam;
692 io.output.type = MSResourceDefault;
693 io.output.resource_arg = NULL;
694 rtp_session_set_jitter_compensation(stream->ms.sessions.rtp_session, jitt_comp);
695 return video_stream_start_from_io(stream, profile, rem_rtp_ip, rem_rtp_port, rem_rtcp_ip, rem_rtcp_port, payload, &io);
696 }
697
video_recorder_handle_event(void * userdata,MSFilter * recorder,unsigned int event,void * event_arg)698 void video_recorder_handle_event(void *userdata, MSFilter *recorder, unsigned int event, void *event_arg){
699 VideoStream *stream = (VideoStream*) userdata;
700 switch (event){
701 case MS_RECORDER_NEEDS_FIR:
702 ms_message("Request sending of FIR on videostream [%p]", stream);
703 video_stream_send_fir(stream);
704 break;
705 default:
706 break;
707 }
708 }
709
video_stream_start_from_io(VideoStream * stream,RtpProfile * profile,const char * rem_rtp_ip,int rem_rtp_port,const char * rem_rtcp_ip,int rem_rtcp_port,int payload,const MSMediaStreamIO * io)710 int video_stream_start_from_io(VideoStream *stream, RtpProfile *profile, const char *rem_rtp_ip, int rem_rtp_port,
711 const char *rem_rtcp_ip, int rem_rtcp_port, int payload, const MSMediaStreamIO *io) {
712 MSWebCam *cam = NULL;
713 MSFilter *source = NULL;
714 MSFilter *output = NULL;
715 MSFilter *recorder = NULL;
716
717 if (stream->ms.state != MSStreamInitialized){
718 ms_error("VideoStream in bad state");
719 return -1;
720 }
721
722 if (!ms_media_stream_io_is_consistent(io)) return -1;
723
724 if (stream->dir != MediaStreamRecvOnly){
725 switch(io->input.type){
726 case MSResourceRtp:
727 stream->rtp_io_session = io->input.session;
728 source = ms_factory_create_filter(stream->ms.factory, MS_RTP_RECV_ID);
729 ms_filter_call_method(source, MS_RTP_RECV_SET_SESSION, stream->rtp_io_session);
730 break;
731 case MSResourceCamera:
732 cam = io->input.camera;
733 source = ms_web_cam_create_reader(cam);
734 break;
735 case MSResourceFile:
736 source = ms_factory_create_filter(stream->ms.factory, MS_MKV_PLAYER_ID);
737 if (!source){
738 ms_error("Mediastreamer2 library compiled without libmastroska2");
739 return -1;
740 }
741 stream->source = source;
742 if (io->input.file) {
743 if (video_stream_open_remote_play(stream, io->input.file)!=NULL)
744 ms_filter_call_method_noarg(source, MS_PLAYER_START);
745 }
746 break;
747 default:
748 ms_error("Unhandled input resource type %s", ms_resource_type_to_string(io->input.type));
749 break;
750 }
751 }
752 if (stream->dir != MediaStreamSendOnly){
753 switch (io->output.type){
754 case MSResourceRtp:
755 output = ms_factory_create_filter(stream->ms.factory, MS_RTP_SEND_ID);
756 stream->rtp_io_session = io->input.session;
757 ms_filter_call_method(output, MS_RTP_SEND_SET_SESSION, stream->rtp_io_session);
758 break;
759 case MSResourceFile:
760 recorder = ms_factory_create_filter(stream->ms.factory, MS_MKV_RECORDER_ID);
761 if (!recorder){
762 ms_error("Mediastreamer2 library compiled without libmastroska2");
763 return -1;
764 }
765 if (stream->recorder_output){
766 ms_filter_destroy(stream->recorder_output);
767 }
768 stream->recorder_output = recorder;
769 ms_filter_add_notify_callback(recorder, video_recorder_handle_event, stream, TRUE);
770 if (io->output.file) video_stream_open_remote_record(stream, io->output.file);
771 break;
772 default:
773 /*will just display in all other cases*/
774 /*ms_error("Unhandled output resource type %s", ms_resource_type_to_string(io->output.type));*/
775 break;
776 }
777 }
778
779 return video_stream_start_with_source_and_output(stream, profile, rem_rtp_ip, rem_rtp_port, rem_rtcp_ip, rem_rtcp_port, payload, -1, cam, source, output);
780 }
781
video_stream_started(VideoStream * stream)782 bool_t video_stream_started(VideoStream *stream) {
783 return media_stream_started(&stream->ms);
784 }
785
apply_video_preset(VideoStream * stream,PayloadType * pt)786 static void apply_video_preset(VideoStream *stream, PayloadType *pt) {
787 MSVideoPresetsManager *vpm = ms_factory_get_video_presets_manager(stream->ms.factory);
788 MSVideoPresetConfiguration *vpc = NULL;
789 MSVideoConfiguration *conf = NULL;
790 bctbx_list_t *codec_tags = NULL;
791 bool_t hardware_accelerated = FALSE;
792 if (stream->preset != NULL) {
793 codec_tags = bctbx_list_append(codec_tags, ms_strdup(payload_type_get_mime(pt)));
794 codec_tags = bctbx_list_append(codec_tags, ms_strdup(stream->ms.encoder->desc->name));
795 if (ms_filter_has_method(stream->ms.encoder, MS_VIDEO_ENCODER_IS_HARDWARE_ACCELERATED) == TRUE) {
796 ms_filter_call_method(stream->ms.encoder, MS_VIDEO_ENCODER_IS_HARDWARE_ACCELERATED, &hardware_accelerated);
797 }
798 if (hardware_accelerated == TRUE) {
799 codec_tags = bctbx_list_append(codec_tags, ms_strdup("hardware"));
800 }
801 vpc = ms_video_presets_manager_find_preset_configuration(vpm, stream->preset, codec_tags);
802 bctbx_list_for_each(codec_tags, ms_free);
803 bctbx_list_free(codec_tags);
804 if (vpc != NULL) {
805 char *conf_tags = ms_video_preset_configuration_get_tags_as_string(vpc);
806 conf = ms_video_preset_configuration_get_video_configuration(vpc);
807 if (conf_tags) {
808 ms_message("Using the '%s' video preset tagged '%s'", stream->preset, conf_tags);
809 ms_free(conf_tags);
810 } else {
811 ms_message("Using the '%s' video preset non-tagged", stream->preset);
812 }
813 } else {
814 ms_warning("No '%s' video preset has been found", stream->preset);
815 }
816 }
817 if (conf == NULL) {
818 ms_message("Using the default video configuration list");
819 }
820 if (ms_filter_has_method(stream->ms.encoder, MS_VIDEO_ENCODER_SET_CONFIGURATION_LIST) == TRUE) {
821 ms_filter_call_method(stream->ms.encoder, MS_VIDEO_ENCODER_SET_CONFIGURATION_LIST, &conf);
822 }
823 }
824
apply_bitrate_limit(VideoStream * stream,PayloadType * pt)825 static void apply_bitrate_limit(VideoStream *stream, PayloadType *pt) {
826 MSVideoConfiguration *vconf_list = NULL;
827
828 if (stream->ms.target_bitrate<=0) {
829 stream->ms.target_bitrate = ms_factory_get_expected_bandwidth(stream->ms.factory);
830 if (stream->ms.target_bitrate <= 0) {
831 stream->ms.target_bitrate=pt->normal_bitrate;
832 ms_message("target bitrate not set for stream [%p] using payload's bitrate is %i",stream,stream->ms.target_bitrate);
833 } else {
834 ms_message("target bitrate not set for stream [%p] using expected bitrate is %i",stream,stream->ms.target_bitrate);
835 }
836 }
837
838 ms_message("Limiting bitrate of video encoder to %i bits/s for stream [%p]",stream->ms.target_bitrate,stream);
839 ms_filter_call_method(stream->ms.encoder, MS_VIDEO_ENCODER_GET_CONFIGURATION_LIST, &vconf_list);
840 if (vconf_list != NULL) {
841 MSVideoConfiguration vconf = ms_video_find_best_configuration_for_bitrate(vconf_list, stream->ms.target_bitrate, ms_factory_get_cpu_count(stream->ms.factory));
842 /* Adjust configuration video size to use the user preferred video size if it is lower that the configuration one. */
843 if ((stream->sent_vsize.height * stream->sent_vsize.width) < (vconf.vsize.height * vconf.vsize.width)) {
844 vconf.vsize = stream->sent_vsize;
845 }
846 ms_filter_call_method(stream->ms.encoder, MS_VIDEO_ENCODER_SET_CONFIGURATION, &vconf);
847 } else {
848 ms_filter_call_method(stream->ms.encoder, MS_FILTER_SET_BITRATE, &stream->ms.target_bitrate);
849 }
850 rtp_session_set_target_upload_bandwidth(stream->ms.sessions.rtp_session, stream->ms.target_bitrate);
851 }
852
mime_type_to_pix_format(const char * mime_type)853 static MSPixFmt mime_type_to_pix_format(const char *mime_type) {
854 if (strcasecmp(mime_type, "H264") == 0) return MS_H264;
855 return MS_PIX_FMT_UNKNOWN;
856 }
857
video_stream_start_with_source_and_output(VideoStream * stream,RtpProfile * profile,const char * rem_rtp_ip,int rem_rtp_port,const char * rem_rtcp_ip,int rem_rtcp_port,int payload,int jitt_comp,MSWebCam * cam,MSFilter * source,MSFilter * output)858 static int video_stream_start_with_source_and_output(VideoStream *stream, RtpProfile *profile, const char *rem_rtp_ip, int rem_rtp_port,
859 const char *rem_rtcp_ip, int rem_rtcp_port, int payload, int jitt_comp, MSWebCam *cam, MSFilter *source, MSFilter *output) {
860 PayloadType *pt;
861 RtpSession *rtps=stream->ms.sessions.rtp_session;
862 MSPixFmt format;
863 MSVideoSize disp_size;
864 JBParameters jbp;
865 const int socket_buf_size=2000000;
866 bool_t avpf_enabled = FALSE;
867 bool_t rtp_source = FALSE;
868 bool_t rtp_output = FALSE;
869 bool_t do_ts_adjustments;
870
871 if (source == NULL) {
872 source = stream->source;
873 }
874 rtp_source = (source && ms_filter_get_id(source) == MS_RTP_RECV_ID) ? TRUE : FALSE;
875 do_ts_adjustments = !rtp_source;
876
877 pt=rtp_profile_get_payload(profile,payload);
878 if (pt==NULL){
879 ms_error("videostream.c: undefined payload type %d.", payload);
880 return -1;
881 }
882 if (pt->flags & PAYLOAD_TYPE_RTCP_FEEDBACK_ENABLED) avpf_enabled = TRUE;
883
884 if ((cam != NULL) && (cam->desc->encode_to_mime_type != NULL) && (cam->desc->encode_to_mime_type(cam, pt->mime_type) == TRUE)) {
885 stream->source_performs_encoding = TRUE;
886 }
887
888 rtp_session_set_profile(rtps,profile);
889 if (rem_rtp_port>0)
890 rtp_session_set_remote_addr_full(rtps,rem_rtp_ip,rem_rtp_port,rem_rtcp_ip,rem_rtcp_port);
891 if (rem_rtcp_port > 0) {
892 rtp_session_enable_rtcp(rtps, TRUE);
893 } else {
894 rtp_session_enable_rtcp(rtps, FALSE);
895 }
896 rtp_session_set_payload_type(rtps,payload);
897 if (jitt_comp != -1) {
898 /*jitt_comp = -1 don't change value. The application can use rtp_session_set_jitter_buffer_params() directly.*/
899 rtp_session_set_jitter_compensation(rtps, jitt_comp);
900 }
901
902 rtp_session_signal_connect(stream->ms.sessions.rtp_session,"payload_type_changed",
903 (RtpCallback)video_stream_payload_type_changed,&stream->ms);
904
905 rtp_session_get_jitter_buffer_params(stream->ms.sessions.rtp_session,&jbp);
906 jbp.max_packets=1000;//needed for high resolution video
907 rtp_session_set_jitter_buffer_params(stream->ms.sessions.rtp_session,&jbp);
908 rtp_session_set_rtp_socket_recv_buffer_size(stream->ms.sessions.rtp_session,socket_buf_size);
909 rtp_session_set_rtp_socket_send_buffer_size(stream->ms.sessions.rtp_session,socket_buf_size);
910
911 /* Plumb the outgoing stream */
912 if (rem_rtp_port>0) ms_filter_call_method(stream->ms.rtpsend,MS_RTP_SEND_SET_SESSION,stream->ms.sessions.rtp_session);
913 ms_filter_call_method(stream->ms.rtpsend, MS_RTP_SEND_ENABLE_TS_ADJUSTMENT, &do_ts_adjustments);
914
915 if (stream->dir==MediaStreamRecvOnly){
916 /* Create a dummy sending stream to send the STUN packets to open firewall ports. */
917 MSConnectionHelper ch;
918 bool_t send_silence = FALSE;
919 stream->void_source = ms_factory_create_filter(stream->ms.factory, MS_VOID_SOURCE_ID);
920 ms_filter_call_method(stream->void_source, MS_VOID_SOURCE_SEND_SILENCE, &send_silence);
921 ms_connection_helper_start(&ch);
922 ms_connection_helper_link(&ch, stream->void_source, -1, 0);
923 ms_connection_helper_link(&ch, stream->ms.rtpsend, 0, -1);
924 } else {
925 MSConnectionHelper ch;
926 if (stream->source_performs_encoding == TRUE) {
927 format = mime_type_to_pix_format(pt->mime_type);
928 ms_filter_call_method(source, MS_FILTER_SET_PIX_FMT, &format);
929 } else if (!rtp_source) {
930 stream->ms.encoder=ms_factory_create_encoder(stream->ms.factory, pt->mime_type);
931 if (stream->ms.encoder==NULL){
932 /* big problem: we don't have a registered codec for this payload...*/
933 ms_error("videostream.c: No encoder available for payload %i:%s.",payload,pt->mime_type);
934 return -1;
935 }
936 }
937 /* creates the filters */
938 stream->cam=cam;
939 stream->source = source;
940 if (rtp_source) {
941 stream->ms.encoder = stream->source; /* Consider that the source is also the encoder */
942 } else {
943 stream->tee = ms_factory_create_filter(stream->ms.factory, MS_TEE_ID);
944 stream->local_jpegwriter=ms_factory_create_filter(stream->ms.factory, MS_JPEG_WRITER_ID);
945 if (stream->source_performs_encoding == TRUE) {
946 stream->ms.encoder = stream->source; /* Consider the encoder is the source */
947 }
948
949 apply_video_preset(stream, pt);
950 if (pt->normal_bitrate>0){
951 apply_bitrate_limit(stream, pt);
952 }
953 if (pt->send_fmtp){
954 ms_filter_call_method(stream->ms.encoder,MS_FILTER_ADD_FMTP,pt->send_fmtp);
955 }
956 ms_filter_call_method(stream->ms.encoder, MS_VIDEO_ENCODER_ENABLE_AVPF, &avpf_enabled);
957 if (stream->use_preview_window){
958 if (stream->rendercb==NULL){
959 stream->output2=ms_factory_create_filter_from_name(stream->ms.factory, stream->display_name);
960 }
961 }
962 configure_video_source(stream);
963 }
964
965 /* and then connect all */
966 ms_connection_helper_start(&ch);
967 ms_connection_helper_link(&ch, stream->source, -1, 0);
968 if (stream->pixconv) {
969 ms_connection_helper_link(&ch, stream->pixconv, 0, 0);
970 }
971 if (stream->tee) {
972 ms_connection_helper_link(&ch, stream->tee, 0, 0);
973 }
974 if (stream->sizeconv) {
975 ms_connection_helper_link(&ch, stream->sizeconv, 0, 0);
976 }
977 if ((stream->source_performs_encoding == FALSE) && !rtp_source) {
978 ms_connection_helper_link(&ch, stream->ms.encoder, 0, 0);
979 }
980 ms_connection_helper_link(&ch, stream->ms.rtpsend, 0, -1);
981 if (stream->output2){
982 if (stream->preview_window_id!=0){
983 ms_filter_call_method(stream->output2, MS_VIDEO_DISPLAY_SET_NATIVE_WINDOW_ID,&stream->preview_window_id);
984 }
985 ms_filter_link(stream->tee,1,stream->output2,0);
986 }
987 if (stream->local_jpegwriter){
988 ms_filter_link(stream->tee,2,stream->local_jpegwriter,0);
989 }
990 }
991
992 /* Plumb the incoming stream */
993 if (output != NULL) {
994 rtp_output = (ms_filter_get_id(output) == MS_RTP_SEND_ID) ? TRUE : FALSE;
995 }
996 /* Define target upload bandwidth for RTCP packets sending. */
997 if (pt->normal_bitrate > 0) {
998 rtp_session_set_target_upload_bandwidth(stream->ms.sessions.rtp_session, pt->normal_bitrate);
999 }
1000 if (stream->dir==MediaStreamSendRecv || stream->dir==MediaStreamRecvOnly){
1001 MSConnectionHelper ch;
1002
1003 if (!rtp_output) {
1004 /* create decoder first */
1005 stream->ms.decoder=ms_factory_create_decoder(stream->ms.factory, pt->mime_type);
1006 if (stream->ms.decoder==NULL){
1007 /* big problem: we don't have a registered decoderfor this payload...*/
1008 ms_error("videostream.c: No decoder available for payload %i:%s.",payload,pt->mime_type);
1009 return -1;
1010 }
1011 }
1012
1013 /* display logic */
1014 if (stream->rendercb!=NULL){
1015 /* rendering logic delegated to user suppsourcelied callback */
1016 stream->output=ms_factory_create_filter(stream->ms.factory, MS_EXT_DISPLAY_ID);
1017 ms_filter_add_notify_callback(stream->output,ext_display_cb,stream,TRUE);
1018 }else{
1019 /* no user supplied callback -> create filter */
1020 MSVideoDisplayDecodingSupport decoding_support;
1021
1022 if ((output == NULL) || (ms_filter_get_id(output) != MS_RTP_SEND_ID)) {
1023 /* Check if the decoding filter can perform the rendering */
1024 decoding_support.mime_type = pt->mime_type;
1025 decoding_support.supported = FALSE;
1026 ms_filter_call_method(stream->ms.decoder, MS_VIDEO_DECODER_SUPPORT_RENDERING, &decoding_support);
1027 stream->output_performs_decoding = decoding_support.supported;
1028 }
1029
1030 if (stream->output_performs_decoding) {
1031 stream->output = stream->ms.decoder;
1032 } else if (output != NULL) {
1033 stream->output = output;
1034 if (rtp_output) {
1035 stream->ms.decoder = stream->output;
1036 }
1037 } else {
1038 /* Create default display filter */
1039 stream->output = ms_factory_create_filter_from_name(stream->ms.factory, stream->display_name);
1040 }
1041 }
1042
1043 /* Don't allow null output */
1044 if(stream->output == NULL) {
1045 ms_fatal("No video display filter could be instantiated. Please check build-time configuration");
1046 }
1047
1048
1049 stream->ms.rtprecv = ms_factory_create_filter( stream->ms.factory, MS_RTP_RECV_ID);
1050 ms_filter_call_method(stream->ms.rtprecv,MS_RTP_RECV_SET_SESSION,stream->ms.sessions.rtp_session);
1051
1052 if (!rtp_output) {
1053 if (stream->output_performs_decoding == FALSE) {
1054 stream->jpegwriter=ms_factory_create_filter(stream->ms.factory, MS_JPEG_WRITER_ID);
1055 if (stream->jpegwriter){
1056 stream->tee2=ms_factory_create_filter(stream->ms.factory, MS_TEE_ID);
1057 }
1058 }
1059
1060 /* set parameters to the decoder*/
1061 if (pt->send_fmtp){
1062 ms_filter_call_method(stream->ms.decoder,MS_FILTER_ADD_FMTP,pt->send_fmtp);
1063 }
1064 if (pt->recv_fmtp!=NULL)
1065 ms_filter_call_method(stream->ms.decoder,MS_FILTER_ADD_FMTP,(void*)pt->recv_fmtp);
1066 configure_decoder(stream,pt);
1067
1068 if (stream->output_performs_decoding) {
1069 format = mime_type_to_pix_format(pt->mime_type);
1070 } else {
1071 /*force the decoder to output YUV420P */
1072 format = MS_YUV420P;
1073 }
1074 ms_filter_call_method(stream->ms.decoder, MS_FILTER_SET_PIX_FMT, &format);
1075
1076 /*configure the display window */
1077 if(stream->output != NULL) {
1078 int autofit = 1;
1079 disp_size.width=MS_VIDEO_SIZE_CIF_W;
1080 disp_size.height=MS_VIDEO_SIZE_CIF_H;
1081 ms_filter_call_method(stream->output,MS_FILTER_SET_VIDEO_SIZE,&disp_size);
1082
1083 /* if pixconv is used, force yuv420 */
1084 if (stream->pixconv || !stream->source)
1085 ms_filter_call_method(stream->output,MS_FILTER_SET_PIX_FMT,&format);
1086 /* else, use format from input */
1087 else {
1088 MSPixFmt source_format;
1089 ms_filter_call_method(stream->source,MS_FILTER_GET_PIX_FMT,&source_format);
1090 ms_filter_call_method(stream->output,MS_FILTER_SET_PIX_FMT,&source_format);
1091 }
1092
1093 ms_filter_call_method(stream->output,MS_VIDEO_DISPLAY_SET_LOCAL_VIEW_MODE,&stream->corner);
1094 if (stream->window_id!=0){
1095 autofit = 0;
1096 ms_filter_call_method(stream->output, MS_VIDEO_DISPLAY_SET_NATIVE_WINDOW_ID,&stream->window_id);
1097 }
1098 ms_filter_call_method(stream->output,MS_VIDEO_DISPLAY_ENABLE_AUTOFIT,&autofit);
1099 if (stream->display_filter_auto_rotate_enabled && ms_filter_has_method(stream->output, MS_VIDEO_DISPLAY_SET_DEVICE_ORIENTATION)) {
1100 ms_filter_call_method(stream->output,MS_VIDEO_DISPLAY_SET_DEVICE_ORIENTATION,&stream->device_orientation);
1101 }
1102 }
1103 }
1104
1105 /* and connect the filters */
1106 ms_connection_helper_start (&ch);
1107 ms_connection_helper_link (&ch,stream->ms.rtprecv,-1,0);
1108 if ((stream->output_performs_decoding == FALSE) && !rtp_output) {
1109 if (stream->recorder_output){
1110 ms_connection_helper_link(&ch,stream->tee3,0,0);
1111 ms_filter_link(stream->tee3,1,stream->recorder_output,0);
1112 configure_recorder_output(stream);
1113 }
1114 ms_connection_helper_link(&ch,stream->ms.decoder,0,0);
1115 }
1116 if (stream->tee2){
1117 ms_connection_helper_link (&ch,stream->tee2,0,0);
1118 ms_filter_link(stream->tee2,1,stream->jpegwriter,0);
1119 }
1120 if (stream->output!=NULL)
1121 ms_connection_helper_link (&ch,stream->output,0,-1);
1122 /* the video source must be send for preview , if it exists*/
1123 if (stream->tee!=NULL && stream->output!=NULL && stream->output2==NULL)
1124 ms_filter_link(stream->tee,1,stream->output,1);
1125 }
1126 if (stream->dir == MediaStreamSendOnly) {
1127 stream->ms.rtprecv = ms_factory_create_filter(stream->ms.factory, MS_RTP_RECV_ID);
1128 ms_filter_call_method(stream->ms.rtprecv, MS_RTP_RECV_SET_SESSION, stream->ms.sessions.rtp_session);
1129 stream->ms.voidsink = ms_factory_create_filter(stream->ms.factory, MS_VOID_SINK_ID);
1130 ms_filter_link(stream->ms.rtprecv, 0, stream->ms.voidsink, 0);
1131 }
1132
1133 /*start the video recorder if it was opened previously*/
1134 if (stream->recorder_output && ms_filter_implements_interface(stream->recorder_output, MSFilterRecorderInterface)){
1135 MSRecorderState state = MSRecorderClosed;
1136 ms_filter_call_method(stream->recorder_output, MS_RECORDER_GET_STATE, &state);
1137 if (state == MSRecorderPaused){
1138 ms_filter_call_method_noarg(stream->recorder_output, MS_RECORDER_START);
1139 }
1140 }
1141
1142 /* create the ticker */
1143 if (stream->ms.sessions.ticker==NULL) media_stream_start_ticker(&stream->ms);
1144
1145 stream->ms.start_time=ms_time(NULL);
1146 stream->last_fps_check=(uint64_t)-1;
1147 stream->ms.is_beginning=TRUE;
1148
1149 /* attach the graphs */
1150 if (stream->source)
1151 ms_ticker_attach (stream->ms.sessions.ticker, stream->source);
1152 if (stream->void_source)
1153 ms_ticker_attach (stream->ms.sessions.ticker, stream->void_source);
1154 if (stream->ms.rtprecv)
1155 ms_ticker_attach (stream->ms.sessions.ticker, stream->ms.rtprecv);
1156
1157 stream->ms.state=MSStreamStarted;
1158 return 0;
1159 }
1160
video_stream_start_with_source(VideoStream * stream,RtpProfile * profile,const char * rem_rtp_ip,int rem_rtp_port,const char * rem_rtcp_ip,int rem_rtcp_port,int payload,int jitt_comp,MSWebCam * cam,MSFilter * source)1161 int video_stream_start_with_source(VideoStream *stream, RtpProfile *profile, const char *rem_rtp_ip, int rem_rtp_port,
1162 const char *rem_rtcp_ip, int rem_rtcp_port, int payload, int jitt_comp, MSWebCam* cam, MSFilter* source) {
1163 return video_stream_start_with_source_and_output(stream, profile, rem_rtp_ip, rem_rtp_port,
1164 rem_rtcp_ip, rem_rtcp_port, payload, jitt_comp, cam, source, NULL);
1165 }
1166
video_stream_prepare_video(VideoStream * stream)1167 void video_stream_prepare_video(VideoStream *stream){
1168 video_stream_unprepare_video(stream);
1169 stream->ms.rtprecv=ms_factory_create_filter(stream->ms.factory, MS_RTP_RECV_ID);
1170 rtp_session_set_payload_type(stream->ms.sessions.rtp_session,0);
1171 rtp_session_enable_rtcp(stream->ms.sessions.rtp_session, FALSE);
1172 ms_filter_call_method(stream->ms.rtprecv,MS_RTP_RECV_SET_SESSION,stream->ms.sessions.rtp_session);
1173 stream->ms.voidsink=ms_factory_create_filter(stream->ms.factory, MS_VOID_SINK_ID);
1174 ms_filter_link(stream->ms.rtprecv,0,stream->ms.voidsink,0);
1175 media_stream_start_ticker(&stream->ms);
1176 ms_ticker_attach(stream->ms.sessions.ticker,stream->ms.rtprecv);
1177 stream->ms.state=MSStreamPreparing;
1178 }
1179
video_stream_unprepare_video(VideoStream * stream)1180 void video_stream_unprepare_video(VideoStream *stream){
1181 if (stream->ms.state==MSStreamPreparing) {
1182 stop_preload_graph(stream);
1183 stream->ms.state=MSStreamInitialized;
1184 }
1185 }
1186
video_stream_get_source_filter(const VideoStream * stream)1187 MSFilter* video_stream_get_source_filter(const VideoStream* stream) {
1188 if( stream ){
1189 return stream->source;
1190 } else {
1191 return NULL;
1192 }
1193 }
1194
video_stream_update_video_params(VideoStream * stream)1195 void video_stream_update_video_params(VideoStream *stream){
1196 /*calling video_stream_change_camera() does the job of unplumbing/replumbing and configuring the new graph*/
1197 video_stream_change_camera(stream,stream->cam);
1198 }
1199
1200 /**
1201 * Will update the source camera for the videostream passed as argument.
1202 * The parameters:
1203 * - stream : the stream for which to update the source
1204 * - cam : the camera which should now be considered as the new source.
1205 * - new_source (optional): if passed as non-NULL, it is expected that this filter comes from the specified camera.
1206 * In this case we don't create the source and use this one instead.
1207 * This allows you to reuse the camera and keep it alive when not needed, for fast on/off operations
1208 * - sink : when this filter is not NULL, it represents the AVPlayer video ITC source, which allows inter-graph communication.
1209 * - keep_old_source: when TRUE, will not destroy the previous stream source and return it for later usage.
1210 *
1211 * @return NULL if keep_old_source is FALSE, or the previous source filter if keep_old_source is TRUE
1212 */
_video_stream_change_camera(VideoStream * stream,MSWebCam * cam,MSFilter * new_source,MSFilter * sink,bool_t keep_old_source,bool_t skip_payload_config)1213 static MSFilter* _video_stream_change_camera(VideoStream *stream, MSWebCam *cam, MSFilter* new_source, MSFilter *sink, bool_t keep_old_source, bool_t skip_payload_config){
1214 MSFilter* old_source = NULL;
1215 bool_t new_src_different = (new_source && new_source != stream->source);
1216 bool_t use_player = (sink && !stream->player_active) || (!sink && stream->player_active);
1217 bool_t change_source = ( cam!=stream->cam || new_src_different || use_player);
1218 bool_t encoder_has_builtin_converter = (!stream->pixconv && !stream->sizeconv);
1219
1220 if (stream->ms.sessions.ticker && stream->source){
1221 ms_ticker_detach(stream->ms.sessions.ticker,stream->source);
1222 /*unlink source filters and subsequent post processing filters */
1223 if (encoder_has_builtin_converter || (stream->source_performs_encoding == TRUE)) {
1224 ms_filter_unlink(stream->source, 0, stream->tee, 0);
1225 } else {
1226 ms_filter_unlink (stream->source, 0, stream->pixconv, 0);
1227 ms_filter_unlink (stream->pixconv, 0, stream->tee, 0);
1228 ms_filter_unlink (stream->tee, 0, stream->sizeconv, 0);
1229 if (stream->source_performs_encoding == FALSE) {
1230 ms_filter_unlink(stream->sizeconv, 0, stream->ms.encoder, 0);
1231 } else {
1232 ms_filter_unlink(stream->sizeconv, 0, stream->ms.rtpsend, 0);
1233 }
1234 }
1235 /*destroy the filters */
1236 if (change_source) {
1237 if (!keep_old_source){
1238 ms_filter_destroy(stream->source);
1239 } else {
1240 old_source = stream->source;
1241 }
1242 }
1243
1244 if (!encoder_has_builtin_converter && (stream->source_performs_encoding == FALSE)) {
1245 ms_filter_destroy(stream->pixconv);
1246 ms_filter_destroy(stream->sizeconv);
1247 }
1248
1249 /*re create new ones and configure them*/
1250 if (change_source) {
1251 if (sink){
1252 stream->source = ms_factory_create_filter(stream->ms.factory, MS_ITC_SOURCE_ID);
1253 ms_filter_call_method(sink,MS_ITC_SINK_CONNECT,stream->source);
1254 stream->player_active = TRUE;
1255 } else {
1256 stream->source = new_source ? new_source : ms_web_cam_create_reader(cam);
1257 stream->cam = cam;
1258 stream->player_active = FALSE;
1259 }
1260 }
1261 if (stream->source_performs_encoding == TRUE) {
1262 stream->ms.encoder = stream->source; /* Consider the encoder is the source */
1263 }
1264
1265 /* update orientation for video output*/
1266 if (stream->output && stream->display_filter_auto_rotate_enabled && ms_filter_has_method(stream->output, MS_VIDEO_DISPLAY_SET_DEVICE_ORIENTATION)) {
1267 ms_filter_call_method(stream->output,MS_VIDEO_DISPLAY_SET_DEVICE_ORIENTATION,&stream->device_orientation);
1268 }
1269
1270 if (!skip_payload_config) {
1271 PayloadType *pt;
1272 RtpProfile *profile;
1273 int payload;
1274
1275 /* Apply bitrate limit to increase video size if the preferred one has changed. */
1276 profile = rtp_session_get_profile(stream->ms.sessions.rtp_session);
1277 payload = rtp_session_get_send_payload_type(stream->ms.sessions.rtp_session);
1278 pt = rtp_profile_get_payload(profile, payload);
1279 if (stream->source_performs_encoding == TRUE) {
1280 MSPixFmt format = mime_type_to_pix_format(pt->mime_type);
1281 ms_filter_call_method(stream->source, MS_FILTER_SET_PIX_FMT, &format);
1282 }
1283 apply_video_preset(stream, pt);
1284 if (pt->normal_bitrate > 0){
1285 apply_bitrate_limit(stream ,pt);
1286 }
1287 }
1288
1289 configure_video_source(stream);
1290
1291 if (encoder_has_builtin_converter || (stream->source_performs_encoding == TRUE)) {
1292 ms_filter_link (stream->source, 0, stream->tee, 0);
1293 }
1294 else {
1295 ms_filter_link (stream->source, 0, stream->pixconv, 0);
1296 ms_filter_link (stream->pixconv, 0, stream->tee, 0);
1297 ms_filter_link (stream->tee, 0, stream->sizeconv, 0);
1298 if (stream->source_performs_encoding == FALSE) {
1299 ms_filter_link(stream->sizeconv, 0, stream->ms.encoder, 0);
1300 } else {
1301 ms_filter_link(stream->sizeconv, 0, stream->ms.rtpsend, 0);
1302 }
1303 }
1304
1305 ms_ticker_attach(stream->ms.sessions.ticker,stream->source);
1306 }
1307 return old_source;
1308 }
1309
video_stream_change_camera(VideoStream * stream,MSWebCam * cam)1310 void video_stream_change_camera(VideoStream *stream, MSWebCam *cam){
1311 _video_stream_change_camera(stream, cam, NULL, NULL, FALSE, FALSE);
1312 }
1313
video_stream_change_camera_keep_previous_source(VideoStream * stream,MSWebCam * cam)1314 MSFilter* video_stream_change_camera_keep_previous_source(VideoStream *stream, MSWebCam *cam){
1315 return _video_stream_change_camera(stream, cam, NULL, NULL, TRUE, FALSE);
1316 }
1317
video_stream_change_source_filter(VideoStream * stream,MSWebCam * cam,MSFilter * filter,bool_t keep_previous)1318 MSFilter* video_stream_change_source_filter(VideoStream *stream, MSWebCam* cam, MSFilter* filter, bool_t keep_previous ){
1319 return _video_stream_change_camera(stream, cam, filter, NULL, keep_previous, FALSE);
1320 }
1321
video_stream_open_player(VideoStream * stream,MSFilter * sink)1322 void video_stream_open_player(VideoStream *stream, MSFilter *sink){
1323 ms_message("video_stream_open_player(): sink=%p",sink);
1324 _video_stream_change_camera(stream, stream->cam, NULL, sink, FALSE, FALSE);
1325 }
1326
video_stream_close_player(VideoStream * stream)1327 void video_stream_close_player(VideoStream *stream){
1328 _video_stream_change_camera(stream,stream->cam, NULL, NULL, FALSE, FALSE);
1329 }
1330
video_stream_recreate_graph(VideoStream * stream)1331 void video_stream_recreate_graph(VideoStream *stream){
1332 _video_stream_change_camera(stream,stream->cam, NULL, NULL, FALSE, TRUE);
1333 }
1334
video_stream_send_fir(VideoStream * stream)1335 void video_stream_send_fir(VideoStream *stream) {
1336 if (stream->ms.sessions.rtp_session != NULL) {
1337 rtp_session_send_rtcp_fb_fir(stream->ms.sessions.rtp_session);
1338 }
1339 }
1340
video_stream_send_pli(VideoStream * stream)1341 void video_stream_send_pli(VideoStream *stream) {
1342 if (stream->ms.sessions.rtp_session != NULL) {
1343 rtp_session_send_rtcp_fb_pli(stream->ms.sessions.rtp_session);
1344 }
1345 }
1346
video_stream_send_sli(VideoStream * stream,uint16_t first,uint16_t number,uint8_t picture_id)1347 void video_stream_send_sli(VideoStream *stream, uint16_t first, uint16_t number, uint8_t picture_id) {
1348 if (stream->ms.sessions.rtp_session != NULL) {
1349 rtp_session_send_rtcp_fb_sli(stream->ms.sessions.rtp_session, first, number, picture_id);
1350 }
1351 }
1352
video_stream_send_rpsi(VideoStream * stream,uint8_t * bit_string,uint16_t bit_string_len)1353 void video_stream_send_rpsi(VideoStream *stream, uint8_t *bit_string, uint16_t bit_string_len) {
1354 if (stream->ms.sessions.rtp_session != NULL) {
1355 rtp_session_send_rtcp_fb_rpsi(stream->ms.sessions.rtp_session, bit_string, bit_string_len);
1356 }
1357 }
1358
video_stream_send_vfu(VideoStream * stream)1359 void video_stream_send_vfu(VideoStream *stream){
1360 if (stream->ms.encoder)
1361 ms_filter_call_method_noarg(stream->ms.encoder, MS_VIDEO_ENCODER_REQ_VFU);
1362 }
1363
_video_stream_stop(VideoStream * stream,bool_t keep_source)1364 static MSFilter* _video_stream_stop(VideoStream * stream, bool_t keep_source)
1365 {
1366 MSEventQueue *evq;
1367 MSFilter* source = NULL;
1368
1369 stream->eventcb = NULL;
1370 stream->event_pointer = NULL;
1371 if (stream->ms.sessions.ticker){
1372 if (stream->ms.state == MSStreamPreparing) {
1373 stop_preload_graph(stream);
1374 } else {
1375 if (stream->source)
1376 ms_ticker_detach(stream->ms.sessions.ticker,stream->source);
1377 if (stream->void_source)
1378 ms_ticker_detach(stream->ms.sessions.ticker,stream->void_source);
1379 if (stream->ms.rtprecv)
1380 ms_ticker_detach(stream->ms.sessions.ticker,stream->ms.rtprecv);
1381
1382 if (stream->ms.ice_check_list != NULL) {
1383 ice_check_list_print_route(stream->ms.ice_check_list, "Video session's route");
1384 stream->ms.ice_check_list = NULL;
1385 }
1386 rtp_stats_display(rtp_session_get_stats(stream->ms.sessions.rtp_session),
1387 " VIDEO SESSION'S RTP STATISTICS ");
1388
1389 if (stream->void_source) {
1390 MSConnectionHelper ch;
1391 ms_connection_helper_start(&ch);
1392 ms_connection_helper_unlink(&ch, stream->void_source, -1, 0);
1393 ms_connection_helper_unlink(&ch, stream->ms.rtpsend, 0, -1);
1394 }
1395 if (stream->source){
1396 MSConnectionHelper ch;
1397 bool_t rtp_source = (ms_filter_get_id(stream->source) == MS_RTP_RECV_ID) ? TRUE : FALSE;
1398 ms_connection_helper_start(&ch);
1399 ms_connection_helper_unlink(&ch, stream->source, -1, 0);
1400 if (stream->pixconv) {
1401 ms_connection_helper_unlink(&ch, stream->pixconv, 0, 0);
1402 }
1403 if (stream->tee) {
1404 ms_connection_helper_unlink(&ch, stream->tee, 0, 0);
1405 }
1406 if (stream->sizeconv) {
1407 ms_connection_helper_unlink(&ch, stream->sizeconv, 0, 0);
1408 }
1409 if ((stream->source_performs_encoding == FALSE) && !rtp_source) {
1410 ms_connection_helper_unlink(&ch, stream->ms.encoder, 0, 0);
1411 }
1412 ms_connection_helper_unlink(&ch, stream->ms.rtpsend, 0, -1);
1413 if (stream->output2){
1414 ms_filter_unlink(stream->tee,1,stream->output2,0);
1415 }
1416 if (stream->local_jpegwriter){
1417 ms_filter_unlink(stream->tee,2,stream->local_jpegwriter,0);
1418 }
1419 }
1420 if (stream->ms.voidsink) {
1421 ms_filter_unlink(stream->ms.rtprecv, 0, stream->ms.voidsink, 0);
1422 } else if (stream->ms.rtprecv){
1423 MSConnectionHelper h;
1424 bool_t rtp_output = (ms_filter_get_id(stream->output) == MS_RTP_SEND_ID) ? TRUE : FALSE;
1425 ms_connection_helper_start (&h);
1426 ms_connection_helper_unlink (&h,stream->ms.rtprecv,-1,0);
1427 if ((stream->output_performs_decoding == FALSE) && !rtp_output) {
1428 if (stream->recorder_output){
1429 ms_connection_helper_unlink(&h,stream->tee3,0,0);
1430 ms_filter_unlink(stream->tee3,1,stream->recorder_output,0);
1431 }
1432 ms_connection_helper_unlink (&h,stream->ms.decoder,0,0);
1433 }
1434 if (stream->tee2){
1435 ms_connection_helper_unlink (&h,stream->tee2,0,0);
1436 ms_filter_unlink(stream->tee2,1,stream->jpegwriter,0);
1437 }
1438 if(stream->output)
1439 ms_connection_helper_unlink (&h,stream->output,0,-1);
1440 if (stream->tee && stream->output && stream->output2==NULL)
1441 ms_filter_unlink(stream->tee,1,stream->output,1);
1442 }
1443 }
1444 }
1445 rtp_session_set_rtcp_xr_media_callbacks(stream->ms.sessions.rtp_session, NULL);
1446 rtp_session_signal_disconnect_by_callback(stream->ms.sessions.rtp_session,"payload_type_changed",(RtpCallback)video_stream_payload_type_changed);
1447
1448 /*Automatically the video recorder if it was opened previously*/
1449 if (stream->recorder_output && ms_filter_implements_interface(stream->recorder_output, MSFilterRecorderInterface)){
1450 MSRecorderState state = MSRecorderClosed;
1451 ms_filter_call_method(stream->recorder_output, MS_RECORDER_GET_STATE, &state);
1452 if (state != MSRecorderClosed){
1453 ms_filter_call_method_noarg(stream->recorder_output, MS_RECORDER_CLOSE);
1454 }
1455 }
1456
1457 if( keep_source ){
1458 source = stream->source;
1459 stream->source = NULL; // will prevent video_stream_free() from destroying the source
1460 }
1461 /*before destroying the filters, pump the event queue so that pending events have a chance to reach their listeners.
1462 * When the filter are destroyed, all their pending events in the event queue will be cancelled*/
1463 evq = ms_factory_get_event_queue(stream->ms.factory);
1464 if (evq) ms_event_queue_pump(evq);
1465
1466 video_stream_free(stream);
1467
1468 return source;
1469 }
1470
video_stream_stop(VideoStream * stream)1471 void video_stream_stop( VideoStream* stream ){
1472 // don't keep the source
1473 _video_stream_stop(stream, FALSE);
1474 }
1475
video_stream_stop_keep_source(VideoStream * stream)1476 MSFilter* video_stream_stop_keep_source( VideoStream* stream) {
1477 return _video_stream_stop(stream, TRUE);
1478 }
1479
1480
video_stream_show_video(VideoStream * stream,bool_t show)1481 void video_stream_show_video(VideoStream *stream, bool_t show){
1482 if (stream->output){
1483 ms_filter_call_method(stream->output,MS_VIDEO_DISPLAY_SHOW_VIDEO,&show);
1484 }
1485 }
1486
1487
video_stream_get_native_window_id(VideoStream * stream)1488 void * video_stream_get_native_window_id(VideoStream *stream){
1489 void *id;
1490 if (stream->output){
1491 if (ms_filter_call_method(stream->output,MS_VIDEO_DISPLAY_GET_NATIVE_WINDOW_ID,&id)==0)
1492 return id;
1493 }
1494 return stream->window_id;
1495 }
1496
video_stream_set_native_window_id(VideoStream * stream,void * id)1497 void video_stream_set_native_window_id(VideoStream *stream, void *id){
1498 stream->window_id=id;
1499 if (stream->output){
1500 ms_filter_call_method(stream->output,MS_VIDEO_DISPLAY_SET_NATIVE_WINDOW_ID,&id);
1501 }
1502 }
1503
video_stream_set_native_preview_window_id(VideoStream * stream,void * id)1504 void video_stream_set_native_preview_window_id(VideoStream *stream, void *id){
1505 stream->preview_window_id=id;
1506 #if !TARGET_OS_IPHONE
1507 if (stream->output2){
1508 ms_filter_call_method(stream->output2,MS_VIDEO_DISPLAY_SET_NATIVE_WINDOW_ID,&id);
1509 }
1510 #endif
1511 if (stream->source){
1512 ms_filter_call_method(stream->source,MS_VIDEO_DISPLAY_SET_NATIVE_WINDOW_ID,&id);
1513 }
1514 }
1515
video_stream_get_native_preview_window_id(VideoStream * stream)1516 void * video_stream_get_native_preview_window_id(VideoStream *stream){
1517 void *id=0;
1518 if (stream->output2){
1519 if (ms_filter_call_method(stream->output2,MS_VIDEO_DISPLAY_GET_NATIVE_WINDOW_ID,&id)==0)
1520 return id;
1521 }
1522 if (stream->source){
1523 if (ms_filter_has_method(stream->source,MS_VIDEO_DISPLAY_GET_NATIVE_WINDOW_ID)
1524 && ms_filter_call_method(stream->source,MS_VIDEO_DISPLAY_GET_NATIVE_WINDOW_ID,&id)==0)
1525 return id;
1526 }
1527 return stream->preview_window_id;
1528 }
1529
video_stream_use_preview_video_window(VideoStream * stream,bool_t yesno)1530 void video_stream_use_preview_video_window(VideoStream *stream, bool_t yesno){
1531 stream->use_preview_window=yesno;
1532 }
1533
video_stream_set_device_rotation(VideoStream * stream,int orientation)1534 void video_stream_set_device_rotation(VideoStream *stream, int orientation){
1535 stream->device_orientation = orientation;
1536 }
1537
video_stream_set_freeze_on_error(VideoStream * stream,bool_t yesno)1538 void video_stream_set_freeze_on_error(VideoStream *stream, bool_t yesno) {
1539 stream->freeze_on_error = yesno;
1540 }
1541
video_stream_get_camera_sensor_rotation(VideoStream * stream)1542 int video_stream_get_camera_sensor_rotation(VideoStream *stream) {
1543 int rotation = -1;
1544 if (stream->source) {
1545 if (ms_filter_has_method(stream->source, MS_VIDEO_CAPTURE_GET_CAMERA_SENSOR_ROTATION)
1546 && ms_filter_call_method(stream->source, MS_VIDEO_CAPTURE_GET_CAMERA_SENSOR_ROTATION, &rotation) == 0)
1547 return rotation;
1548 }
1549 return -1;
1550 }
1551
video_preview_new(MSFactory * factory)1552 VideoPreview * video_preview_new(MSFactory *factory){
1553 VideoPreview *stream = (VideoPreview *)ms_new0 (VideoPreview, 1);
1554 stream->ms.factory = factory;
1555 MS_VIDEO_SIZE_ASSIGN(stream->sent_vsize, CIF);
1556 choose_display_name(stream);
1557 stream->ms.owns_sessions=TRUE;
1558 return stream;
1559 }
1560
video_preview_get_current_size(VideoPreview * stream)1561 MSVideoSize video_preview_get_current_size(VideoPreview *stream){
1562 MSVideoSize ret={0};
1563 if (stream->source){
1564 ms_filter_call_method(stream->source,MS_FILTER_GET_VIDEO_SIZE,&ret);
1565 }
1566 return ret;
1567 }
1568
configure_video_preview_source(VideoPreview * stream)1569 static void configure_video_preview_source(VideoPreview *stream) {
1570 MSPixFmt format;
1571 MSVideoSize vsize = stream->sent_vsize;
1572 float fps;
1573
1574 if (stream->forced_fps != 0) fps = stream->forced_fps;
1575 else fps = (float)29.97;
1576
1577 /* Transmit orientation to source filter. */
1578 if (ms_filter_has_method(stream->source, MS_VIDEO_CAPTURE_SET_DEVICE_ORIENTATION))
1579 ms_filter_call_method(stream->source, MS_VIDEO_CAPTURE_SET_DEVICE_ORIENTATION, &stream->device_orientation);
1580 /* Initialize the capture device orientation. */
1581 if (ms_filter_has_method(stream->source, MS_VIDEO_DISPLAY_SET_DEVICE_ORIENTATION)) {
1582 ms_filter_call_method(stream->source, MS_VIDEO_DISPLAY_SET_DEVICE_ORIENTATION, &stream->device_orientation);
1583 }
1584
1585 ms_filter_call_method(stream->source, MS_FILTER_SET_VIDEO_SIZE, &vsize);
1586 if (ms_filter_get_id(stream->source) != MS_STATIC_IMAGE_ID) {
1587 ms_filter_call_method(stream->source, MS_FILTER_SET_FPS, &fps);
1588 }
1589 ms_filter_call_method(stream->source, MS_FILTER_GET_VIDEO_SIZE, &vsize);
1590 ms_filter_call_method(stream->source, MS_FILTER_GET_PIX_FMT, &format);
1591
1592 if (format == MS_MJPEG) {
1593 stream->pixconv = ms_factory_create_filter(stream->ms.factory, MS_MJPEG_DEC_ID);
1594 if (stream->pixconv == NULL) {
1595 ms_error("Could not create mjpeg decoder, check your build options.");
1596 }
1597 }
1598 else {
1599 stream->pixconv = ms_factory_create_filter(stream->ms.factory, MS_PIX_CONV_ID);
1600 ms_filter_call_method(stream->pixconv, MS_FILTER_SET_PIX_FMT, &format);
1601 ms_filter_call_method(stream->pixconv, MS_FILTER_SET_VIDEO_SIZE, &vsize);
1602 }
1603 }
1604
video_preview_start(VideoPreview * stream,MSWebCam * device)1605 void video_preview_start(VideoPreview *stream, MSWebCam *device) {
1606 MSPixFmt format = MS_YUV420P; /* Display format */
1607 int mirroring = 1;
1608 int corner = -1;
1609 MSVideoSize disp_size = stream->sent_vsize;
1610 const char *displaytype = stream->display_name;
1611 MSConnectionHelper ch;
1612
1613 stream->source = ms_web_cam_create_reader(device);
1614
1615 /* configure the filters */
1616 configure_video_preview_source(stream);
1617
1618 if (displaytype) {
1619 stream->output2=ms_factory_create_filter_from_name(stream->ms.factory, displaytype);
1620 ms_filter_call_method(stream->output2, MS_FILTER_SET_PIX_FMT, &format);
1621 ms_filter_call_method(stream->output2, MS_FILTER_SET_VIDEO_SIZE, &disp_size);
1622 ms_filter_call_method(stream->output2, MS_VIDEO_DISPLAY_ENABLE_MIRRORING, &mirroring);
1623 ms_filter_call_method(stream->output2, MS_VIDEO_DISPLAY_SET_LOCAL_VIEW_MODE, &corner);
1624 /* and then connect all */
1625 }
1626
1627 stream->local_jpegwriter = ms_factory_create_filter(stream->ms.factory, MS_JPEG_WRITER_ID);
1628 if (stream->local_jpegwriter) {
1629 stream->tee = ms_factory_create_filter(stream->ms.factory, MS_TEE_ID);
1630 }
1631
1632 ms_connection_helper_start(&ch);
1633 ms_connection_helper_link(&ch, stream->source, -1, 0);
1634 if (stream->pixconv) {
1635 ms_connection_helper_link(&ch, stream->pixconv, 0, 0);
1636 }
1637
1638 if (stream->output2) {
1639 if (stream->preview_window_id != 0) {
1640 video_stream_set_native_preview_window_id(stream, stream->preview_window_id);
1641 }
1642 }
1643
1644 if (stream->tee) {
1645 ms_connection_helper_link(&ch, stream->tee, 0, 0);
1646 ms_filter_link(stream->tee, 1, stream->output2, 0);
1647 ms_filter_link(stream->tee, 2, stream->local_jpegwriter, 0);
1648 } else {
1649 ms_filter_link(stream->pixconv, 0, stream->output2, 0);
1650 }
1651
1652 /* create the ticker */
1653 stream->ms.sessions.ticker = ms_ticker_new();
1654 ms_ticker_set_name(stream->ms.sessions.ticker, "Video MSTicker");
1655 ms_ticker_attach (stream->ms.sessions.ticker, stream->source);
1656 stream->ms.state = MSStreamStarted;
1657 }
1658
_video_preview_stop(VideoPreview * stream,bool_t keep_source)1659 static MSFilter* _video_preview_stop( VideoPreview* stream, bool_t keep_source) {
1660 MSFilter* source = NULL;
1661 MSConnectionHelper ch;
1662 ms_ticker_detach(stream->ms.sessions.ticker, stream->source);
1663
1664 ms_connection_helper_start(&ch);
1665 ms_connection_helper_unlink(&ch, stream->source, -1, 0);
1666 if (stream->pixconv) {
1667 ms_connection_helper_unlink(&ch, stream->pixconv, 0, 0);
1668 }
1669 if (stream->tee) {
1670 ms_connection_helper_unlink(&ch, stream->tee, 0, 0);
1671 if (stream->output2) {
1672 ms_filter_unlink(stream->tee, 1, stream->output2, 0);
1673 }
1674 if (stream->local_jpegwriter) {
1675 ms_filter_unlink(stream->tee, 2, stream->local_jpegwriter, 0);
1676 }
1677 } else {
1678 ms_connection_helper_unlink(&ch, stream->output2, 0, 0);
1679 }
1680
1681 if (keep_source) {
1682 source = stream->source;
1683 ms_message("video_preview_stop: keeping source %p", source);
1684 stream->source = NULL; // prevent destroy of the source
1685 }
1686 video_stream_free(stream);
1687 return source;
1688 }
1689
video_preview_stop(VideoPreview * stream)1690 void video_preview_stop(VideoPreview *stream){
1691 _video_preview_stop(stream, FALSE);
1692 }
1693
video_preview_stop_reuse_source(VideoPreview * stream)1694 MSFilter* video_preview_stop_reuse_source(VideoPreview *stream){
1695 return _video_preview_stop(stream, TRUE);
1696 }
1697
_video_preview_change_camera(VideoPreview * stream,MSWebCam * cam,MSFilter * new_source,bool_t keep_old_source)1698 static MSFilter* _video_preview_change_camera(VideoPreview *stream, MSWebCam *cam, MSFilter* new_source, bool_t keep_old_source) {
1699 MSFilter *cur_filter;
1700 MSFilter* old_source = NULL;
1701 bool_t new_src_different = (new_source && new_source != stream->source);
1702 bool_t change_source = (cam != stream->cam || new_src_different);
1703 MSVideoSize disp_size = stream->sent_vsize;
1704
1705 if (stream->ms.sessions.ticker && stream->source) {
1706 ms_ticker_detach(stream->ms.sessions.ticker, stream->source);
1707 /*unlink source filters and subsequent post processing filters */
1708 if (stream->pixconv) {
1709 ms_filter_unlink(stream->source, 0, stream->pixconv, 0);
1710 cur_filter = stream->pixconv;
1711 } else {
1712 cur_filter = stream->source;
1713 }
1714 if (stream->tee) {
1715 ms_filter_unlink(cur_filter, 0, stream->tee, 0);
1716 if (stream->output2) {
1717 ms_filter_unlink(stream->tee, 1, stream->output2, 0);
1718 }
1719 if (stream->local_jpegwriter) {
1720 ms_filter_unlink(stream->tee, 2, stream->local_jpegwriter, 0);
1721 }
1722 } else {
1723 ms_filter_unlink(cur_filter, 0, stream->output2, 0);
1724 }
1725
1726 /*destroy the filters */
1727 if (change_source) {
1728 if (!keep_old_source) {
1729 ms_filter_destroy(stream->source);
1730 }
1731 else {
1732 old_source = stream->source;
1733 }
1734 }
1735
1736 if (stream->pixconv) {
1737 ms_filter_destroy(stream->pixconv);
1738 }
1739
1740 /*re create new ones and configure them*/
1741 if (change_source) {
1742 stream->source = new_source ? new_source : ms_web_cam_create_reader(cam);
1743 stream->cam = cam;
1744 stream->player_active = FALSE;
1745 }
1746
1747 configure_video_preview_source(stream);
1748 ms_filter_call_method(stream->output2, MS_FILTER_SET_VIDEO_SIZE, &disp_size);
1749
1750 if (stream->pixconv) {
1751 ms_filter_link(stream->source, 0, stream->pixconv, 0);
1752 cur_filter = stream->pixconv;
1753 } else {
1754 cur_filter = stream->source;
1755 }
1756 if (stream->tee) {
1757 ms_filter_link(cur_filter, 0, stream->tee, 0);
1758 if (stream->output2) {
1759 ms_filter_link(stream->tee, 1, stream->output2, 0);
1760 }
1761 if (stream->local_jpegwriter) {
1762 ms_filter_link(stream->tee, 2, stream->local_jpegwriter, 0);
1763 }
1764 }
1765 else {
1766 ms_filter_link(cur_filter, 0, stream->output2, 0);
1767 }
1768
1769 ms_ticker_attach(stream->ms.sessions.ticker, stream->source);
1770 }
1771 return old_source;
1772 }
1773
video_preview_change_camera(VideoPreview * stream,MSWebCam * cam)1774 void video_preview_change_camera(VideoPreview *stream, MSWebCam *cam) {
1775 _video_preview_change_camera(stream, cam, NULL, FALSE);
1776 }
1777
video_preview_update_video_params(VideoPreview * stream)1778 void video_preview_update_video_params(VideoPreview *stream) {
1779 /* Calling video_preview_change_camera() does the job of unplumbing/replumbing and configuring the new graph */
1780 video_preview_change_camera(stream, stream->cam);
1781 }
1782
1783
video_stream_recv_only_start(VideoStream * videostream,RtpProfile * profile,const char * addr,int port,int used_pt,int jitt_comp)1784 int video_stream_recv_only_start(VideoStream *videostream, RtpProfile *profile, const char *addr, int port, int used_pt, int jitt_comp){
1785 video_stream_set_direction(videostream, MediaStreamRecvOnly);
1786 return video_stream_start(videostream,profile,addr,port,addr,port+1,used_pt,jitt_comp,NULL);
1787 }
1788
video_stream_send_only_start(VideoStream * videostream,RtpProfile * profile,const char * addr,int port,int rtcp_port,int used_pt,int jitt_comp,MSWebCam * device)1789 int video_stream_send_only_start(VideoStream *videostream,
1790 RtpProfile *profile, const char *addr, int port, int rtcp_port,
1791 int used_pt, int jitt_comp, MSWebCam *device){
1792 video_stream_set_direction (videostream, MediaStreamSendOnly);
1793 return video_stream_start(videostream,profile,addr,port,addr,rtcp_port,used_pt,jitt_comp,device);
1794 }
1795
1796
video_stream_recv_only_stop(VideoStream * vs)1797 void video_stream_recv_only_stop(VideoStream *vs){
1798 video_stream_stop(vs);
1799 }
1800
video_stream_send_only_stop(VideoStream * vs)1801 void video_stream_send_only_stop(VideoStream *vs){
1802 video_stream_stop(vs);
1803 }
1804
1805 /* enable ZRTP on the video stream using information from the audio stream */
video_stream_enable_zrtp(VideoStream * vstream,AudioStream * astream)1806 void video_stream_enable_zrtp(VideoStream *vstream, AudioStream *astream){
1807 if (astream->ms.sessions.zrtp_context != NULL && vstream->ms.sessions.zrtp_context == NULL) {
1808 vstream->ms.sessions.zrtp_context=ms_zrtp_multistream_new(&(vstream->ms.sessions), astream->ms.sessions.zrtp_context);
1809 } else if (vstream->ms.sessions.zrtp_context && !media_stream_secured(&vstream->ms))
1810 ms_zrtp_reset_transmition_timer(vstream->ms.sessions.zrtp_context);
1811 }
1812
video_stream_start_zrtp(VideoStream * stream)1813 void video_stream_start_zrtp(VideoStream *stream) {
1814 if (stream->ms.sessions.zrtp_context!=NULL) {
1815 if (ms_zrtp_channel_start(stream->ms.sessions.zrtp_context) == MSZRTP_ERROR_CHANNEL_ALREADY_STARTED) {
1816 ms_zrtp_reset_transmition_timer(stream->ms.sessions.zrtp_context);
1817 }
1818 } else {
1819 ms_warning("Trying to start a ZRTP channel on videotream, but none was enabled");
1820 }
1821 }
1822
video_stream_enable_display_filter_auto_rotate(VideoStream * stream,bool_t enable)1823 void video_stream_enable_display_filter_auto_rotate(VideoStream* stream, bool_t enable) {
1824 stream->display_filter_auto_rotate_enabled = enable;
1825 }
1826
video_stream_is_decoding_error_to_be_reported(VideoStream * stream,uint32_t ms)1827 bool_t video_stream_is_decoding_error_to_be_reported(VideoStream *stream, uint32_t ms) {
1828 if (((stream->ms.sessions.ticker->time - stream->last_reported_decoding_error_time) > ms) || (stream->last_reported_decoding_error_time == 0))
1829 return TRUE;
1830 else
1831 return FALSE;
1832 }
1833
video_stream_decoding_error_reported(VideoStream * stream)1834 void video_stream_decoding_error_reported(VideoStream *stream) {
1835 stream->last_reported_decoding_error_time = stream->ms.sessions.ticker->time;
1836 }
1837
video_stream_decoding_error_recovered(VideoStream * stream)1838 void video_stream_decoding_error_recovered(VideoStream *stream) {
1839 stream->last_reported_decoding_error_time = 0;
1840 }
video_stream_get_camera(const VideoStream * stream)1841 const MSWebCam * video_stream_get_camera(const VideoStream *stream) {
1842 return stream->cam;
1843 }
1844
video_stream_use_video_preset(VideoStream * stream,const char * preset)1845 void video_stream_use_video_preset(VideoStream *stream, const char *preset) {
1846 if (stream->preset != NULL) ms_free(stream->preset);
1847 stream->preset = ms_strdup(preset);
1848 }
1849
video_stream_open_remote_play(VideoStream * stream,const char * filename)1850 MSFilter * video_stream_open_remote_play(VideoStream *stream, const char *filename){
1851 MSFilter *source = stream->source;
1852
1853 if (!source || !ms_filter_implements_interface(source, MSFilterPlayerInterface)){
1854 ms_error("video_stream_open_remote_play(): the stream is not using a player.");
1855 return NULL;
1856 }
1857 video_stream_close_remote_play(stream);
1858 if (ms_filter_call_method(source, MS_PLAYER_OPEN, (void*)filename)!=0){
1859 return NULL;
1860 }
1861 return source;
1862 }
1863
video_stream_close_remote_play(VideoStream * stream)1864 void video_stream_close_remote_play(VideoStream *stream){
1865 MSPlayerState state = MSPlayerClosed;
1866 MSFilter *source = stream->source;
1867
1868 if (!source) return;
1869
1870 ms_filter_call_method(source, MS_PLAYER_GET_STATE, &state);
1871 if (state != MSPlayerClosed){
1872 ms_filter_call_method_noarg(source, MS_PLAYER_CLOSE);
1873 }
1874 }
1875
video_stream_open_remote_record(VideoStream * stream,const char * filename)1876 MSFilter *video_stream_open_remote_record(VideoStream *stream, const char *filename){
1877 MSFilter *recorder = stream->recorder_output;
1878 if (!recorder || !ms_filter_implements_interface(recorder, MSFilterRecorderInterface)){
1879 ms_error("video_stream_open_remote_play(): the stream is not using a recorder.");
1880 return NULL;
1881 }
1882 if (ms_filter_call_method(recorder, MS_RECORDER_OPEN, (void*)filename)!=0){
1883 return NULL;
1884 }
1885 return recorder;
1886 }
1887
video_stream_close_remote_record(VideoStream * stream)1888 void video_stream_close_remote_record(VideoStream *stream){
1889 MSFilter *recorder = stream->recorder_output;
1890 MSRecorderState state = MSRecorderClosed;
1891
1892 if (!recorder || !ms_filter_implements_interface(recorder, MSFilterRecorderInterface)){
1893 ms_error("video_stream_close_remote_record(): the stream is not using a recorder.");
1894 return ;
1895 }
1896
1897 ms_filter_call_method(recorder, MS_RECORDER_GET_STATE, &state);
1898 if (state != MSRecorderClosed){
1899 ms_filter_call_method_noarg(recorder, MS_RECORDER_CLOSE);
1900 }
1901 }
1902