2 mediastreamer2 library - modular sound and video processing and streaming
3 Copyright (C) 2006 Simon MORLAT (simon.morlat@linphone.org)
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.
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.
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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20 #include "mediastreamer2/mediastream.h"
21 #include "mediastreamer2/msfilter.h"
22 #include "mediastreamer2/msvideo.h"
23 #include "mediastreamer2/msrtp.h"
24 #include "mediastreamer2/msvideoout.h"
28 #include "mediastreamer-config.h"
31 extern RtpSession * create_duplex_rtpsession( int locport, bool_t ipv6);
33 #define MAX_RTP_SIZE UDP_MAX_SIZE
35 /* this code is not part of the library itself, it is part of the mediastream program */
36 void video_stream_free (VideoStream * stream)
38 if (stream->session!=NULL){
39 rtp_session_unregister_event_queue(stream->session,stream->evq);
40 rtp_session_destroy(stream->session);
42 if (stream->rtprecv != NULL)
43 ms_filter_destroy (stream->rtprecv);
44 if (stream->rtpsend!=NULL)
45 ms_filter_destroy (stream->rtpsend);
46 if (stream->source != NULL)
47 ms_filter_destroy (stream->source);
48 if (stream->output != NULL)
49 ms_filter_destroy (stream->output);
50 if (stream->decoder != NULL)
51 ms_filter_destroy (stream->decoder);
52 if (stream->sizeconv != NULL)
53 ms_filter_destroy (stream->sizeconv);
54 if (stream->pixconv!=NULL)
55 ms_filter_destroy(stream->pixconv);
56 if (stream->tee!=NULL)
57 ms_filter_destroy(stream->tee);
58 if (stream->ticker != NULL)
59 ms_ticker_destroy (stream->ticker);
60 if (stream->evq!=NULL)
61 ortp_ev_queue_destroy(stream->evq);
65 /*this function must be called from the MSTicker thread:
66 it replaces one filter by another one.
67 This is a dirty hack that works anyway.
68 It would be interesting to have something that does the job
69 simplier within the MSTicker api
71 void video_stream_change_decoder(VideoStream *stream, int payload){
72 RtpSession *session=stream->session;
73 RtpProfile *prof=rtp_session_get_profile(session);
74 PayloadType *pt=rtp_profile_get_payload(prof,payload);
76 MSFilter *dec=ms_filter_create_decoder(pt->mime_type);
78 ms_filter_unlink(stream->rtprecv, 0, stream->decoder, 0);
79 ms_filter_unlink(stream->decoder,0,stream->output,0);
80 ms_filter_postprocess(stream->decoder);
81 ms_filter_destroy(stream->decoder);
83 if (pt->recv_fmtp!=NULL)
84 ms_filter_call_method(stream->decoder,MS_FILTER_ADD_FMTP,(void*)pt->recv_fmtp);
85 ms_filter_link (stream->rtprecv, 0, stream->decoder, 0);
86 ms_filter_link (stream->decoder,0 , stream->output, 0);
87 ms_filter_preprocess(stream->decoder,stream->ticker);
90 ms_warning("No decoder found for %s",pt->mime_type);
93 ms_warning("No payload defined with number %i",payload);
97 static void video_stream_adapt_bitrate(VideoStream *stream, int jitter, float lost){
98 if (stream->encoder!=NULL){
102 ms_warning("Remote reports bad receiving experience, trying to reduce bitrate of video encoder.");
104 ms_filter_call_method(stream->encoder,MS_FILTER_GET_BITRATE,&bitrate);
106 ms_error("Video encoder does not implement MS_FILTER_GET_BITRATE.");
110 new_bitrate=bitrate-10000;
111 ms_warning("Encoder bitrate reduced from %i to %i b/s.",bitrate,new_bitrate);
112 ms_filter_call_method(stream->encoder,MS_FILTER_SET_BITRATE,&new_bitrate);
114 ms_warning("Video encoder bitrate already at minimum.");
121 static void video_steam_process_rtcp(VideoStream *stream, mblk_t *m){
124 const report_block_t *rb;
125 ms_message("video_steam_process_rtcp: receiving RTCP SR");
126 rb=rtcp_SR_get_report_block(m,0);
130 ij=report_block_get_interarrival_jitter(rb);
131 flost=(float)(100.0*report_block_get_fraction_lost(rb)/256.0);
132 ms_message("interarrival jitter=%u , lost packets percentage since last report=%f ",ij,flost);
133 if (stream->adapt_bitrate) video_stream_adapt_bitrate(stream,ij,flost);
136 }while(rtcp_next_packet(m));
139 void video_stream_iterate(VideoStream *stream){
141 if (stream->output!=NULL)
142 ms_filter_call_method_noarg(stream->output,
143 MS_VIDEO_OUT_HANDLE_RESIZING);
146 OrtpEvent *ev=ortp_ev_queue_get(stream->evq);
148 if (ortp_event_get_type(ev)==ORTP_EVENT_RTCP_PACKET_RECEIVED){
149 OrtpEventData *evd=ortp_event_get_data(ev);
150 video_steam_process_rtcp(stream,evd->packet);
152 ortp_event_destroy(ev);
157 static void payload_type_changed(RtpSession *session, unsigned long data){
158 VideoStream *stream=(VideoStream*)data;
159 int pt=rtp_session_get_recv_payload_type(stream->session);
160 video_stream_change_decoder(stream,pt);
163 VideoStream *video_stream_new(int locport, bool_t use_ipv6){
164 VideoStream *stream = (VideoStream *)ms_new0 (VideoStream, 1);
165 stream->session=create_duplex_rtpsession(locport,use_ipv6);
166 stream->evq=ortp_ev_queue_new();
167 stream->rtpsend=ms_filter_new(MS_RTP_SEND_ID);
168 rtp_session_register_event_queue(stream->session,stream->evq);
169 stream->sent_vsize.width=MS_VIDEO_SIZE_CIF_W;
170 stream->sent_vsize.height=MS_VIDEO_SIZE_CIF_H;
174 void video_stream_set_sent_video_size(VideoStream *stream, MSVideoSize vsize){
175 ms_message("Setting vidoe size %dx%d", vsize.width, vsize.height);
176 stream->sent_vsize=vsize;
179 void video_stream_set_relay_session_id(VideoStream *stream, const char *id){
180 ms_filter_call_method(stream->rtpsend, MS_RTP_SEND_SET_RELAY_SESSION_ID,(void*)id);
183 void video_stream_enable_self_view(VideoStream *stream, bool_t val){
184 MSFilter *out=stream->output;
185 stream->corner=val ? 0 : -1;
187 ms_filter_call_method(out,MS_VIDEO_OUT_SET_CORNER,&stream->corner);
191 void video_stream_enable_adaptive_bitrate_control(VideoStream *s, bool_t yesno){
192 s->adapt_bitrate=yesno;
195 int video_stream_start (VideoStream *stream, RtpProfile *profile, const char *remip, int remport,
196 int rem_rtcp_port, int payload, int jitt_comp, MSWebCam *cam){
198 RtpSession *rtps=stream->session;
200 MSVideoSize vsize,cam_vsize,disp_size;
204 const int socket_buf_size=2000000;
206 ms_message("%s entry", __FUNCTION__);
208 pt=rtp_profile_get_payload(profile,payload);
210 ms_error("videostream.c: undefined payload type.");
213 stream->encoder=ms_filter_create_encoder(pt->mime_type);
214 stream->decoder=ms_filter_create_decoder(pt->mime_type);
215 if ((stream->encoder==NULL) || (stream->decoder==NULL)){
216 /* big problem: we have not a registered codec for this payload...*/
217 ms_error("videostream.c: No codecs available for payload %i:%s.",payload,pt->mime_type);
221 rtp_session_set_profile(rtps,profile);
222 if (remport>0) rtp_session_set_remote_addr_full(rtps,remip,remport,rem_rtcp_port);
223 rtp_session_set_payload_type(rtps,payload);
224 rtp_session_set_jitter_compensation(rtps,jitt_comp);
226 rtp_session_signal_connect(stream->session,"payload_type_changed",
227 (RtpCallback)payload_type_changed,(unsigned long)stream);
229 rtp_session_set_recv_buf_size(stream->session,MAX_RTP_SIZE);
231 rtp_session_get_jitter_buffer_params(stream->session,&jbp);
232 jbp.max_packets=1000;//needed for high resolution video
233 rtp_session_set_jitter_buffer_params(stream->session,&jbp);
235 rtp_session_set_rtp_socket_recv_buffer_size(stream->session,socket_buf_size);
236 rtp_session_set_rtp_socket_send_buffer_size(stream->session,socket_buf_size);
238 /* creates two rtp filters to recv send streams (remote part) */
239 if (remport>0) ms_filter_call_method(stream->rtpsend,MS_RTP_SEND_SET_SESSION,stream->session);
241 stream->rtprecv = ms_filter_new (MS_RTP_RECV_ID);
242 ms_filter_call_method(stream->rtprecv,MS_RTP_RECV_SET_SESSION,stream->session);
244 /* creates the filters */
245 stream->source = ms_web_cam_create_reader(cam);
246 stream->tee = ms_filter_new(MS_TEE_ID);
247 stream->output=ms_filter_new(MS_VIDEO_OUT_ID);
248 stream->sizeconv=ms_filter_new(MS_SIZE_CONV_ID);
250 if (pt->normal_bitrate>0){
251 ms_message("Limiting bitrate of video encoder to %i bits/s",pt->normal_bitrate);
252 ms_filter_call_method(stream->encoder,MS_FILTER_SET_BITRATE,&pt->normal_bitrate);
254 /* set parameters to the encoder and decoder*/
256 ms_filter_call_method(stream->encoder,MS_FILTER_ADD_FMTP,pt->send_fmtp);
257 ms_filter_call_method(stream->decoder,MS_FILTER_ADD_FMTP,pt->send_fmtp);
259 ms_filter_call_method(stream->encoder,MS_FILTER_GET_VIDEO_SIZE,&vsize);
260 vsize=ms_video_size_min(vsize,stream->sent_vsize);
261 ms_filter_call_method(stream->encoder,MS_FILTER_SET_VIDEO_SIZE,&vsize);
262 ms_filter_call_method(stream->encoder,MS_FILTER_GET_FPS,&fps);
263 ms_message("Setting vsize=%ix%i, fps=%f",vsize.width,vsize.height,fps);
264 /* configure the filters */
265 ms_filter_call_method(stream->source,MS_FILTER_SET_FPS,&fps);
266 ms_filter_call_method(stream->source,MS_FILTER_SET_VIDEO_SIZE,&vsize);
268 /* get the output format for webcam reader */
269 ms_filter_call_method(stream->source,MS_FILTER_GET_PIX_FMT,&format);
270 if (format==MS_MJPEG){
271 stream->pixconv=ms_filter_new(MS_MJPEG_DEC_ID);
273 stream->pixconv = ms_filter_new(MS_PIX_CONV_ID);
274 /*set it to the pixconv */
275 ms_filter_call_method(stream->pixconv,MS_FILTER_SET_PIX_FMT,&format);
277 ms_filter_call_method(stream->source,MS_FILTER_GET_VIDEO_SIZE,&cam_vsize);
279 ms_filter_call_method(stream->pixconv,MS_FILTER_SET_VIDEO_SIZE,&cam_vsize);
282 ms_filter_call_method(stream->sizeconv,MS_FILTER_SET_VIDEO_SIZE,&vsize);
284 /*force the decoder to output YUV420P */
286 ms_filter_call_method(stream->decoder,MS_FILTER_SET_PIX_FMT,&format);
288 disp_size.width=MS_VIDEO_SIZE_CIF_W;
289 disp_size.height=MS_VIDEO_SIZE_CIF_H;
291 ms_filter_call_method(stream->output,MS_FILTER_SET_VIDEO_SIZE,&disp_size);
292 ms_filter_call_method(stream->output,MS_VIDEO_OUT_AUTO_FIT,&tmp);
293 ms_filter_call_method(stream->output,MS_FILTER_SET_PIX_FMT,&format);
294 ms_filter_call_method(stream->output,MS_VIDEO_OUT_SET_CORNER,&stream->corner);
296 if (pt->recv_fmtp!=NULL)
297 ms_filter_call_method(stream->decoder,MS_FILTER_ADD_FMTP,(void*)pt->recv_fmtp);
299 /* and then connect all */
300 ms_filter_link (stream->source, 0, stream->pixconv, 0);
301 ms_filter_link (stream->pixconv, 0, stream->sizeconv, 0);
302 ms_filter_link (stream->sizeconv, 0, stream->tee, 0);
303 ms_filter_link (stream->tee, 0 ,stream->encoder, 0 );
304 ms_filter_link (stream->encoder,0, stream->rtpsend,0);
306 ms_filter_link (stream->rtprecv, 0, stream->decoder, 0);
307 ms_filter_link (stream->decoder,0 , stream->output, 0);
308 /* the source video must be send for preview */
309 ms_filter_link(stream->tee,1,stream->output,1);
311 /* create the ticker */
312 stream->ticker = ms_ticker_new();
313 ms_ticker_set_name(stream->ticker,"Video MSTicker");
314 /* attach it the graph */
315 ms_ticker_attach (stream->ticker, stream->source);
319 void video_stream_send_vfu(VideoStream *stream){
321 ms_filter_call_method_noarg(stream->encoder,MS_FILTER_REQ_VFU);
325 video_stream_stop (VideoStream * stream)
328 ms_ticker_detach(stream->ticker,stream->source);
330 rtp_stats_display(rtp_session_get_stats(stream->session),"Video session's RTP statistics");
332 ms_filter_unlink(stream->source,0,stream->pixconv,0);
333 ms_filter_unlink (stream->pixconv, 0, stream->sizeconv, 0);
334 ms_filter_unlink (stream->sizeconv, 0, stream->tee, 0);
335 ms_filter_unlink(stream->tee,0,stream->encoder,0);
336 ms_filter_unlink(stream->encoder, 0, stream->rtpsend,0);
337 ms_filter_unlink(stream->rtprecv, 0, stream->decoder, 0);
338 ms_filter_unlink(stream->decoder,0,stream->output,0);
339 ms_filter_unlink(stream->tee,1,stream->output,1);
341 video_stream_free (stream);
345 void video_stream_set_rtcp_information(VideoStream *st, const char *cname, const char *tool){
346 if (st->session!=NULL){
347 rtp_session_set_source_description(st->session,cname,NULL,NULL,NULL,NULL,tool,
348 "This is free software (GPL) !");
352 unsigned long video_stream_get_native_window_id(VideoStream *stream){
355 if (ms_filter_call_method(stream->output,MS_VIDEO_OUT_GET_NATIVE_WINDOW_ID,&id)==0)
362 VideoStream * video_preview_start(MSWebCam *device, MSVideoSize disp_size){
363 VideoStream *stream = (VideoStream *)ms_new0 (VideoStream, 1);
364 MSVideoSize vsize=disp_size;
366 float fps=(float)29.97;
369 /* creates the filters */
370 stream->source = ms_web_cam_create_reader(device);
371 stream->output = ms_filter_new(MS_VIDEO_OUT_ID);
374 /* configure the filters */
375 ms_filter_call_method(stream->source,MS_FILTER_SET_VIDEO_SIZE,&vsize);
376 ms_filter_call_method(stream->source,MS_FILTER_SET_FPS,&fps);
377 ms_filter_call_method(stream->source,MS_FILTER_GET_PIX_FMT,&format);
378 ms_filter_call_method(stream->source,MS_FILTER_GET_VIDEO_SIZE,&vsize);
379 if (format==MS_MJPEG){
380 stream->pixconv=ms_filter_new(MS_MJPEG_DEC_ID);
382 stream->pixconv=ms_filter_new(MS_PIX_CONV_ID);
383 ms_filter_call_method(stream->pixconv,MS_FILTER_SET_PIX_FMT,&format);
384 ms_filter_call_method(stream->pixconv,MS_FILTER_SET_VIDEO_SIZE,&vsize);
388 ms_filter_call_method(stream->output,MS_FILTER_SET_PIX_FMT,&format);
389 ms_filter_call_method(stream->output,MS_FILTER_SET_VIDEO_SIZE,&disp_size);
390 ms_filter_call_method(stream->output,MS_VIDEO_OUT_ENABLE_MIRRORING,&mirroring);
391 /* and then connect all */
393 ms_filter_link(stream->source,0, stream->pixconv,0);
394 ms_filter_link(stream->pixconv, 0, stream->output, 0);
396 /* create the ticker */
397 stream->ticker = ms_ticker_new();
398 ms_ticker_set_name(stream->ticker,"Video MSTicker");
399 ms_ticker_attach (stream->ticker, stream->source);
403 void video_preview_stop(VideoStream *stream){
404 ms_ticker_detach(stream->ticker, stream->source);
405 ms_filter_unlink(stream->source,0,stream->pixconv,0);
406 ms_filter_unlink(stream->pixconv,0,stream->output,0);
408 video_stream_free(stream);
412 int video_stream_send_only_start(VideoStream* stream, RtpProfile *profile, const char *remip, int remport,
413 int rem_rtcp_port, int payload, int jitt_comp, MSWebCam *device){
417 RtpSession *rtps=stream->session;
420 vsize.width=MS_VIDEO_SIZE_CIF_W;
421 vsize.height=MS_VIDEO_SIZE_CIF_H;
423 rtp_session_set_profile(rtps,profile);
424 if (remport>0) rtp_session_set_remote_addr_full(rtps,remip,remport,rem_rtcp_port);
425 rtp_session_set_payload_type(rtps,payload);
426 rtp_session_set_jitter_compensation(rtps,jitt_comp);
428 /* creates rtp filter to send streams (remote part) */
429 rtp_session_set_recv_buf_size(rtps,MAX_RTP_SIZE);
430 stream->rtpsend =ms_filter_new(MS_RTP_SEND_ID);
431 if (remport>0) ms_filter_call_method(stream->rtpsend,MS_RTP_SEND_SET_SESSION,stream->session);
433 /* creates the filters */
434 pt=rtp_profile_get_payload(profile,payload);
436 video_stream_free(stream);
437 ms_error("videostream.c: undefined payload type.");
440 stream->encoder=ms_filter_create_encoder(pt->mime_type);
441 if ((stream->encoder==NULL)){
442 /* big problem: we have not a registered codec for this payload...*/
443 video_stream_free(stream);
444 ms_error("videostream.c: No codecs available for payload %i.",payload);
448 /* creates the filters */
449 stream->source = ms_web_cam_create_reader(device);
450 stream->sizeconv=ms_filter_new(MS_SIZE_CONV_ID);
452 /* configure the filters */
454 ms_filter_call_method(stream->encoder,MS_FILTER_ADD_FMTP,pt->send_fmtp);
456 if (pt->normal_bitrate>0){
457 ms_message("Limiting bitrate of video encoder to %i bits/s",pt->normal_bitrate);
458 ms_filter_call_method(stream->encoder,MS_FILTER_SET_BITRATE,&pt->normal_bitrate);
461 ms_filter_call_method(stream->encoder,MS_FILTER_GET_FPS,&fps);
462 ms_filter_call_method(stream->encoder,MS_FILTER_GET_VIDEO_SIZE,&vsize);
463 vsize=ms_video_size_min(vsize,stream->sent_vsize);
464 ms_filter_call_method(stream->encoder,MS_FILTER_SET_VIDEO_SIZE,&vsize);
466 ms_filter_call_method(stream->source,MS_FILTER_SET_FPS,&fps);
467 ms_filter_call_method(stream->source,MS_FILTER_SET_VIDEO_SIZE,&vsize);
469 /* get the output format for webcam reader */
470 ms_filter_call_method(stream->source,MS_FILTER_GET_PIX_FMT,&format);
471 /*set it to the pixconv */
473 /* bug fix from AMD: What about MJPEG mode???*/
474 if (format==MS_MJPEG){
475 stream->pixconv=ms_filter_new(MS_MJPEG_DEC_ID);
477 stream->pixconv=ms_filter_new(MS_PIX_CONV_ID);
478 ms_filter_call_method(stream->pixconv,MS_FILTER_SET_PIX_FMT,&format);
480 ms_filter_call_method(stream->source,MS_FILTER_GET_VIDEO_SIZE,&vsize);
481 ms_filter_call_method(stream->pixconv,MS_FILTER_SET_VIDEO_SIZE,&vsize);
484 ms_filter_call_method(stream->encoder,MS_FILTER_GET_VIDEO_SIZE,&vsize);
485 ms_filter_call_method(stream->sizeconv,MS_FILTER_SET_VIDEO_SIZE,&vsize);
487 ms_message("vsize=%ix%i, fps=%f, send format: %s, capture format: %d, bitrate: %d",
488 vsize.width,vsize.height,fps,pt->send_fmtp,format, pt->normal_bitrate);
490 /* and then connect all */
491 ms_filter_link (stream->source, 0, stream->pixconv, 0);
492 ms_filter_link (stream->pixconv, 0, stream->sizeconv, 0);
493 ms_filter_link (stream->sizeconv, 0, stream->encoder, 0);
494 ms_filter_link (stream->encoder,0, stream->rtpsend,0);
496 /* create the ticker */
497 stream->ticker = ms_ticker_new();
498 /* attach it the graph */
499 ms_ticker_attach (stream->ticker, stream->source);
503 void video_stream_send_only_stop(VideoStream *stream){
505 ms_ticker_detach (stream->ticker, stream->source);
506 ms_filter_unlink(stream->source,0,stream->pixconv,0);
507 ms_filter_unlink (stream->pixconv, 0, stream->sizeconv, 0);
508 ms_filter_unlink (stream->sizeconv, 0, stream->encoder, 0);
509 ms_filter_unlink(stream->encoder,0,stream->rtpsend,0);
511 video_stream_free(stream);
514 int video_stream_recv_only_start (VideoStream *stream, RtpProfile *profile, const char *remip, int remport,int payload, int jitt_comp){
518 RtpSession *rtps=stream->session;
520 vsize.width=MS_VIDEO_SIZE_CIF_W;
521 vsize.height=MS_VIDEO_SIZE_CIF_H;
523 rtp_session_set_profile(rtps,profile);
524 if (remport>0) rtp_session_set_remote_addr(rtps,remip,remport);
525 rtp_session_set_payload_type(rtps,payload);
526 rtp_session_set_jitter_compensation(rtps,jitt_comp);
528 /* creates rtp filters to recv streams */
529 rtp_session_set_recv_buf_size(rtps,MAX_RTP_SIZE);
530 stream->rtprecv = ms_filter_new (MS_RTP_RECV_ID);
531 ms_filter_call_method(stream->rtprecv,MS_RTP_RECV_SET_SESSION,rtps);
533 /* creates the filters */
534 pt=rtp_profile_get_payload(profile,payload);
536 ms_error("videostream.c: undefined payload type.");
539 stream->decoder=ms_filter_create_decoder(pt->mime_type);
540 if (stream->decoder==NULL){
541 /* big problem: we have not a registered codec for this payload...*/
542 ms_error("videostream.c: No codecs available for payload %i:%s.",payload,pt->mime_type);
545 stream->output=ms_filter_new(MS_VIDEO_OUT_ID);
547 /*force the decoder to output YUV420P */
549 /*ask the size-converter to always output CIF */
550 vsize.width=MS_VIDEO_SIZE_CIF_W;
551 vsize.height=MS_VIDEO_SIZE_CIF_H;
552 ms_message("Setting output vsize=%ix%i",vsize.width,vsize.height);
554 ms_filter_call_method(stream->decoder,MS_FILTER_SET_PIX_FMT,&format);
555 ms_filter_call_method(stream->output,MS_FILTER_SET_PIX_FMT,&format);
556 ms_filter_call_method(stream->output,MS_FILTER_SET_VIDEO_SIZE,&vsize);
558 if (pt->recv_fmtp!=NULL) {
559 ms_message("pt->recv_fmtp: %s", pt->recv_fmtp);
560 ms_filter_call_method(stream->decoder,MS_FILTER_ADD_FMTP,(void*)pt->recv_fmtp);
563 /* and then connect all */
564 ms_filter_link (stream->rtprecv, 0, stream->decoder, 0);
565 ms_filter_link (stream->decoder,0 , stream->output, 0);
567 /* create the ticker */
568 stream->ticker = ms_ticker_new();
569 /* attach it the graph */
570 ms_ticker_attach (stream->ticker, stream->rtprecv);
574 void video_stream_recv_only_stop (VideoStream * stream){
575 if (stream->ticker!=NULL){
576 ms_ticker_detach(stream->ticker, stream->rtprecv);
577 rtp_stats_display(rtp_session_get_stats(stream->session),"Video session's RTP statistics");
578 ms_filter_unlink(stream->rtprecv, 0, stream->decoder, 0);
579 ms_filter_unlink(stream->decoder,0,stream->output,0);
581 video_stream_free (stream);