diff options
27 files changed, 8718 insertions, 0 deletions
diff --git a/doc/help-pdp_ieee1394.pd b/doc/help-pdp_ieee1394.pd new file mode 100644 index 0000000..3b90eea --- /dev/null +++ b/doc/help-pdp_ieee1394.pd @@ -0,0 +1,47 @@ +#N canvas 237 22 740 692 10; +#X obj 177 152 metro 70; +#X obj 222 118 bng 15 250 50 0 empty empty empty 20 8 0 8 -262144 -1 +-1; +#X msg 179 119 stop; +#X obj 414 352 tgl 15 0 empty empty empty 0 -6 0 8 -262144 -1 -1 0 +1; +#X obj 414 404 pdp_control; +#X msg 414 377 thread \$1; +#X floatatom 414 465 5 0 0 0 - - -; +#X obj 414 436 route pdp_drop; +#X text 33 439 pdp_ieee1394 : fire wire capture object for OSX; +#X text 31 455 inspired by pix_video from Gem; +#X text 31 469 written by Yves Degoyon (ydegoyon@free.fr); +#X msg 296 154 open; +#X text 339 155 Start Capture; +#X msg 300 185 close; +#X text 344 187 Stop Capture; +#X text 179 97 This autostarts capture; +#X msg 321 218 reset; +#X text 366 220 Reset Capture; +#X msg 288 267 quality \$1; +#X obj 422 264 vradio 15 1 0 4 empty empty empty 0 -6 0 8 -262144 -1 +-1 0; +#X text 446 262 Normal Quality; +#X text 446 280 High Quality; +#X text 446 295 Fast Quality; +#X text 447 311 All data; +#X text 171 329 Constructor : pdp_ieee1394 <width> <height>; +#X text 363 245 Quality (set before starting capture !); +#X obj 143 396 pdp_glx; +#X obj 168 305 pdp_ieee1394 640 480; +#X obj 166 358 pdp_scale 320 240; +#X connect 0 0 27 0; +#X connect 1 0 0 0; +#X connect 2 0 0 0; +#X connect 3 0 5 0; +#X connect 4 0 7 0; +#X connect 5 0 4 0; +#X connect 7 0 6 0; +#X connect 11 0 27 0; +#X connect 13 0 27 0; +#X connect 16 0 27 0; +#X connect 18 0 27 0; +#X connect 19 0 18 0; +#X connect 27 0 28 0; +#X connect 28 0 26 0; diff --git a/doc/help-pdp_mp4live~.pd b/doc/help-pdp_mp4live~.pd new file mode 100644 index 0000000..d87d9e3 --- /dev/null +++ b/doc/help-pdp_mp4live~.pd @@ -0,0 +1,129 @@ +#N canvas 84 12 807 665 10; +#X obj 268 64 bng 15 250 50 0 empty empty empty 20 8 0 8 -262144 -1 +-1; +#X msg 93 136 loop \$1; +#X obj 94 114 tgl 15 0 empty empty empty 20 8 0 8 -262144 -1 -1 1 1 +; +#X msg 123 92 open \$1; +#X obj 122 68 openpanel; +#X obj 107 51 bng 15 250 50 0 empty empty empty 20 8 0 8 -262144 -1 +-1; +#X floatatom 316 99 5 0 0 0 - - -; +#X msg 225 65 stop; +#X obj 323 68 hsl 300 15 0 1000 0 0 empty empty empty -2 -6 0 8 -262144 +-1 -1 0 1; +#X obj 257 134 metro 70; +#X obj 365 156 pdp_v4l; +#X obj 374 125 metro 70; +#X obj 419 91 bng 15 250 50 0 empty empty empty 20 8 0 8 -262144 -1 +-1; +#X msg 376 92 stop; +#X obj 207 160 pdp_yqt; +#X obj 606 530 tgl 15 0 empty empty empty 0 -6 0 8 -262144 -1 -1 0 +1; +#X obj 606 582 pdp_control; +#X msg 606 555 thread \$1; +#X floatatom 606 643 5 0 0 0 - - -; +#X obj 606 614 route pdp_drop; +#X floatatom 220 496 5 0 0 0 - - -; +#X text 268 497 Streaming status; +#X floatatom 256 519 5 0 0 0 - - -; +#X text 299 519 Number of video frames emitted; +#X floatatom 294 539 5 0 0 0 - - -; +#X obj 55 315 pdp_xv; +#X obj 119 254 tgl 15 0 empty empty empty 0 -6 0 8 -262144 -1 -1 0 +1; +#X obj 43 244 dac~; +#X obj 40 211 spigot~; +#X obj 119 254 tgl 15 0 empty empty empty 0 -6 0 8 -262144 -1 -1 0 +1; +#X obj 87 183 tgl 15 0 empty empty empty 0 -6 0 8 -262144 -1 -1 0 1 +; +#X msg 109 316 close; +#X text 32 162 Local echoes; +#X text 81 579 pdp_mp4live~ : mpeg4ip streaming emitter; +#X text 0 390 Start the streaming; +#X text 1 409 Stop the streaming; +#X msg 142 411 disconnect; +#X msg 142 390 connect; +#X text 368 189 Step 1 : set streaming parameters; +#X msg 363 229 videoport 47168; +#X msg 364 250 audioport 47170; +#X text 516 228 Set video port ( default : 7070 ); +#X text 515 250 Set audio port ( default : 8000 ); +#X text 516 209 Set ip address ( default : 127.0.0.1 ); +#X msg 365 269 ttl 1; +#X text 515 269 Set ttl ( default : 15 ); +#X text 17 371 Step 3 : on air !!; +#X text 515 291 Set video width ( default : 320 ); +#X msg 365 310 vheight 240; +#X text 514 310 Set video height ( default : 240 ); +#X msg 365 330 framerate 15; +#X text 515 331 Set video framerate ( default : 25 ); +#X text 516 352 Set video bitrate ( default : 128 ); +#X msg 365 351 vbitrate 256; +#X msg 365 373 samplerate 22050; +#X text 516 374 Set audio samplerate ( default : 44100 ); +#X text 516 395 Set audio bitrate ( default : 128 ); +#X msg 365 394 abitrate 64; +#X text 368 420 Step 2 : save sdp file and upload it to your server +; +#X text 338 540 Frame Rate; +#X obj 220 470 pdp_mp4live~; +#X msg 366 436 sdp /usr/local/movies/pdstream.sdp; +#X text 81 604 The rest is written by Yves Degoyon (ydegoyon@free.fr) +; +#X text 80 592 This object uses some code from mpeg4ip; +#X msg 362 208 ipaddr 213.56.149.35; +#X obj 207 200 pdp_scale 320 240; +#X obj 273 231 adc~; +#X msg 366 289 vwidth 320; +#X obj 54 282 pdp_spigot; +#X connect 0 0 9 0; +#X connect 1 0 14 0; +#X connect 2 0 1 0; +#X connect 3 0 14 0; +#X connect 4 0 3 0; +#X connect 5 0 4 0; +#X connect 6 0 9 1; +#X connect 7 0 9 0; +#X connect 8 0 6 0; +#X connect 9 0 14 0; +#X connect 10 0 60 0; +#X connect 10 0 68 0; +#X connect 11 0 10 0; +#X connect 12 0 11 0; +#X connect 13 0 11 0; +#X connect 14 0 65 0; +#X connect 14 0 68 0; +#X connect 14 4 28 0; +#X connect 14 5 28 0; +#X connect 15 0 17 0; +#X connect 16 0 19 0; +#X connect 17 0 16 0; +#X connect 19 0 18 0; +#X connect 26 0 68 1; +#X connect 28 1 27 1; +#X connect 28 1 27 0; +#X connect 30 0 28 1; +#X connect 31 0 25 0; +#X connect 36 0 60 0; +#X connect 37 0 60 0; +#X connect 39 0 60 0; +#X connect 40 0 60 0; +#X connect 44 0 60 0; +#X connect 48 0 60 0; +#X connect 50 0 60 0; +#X connect 53 0 60 0; +#X connect 54 0 60 0; +#X connect 57 0 60 0; +#X connect 60 0 20 0; +#X connect 60 1 22 0; +#X connect 60 2 24 0; +#X connect 61 0 60 0; +#X connect 64 0 60 0; +#X connect 65 0 60 0; +#X connect 66 0 60 0; +#X connect 66 1 60 1; +#X connect 67 0 60 0; +#X connect 68 1 25 0; diff --git a/doc/help-pdp_mp4player~.pd b/doc/help-pdp_mp4player~.pd new file mode 100644 index 0000000..d9935cb --- /dev/null +++ b/doc/help-pdp_mp4player~.pd @@ -0,0 +1,17 @@ +#N canvas 259 178 509 391 10; +#X obj 156 158 dac~; +#X text 250 113 <-- everything is in this box; +#X text 279 128 where the block size is redefined; +#X text 279 143 this is necessary for an; +#X text 280 155 ( acceptable? ) audio decoding; +#X obj 395 221 loadbang; +#X msg 395 251 \; pd dsp 1; +#X text 51 295 pdp_mp4player~ : decodes a mpeg4ip video stream; +#X text 51 308 ( from darwin or quicktime server ); +#X obj 128 113 rs_pdp_mp4player~; +#X text 50 333 The rest is written by Yves Degoyon (ydegoyon@free.fr) +; +#X text 51 321 This object uses some code from mpeg4ip; +#X connect 5 0 6 0; +#X connect 9 0 0 0; +#X connect 9 1 0 1; diff --git a/doc/help-pdp_spotlight.pd b/doc/help-pdp_spotlight.pd new file mode 100644 index 0000000..bfd1bfd --- /dev/null +++ b/doc/help-pdp_spotlight.pd @@ -0,0 +1,76 @@ +#N canvas 126 7 818 664 10; +#X obj 221 458 pdp_xv; +#X obj 268 64 bng 15 250 50 0 empty empty empty 20 8 0 8 -262144 -1 +-1; +#X msg 123 136 loop \$1; +#X obj 124 114 tgl 15 0 empty empty empty 20 8 0 8 -262144 -1 -1 1 +1; +#X msg 370 44 open \$1; +#X obj 369 20 openpanel; +#X obj 354 3 bng 15 250 50 0 empty empty empty 20 8 0 8 -262144 -1 +-1; +#X floatatom 316 99 5 0 0 0 - - -; +#X msg 225 65 stop; +#X obj 323 68 hsl 300 15 0 1000 0 0 empty empty empty -2 -6 0 8 -262144 +-1 -1 0 1; +#X obj 257 135 metro 70; +#X floatatom 344 229 5 0 0 0 - - -; +#X floatatom 358 251 5 0 0 0 - - -; +#X floatatom 371 272 5 0 0 0 - - -; +#X obj 252 167 pdp_yqt; +#X obj 390 163 pdp_v4l; +#X obj 399 132 metro 70; +#X obj 444 98 bng 15 250 50 0 empty empty empty 20 8 0 8 -262144 -1 +-1; +#X msg 401 99 stop; +#X msg 486 130 open /dev/video; +#X obj 547 426 tgl 15 0 empty empty empty 0 -6 0 8 -262144 -1 -1 0 +1; +#X obj 547 478 pdp_control; +#X msg 547 451 thread \$1; +#X floatatom 547 539 5 0 0 0 - - -; +#X obj 547 510 route pdp_drop; +#X text 421 271 Spotlight Size; +#X text 401 250 Y coordinate of spotlight upper left corner; +#X text 389 227 X coordinate of spotlight upper left corner; +#X text 100 508 pdp_spotlight : specially made for cabaret and pep' +; +#X text 100 522 author : Yves Degoyon ( ydegoyon@free.fr ); +#X floatatom 385 293 5 0 0 0 - - -; +#X text 435 292 Red component of the color; +#X floatatom 398 316 5 0 0 0 - - -; +#X floatatom 409 337 5 0 0 0 - - -; +#X text 448 315 Green component of the color; +#X text 459 336 Blue component of the color; +#X text 424 381 Strength (0<=s<=1) ( default : 0.5 ); +#X obj 429 363 hsl 128 15 0 1 0 0 empty empty empty -2 -6 0 8 -262144 +-1 -1 0 1; +#X obj 220 390 pdp_spotlight; +#X connect 1 0 10 0; +#X connect 2 0 14 0; +#X connect 3 0 2 0; +#X connect 4 0 14 0; +#X connect 5 0 4 0; +#X connect 6 0 5 0; +#X connect 7 0 10 1; +#X connect 8 0 10 0; +#X connect 9 0 7 0; +#X connect 10 0 14 0; +#X connect 11 0 38 1; +#X connect 12 0 38 2; +#X connect 13 0 38 3; +#X connect 14 0 38 0; +#X connect 15 0 38 0; +#X connect 16 0 15 0; +#X connect 17 0 16 0; +#X connect 18 0 16 0; +#X connect 19 0 15 0; +#X connect 20 0 22 0; +#X connect 21 0 24 0; +#X connect 22 0 21 0; +#X connect 24 0 23 0; +#X connect 30 0 38 4; +#X connect 32 0 38 5; +#X connect 33 0 38 6; +#X connect 37 0 38 7; +#X connect 38 0 0 0; diff --git a/doc/rs_pdp_mp4player~.pd b/doc/rs_pdp_mp4player~.pd new file mode 100644 index 0000000..4981102 --- /dev/null +++ b/doc/rs_pdp_mp4player~.pd @@ -0,0 +1,57 @@ +#N canvas 168 29 842 529 10; +#X text 460 551 written by Yves Degoyon (ydegoyon@free.fr); +#X floatatom 226 441 5 0 0 0 - - -; +#X text 272 440 Streaming status; +#X floatatom 221 419 5 0 0 0 - - -; +#X obj 36 449 pdp_xv; +#X text 321 185 Disconnect from the current stream; +#X msg 243 183 disconnect; +#X text 270 420 Number of video frames decoded; +#X text 457 527 ( at least from ffserver ); +#X text 23 547 NOTE : as for pdp_ffmpeg~ \, transmitting audio; +#X text 22 565 with the video stream produces some unsteady sound; +#X text 23 580 a prefered solution would be to use mp3cast~/mp3amp~ +; +#X obj 70 493 outlet~; +#X obj 146 486 outlet~; +#X obj 65 60 block~ 4096; +#X text 460 513 pdp_live~ : decodes a live video stream; +#X text 458 539 and reads most common files ( avi \, mpg \, .... ) +; +#X msg 245 243 audio \$1; +#X obj 314 245 tgl 15 0 empty empty empty 0 -6 0 8 -262144 -1 -1 0 +1; +#X text 345 245 Activate decoding of audio ( default : off ); +#X msg 240 125 connect rtsp://ayp.unia.es/pdstream.sdp; +#X text 529 126 Connect to a mpeg4ip stream; +#X msg 244 213 overtcp \$1; +#X text 358 215 Use RTP over RTSP (TCP); +#X obj 330 215 tgl 15 0 empty empty empty 0 -6 0 8 -262144 -1 -1 0 +1; +#X msg 245 277 priority \$1; +#X floatatom 334 278 5 0 0 0 - - -; +#X text 384 299 ( optional \, if you know what you're doing ); +#X text 386 284 ([-20 \, 20 ] default : 0 ); +#X text 389 271 Set the priority of decoding thread; +#X msg 242 151 connect rtsp://localhost/pdstream.sdp; +#X obj 114 390 pdp_mp4player~; +#X msg 243 347 vheight 240; +#X msg 244 322 vwidth 320; +#X text 339 324 Set video width ( default : 320 ); +#X text 339 344 Set video height ( default : 240 ); +#X connect 6 0 31 0; +#X connect 17 0 31 0; +#X connect 18 0 17 0; +#X connect 20 0 31 0; +#X connect 22 0 31 0; +#X connect 24 0 22 0; +#X connect 25 0 31 0; +#X connect 26 0 25 0; +#X connect 30 0 31 0; +#X connect 31 0 4 0; +#X connect 31 1 12 0; +#X connect 31 2 13 0; +#X connect 31 3 1 0; +#X connect 31 4 3 0; +#X connect 32 0 31 0; +#X connect 33 0 31 0; diff --git a/include/pdp_mp4audiosource.h b/include/pdp_mp4audiosource.h new file mode 100644 index 0000000..ec42c3b --- /dev/null +++ b/include/pdp_mp4audiosource.h @@ -0,0 +1,76 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is MPEG4IP. + * + * The Initial Developer of the Original Code is Cisco Systems Inc. + * Portions created by Cisco Systems Inc. are + * Copyright (C) Cisco Systems Inc. 2000, 2001. All Rights Reserved. + * + * Contributor(s): + * Dave Mackie dmackie@cisco.com + * Bill May wmay@cisco.com + * + * Adapted for PD/PDP by Yves Degoyon (ydegoyon@free.fr) + */ + +#ifndef __PDP_MP4AUDIOSOURCE__ +#define __PDP_MP4AUDIOSOURCE__ + +#include <sys/types.h> +#include <sys/ioctl.h> +#include <linux/soundcard.h> + +#include "media_source.h" +#include "audio_encoder.h" + +class CPDPAudioSource : public CMediaSource { + public: + CPDPAudioSource(CLiveConfig *pConfig); + + ~CPDPAudioSource() { + free(m_pcmFrameBuffer); + } + + bool IsDone() { + return false; + } + + float GetProgress() { + return 0.0; + } + + void CPDPAudioSource::DoStart(); + + void CPDPAudioSource::DoStop(); + + void ProcessAudio(u_int8_t* pcmBuffer, u_int32_t pcmBufferSize); + + protected: + int ThreadMain(); + + bool Init(); + + + protected: + int m_maxPasses; + Timestamp m_prevTimestamp; + int m_audioOssMaxBufferSize; + int m_audioOssMaxBufferFrames; + Timestamp *m_timestampOverflowArray; + size_t m_timestampOverflowArrayIndex; + u_int8_t* m_pcmFrameBuffer; + u_int32_t m_pcmFrameSize; + uint32_t m_channelsConfigured; +}; + + +#endif /* __PDP_MP4AUDIOSOURCE__ */ diff --git a/include/pdp_mp4audiosync.h b/include/pdp_mp4audiosync.h new file mode 100644 index 0000000..92098d9 --- /dev/null +++ b/include/pdp_mp4audiosync.h @@ -0,0 +1,115 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is MPEG4IP. + * + * The Initial Developer of the Original Code is Cisco Systems Inc. + * Portions created by Cisco Systems Inc. are + * Copyright (C) Cisco Systems Inc. 2000, 2001. All Rights Reserved. + * + * Contributor(s): + * Bill May wmay@cisco.com + * + * Adapted to PD/PDP by Yves Degoyon (ydegoyon@free.fr) + */ + +/* + * audio.h - provides a class that interfaces between the codec and + * the SDL audio application. Will provide for volume, buffering, + * syncronization + */ + +#ifndef __PDP_MP4AUDIOSYNC__ +#define __PDP_MP4AUDIOSYNC__ + +#include "audio.h" +#include "pdp_mp4player~.h" + +#define DECODE_BUFFERS_MAX 32 + +class CPDPAudioSync : public CAudioSync { + public: + CPDPAudioSync(CPlayerSession *psptr, t_pdp_mp4player *pdp_father); + ~CPDPAudioSync(void); + // APIs from codec + uint8_t *get_audio_buffer(void); + void filled_audio_buffer(uint64_t ts, int resync); + void set_config(int freq, int channels, int format, uint32_t max_buffer_size); + void set_eof(void); + void load_audio_buffer(uint8_t *from, + uint32_t bytes, + uint64_t ts, + int resync); + + // APIs from sync task + int initialize_audio(int have_video); + int is_audio_ready(uint64_t &disptime); + uint64_t check_audio_sync(uint64_t current_time, int &have_eof); + void play_audio(void); + void audio_callback(Uint8 *stream, int len); + void flush_sync_buffers(void); + void flush_decode_buffers(void); + + // Initialization, other APIs + void set_wait_sem(SDL_sem *p) { }; //m_audio_waiting = p; } ; + void set_volume(int volume); + + private: + void audio_convert_data(void *from, uint32_t len); + volatile int m_dont_fill; + uint64_t m_buffer_ts; + uint32_t m_buffer_offset_on; + uint32_t m_buffer_size; + uint32_t m_fill_index, m_play_index; + volatile int m_buffer_filled[DECODE_BUFFERS_MAX]; + uint64_t m_buffer_time[DECODE_BUFFERS_MAX]; + uint64_t m_last_fill_timestamp; + uint64_t m_play_time; + SDL_AudioSpec m_obtained; + uint8_t *m_sample_buffer[DECODE_BUFFERS_MAX]; + int m_config_set; + int m_audio_initialized; + int m_freq; + int m_channels; + int m_format; + int m_resync_required; + int m_audio_paused; + int m_consec_no_buffers; + volatile int m_audio_waiting_buffer; + int m_use_SDL_delay; + uint32_t m_resync_buffer; + SDL_sem *m_audio_waiting; + uint32_t m_skipped_buffers; + uint32_t m_didnt_fill_buffers; + int m_first_time; + int m_first_filled; + uint32_t m_msec_per_frame; + uint64_t m_buffer_latency; + int m_consec_wrong_latency; + int64_t m_wrong_latency_total; + int m_volume; + int m_do_sync; + int m_load_audio_do_next_resync; + uint32_t m_sample_size; + uint32_t m_play_sample_index; + uint32_t m_samples_loaded; + uint32_t m_bytes_per_sample; + uint64_t m_loaded_next_ts; + int m_silence; + void *m_convert_buffer; + t_pdp_mp4player *m_father; +}; + +CPDPAudioSync *pdp_create_audio_sync(CPlayerSession *, t_pdp_mp4player *pdp_father); + +#endif + + diff --git a/include/pdp_mp4config.h b/include/pdp_mp4config.h new file mode 100644 index 0000000..aac8b8b --- /dev/null +++ b/include/pdp_mp4config.h @@ -0,0 +1,381 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is MPEG4IP. + * + * The Initial Developer of the Original Code is Cisco Systems Inc. + * Portions created by Cisco Systems Inc. are + * Copyright (C) Cisco Systems Inc. 2000, 2001. All Rights Reserved. + * + * Contributor(s): + * Dave Mackie dmackie@cisco.com + * Bill May wmay@cisco.com + * + * Adapted for PD/PDP by Yves Degoyon (ydegoyon@free.fr) + */ + +#ifndef __LIVE_CONFIG_H__ +#define __LIVE_CONFIG_H__ + +#include <sys/types.h> +#include <linux/videodev.h> + +#include "pdp_mp4configset.h" + +#include "media_time.h" +#include "video_util_tv.h" + +#define FILE_SOURCE "FILE" +#define URL_SOURCE "URL" + +#define AUDIO_SOURCE_OSS "OSS" +#define AUDIO_SOURCE_PDP "PDP" + +#define AUDIO_ENCODER_FAAC "faac" +#define AUDIO_ENCODER_LAME "lame" +#define AUDIO_ENCODING_NONE "None" +#define AUDIO_ENCODING_PCM16 "PCM16" +#define AUDIO_ENCODING_MP3 "MP3" +#define AUDIO_ENCODING_AAC "AAC" +#define AUDIO_ENCODING_AC3 "AC3" +#define AUDIO_ENCODING_VORBIS "VORBIS" + +#define VIDEO_SOURCE_V4L "V4L" +#define VIDEO_SOURCE_PDP "PDP" + +#define VIDEO_ENCODER_FFMPEG "ffmpeg" +#define VIDEO_ENCODER_DIVX "divx" +#define VIDEO_ENCODER_H26L "h26l" +#define VIDEO_ENCODER_XVID "xvid" +#define VIDEO_ENCODER_H261 "h261" + +#define VIDEO_ENCODING_NONE "None" +#define VIDEO_ENCODING_YUV12 "YUV12" +#define VIDEO_ENCODING_MPEG2 "MPEG2" +#define VIDEO_ENCODING_MPEG4 "MPEG4" +#define VIDEO_ENCODING_H26L "H26L" +#define VIDEO_ENCODING_H261 "H261" + +#define VIDEO_NTSC_FRAME_RATE ((float)29.97) +#define VIDEO_PAL_FRAME_RATE ((float)25.00) + +#define VIDEO_STD_ASPECT_RATIO ((float)1.33) // standard 4:3 +#define VIDEO_LB1_ASPECT_RATIO ((float)2.35) // typical "widescreen" format +#define VIDEO_LB2_ASPECT_RATIO ((float)1.85) // alternate widescreen format +#define VIDEO_LB3_ASPECT_RATIO ((float)1.78) // hdtv 16:9 + +#define MP3_MPEG1_SAMPLES_PER_FRAME 1152 // for MPEG-1 bitrates +#define MP3_MPEG2_SAMPLES_PER_FRAME 576 // for MPEG-2 bitrates + +#define VIDEO_SIGNAL_PAL 0 +#define VIDEO_SIGNAL_NTSC 1 +#define VIDEO_SIGNAL_SECAM 2 + +DECLARE_CONFIG(CONFIG_APP_REAL_TIME); +DECLARE_CONFIG(CONFIG_APP_REAL_TIME_SCHEDULER); +DECLARE_CONFIG(CONFIG_APP_DURATION); +DECLARE_CONFIG(CONFIG_APP_DURATION_UNITS); +DECLARE_CONFIG(CONFIG_APP_FILE_0); +DECLARE_CONFIG(CONFIG_APP_FILE_1); +DECLARE_CONFIG(CONFIG_APP_FILE_2); +DECLARE_CONFIG(CONFIG_APP_FILE_3); +DECLARE_CONFIG(CONFIG_APP_FILE_4); +DECLARE_CONFIG(CONFIG_APP_FILE_5); +DECLARE_CONFIG(CONFIG_APP_FILE_6); +DECLARE_CONFIG(CONFIG_APP_FILE_7); +DECLARE_CONFIG(CONFIG_APP_DEBUG); +DECLARE_CONFIG(CONFIG_APP_LOGLEVEL); +DECLARE_CONFIG(CONFIG_APP_SIGNAL_HALT); + +DECLARE_CONFIG(CONFIG_AUDIO_ENABLE); +DECLARE_CONFIG(CONFIG_AUDIO_SOURCE_TYPE); +DECLARE_CONFIG(CONFIG_AUDIO_SOURCE_NAME); +DECLARE_CONFIG(CONFIG_AUDIO_MIXER_NAME); +DECLARE_CONFIG(CONFIG_AUDIO_INPUT_NAME); +DECLARE_CONFIG(CONFIG_AUDIO_SOURCE_TRACK); +DECLARE_CONFIG(CONFIG_AUDIO_CHANNELS); +DECLARE_CONFIG(CONFIG_AUDIO_SAMPLE_RATE); +DECLARE_CONFIG(CONFIG_AUDIO_BIT_RATE_KBPS); +DECLARE_CONFIG(CONFIG_AUDIO_BIT_RATE); +DECLARE_CONFIG(CONFIG_AUDIO_ENCODING); +DECLARE_CONFIG(CONFIG_AUDIO_ENCODER); +DECLARE_CONFIG(CONFIG_AUDIO_OSS_USE_SMALL_FRAGS); +DECLARE_CONFIG(CONFIG_AUDIO_OSS_FRAGMENTS); +DECLARE_CONFIG(CONFIG_AUDIO_OSS_FRAG_SIZE); + +DECLARE_CONFIG(CONFIG_VIDEO_ENABLE); +DECLARE_CONFIG(CONFIG_VIDEO_SOURCE_TYPE); +DECLARE_CONFIG(CONFIG_VIDEO_SOURCE_NAME); +DECLARE_CONFIG(CONFIG_VIDEO_INPUT); +DECLARE_CONFIG(CONFIG_VIDEO_SIGNAL); +DECLARE_CONFIG(CONFIG_VIDEO_TUNER); +DECLARE_CONFIG(CONFIG_VIDEO_CHANNEL_LIST_INDEX); +DECLARE_CONFIG(CONFIG_VIDEO_CHANNEL_INDEX); +DECLARE_CONFIG(CONFIG_VIDEO_SOURCE_TRACK); +DECLARE_CONFIG(CONFIG_VIDEO_PREVIEW); +DECLARE_CONFIG(CONFIG_VIDEO_RAW_PREVIEW); +DECLARE_CONFIG(CONFIG_VIDEO_ENCODED_PREVIEW); +DECLARE_CONFIG(CONFIG_VIDEO_ENCODER); +DECLARE_CONFIG(CONFIG_VIDEO_ENCODING); +DECLARE_CONFIG(CONFIG_VIDEO_RAW_WIDTH); +DECLARE_CONFIG(CONFIG_VIDEO_RAW_HEIGHT); +DECLARE_CONFIG(CONFIG_VIDEO_ASPECT_RATIO); +DECLARE_CONFIG(CONFIG_VIDEO_FRAME_RATE); +DECLARE_CONFIG(CONFIG_VIDEO_KEY_FRAME_INTERVAL); +DECLARE_CONFIG(CONFIG_VIDEO_BIT_RATE); +DECLARE_CONFIG(CONFIG_VIDEO_PROFILE_ID); +DECLARE_CONFIG(CONFIG_VIDEO_BRIGHTNESS); +DECLARE_CONFIG(CONFIG_VIDEO_HUE); +DECLARE_CONFIG(CONFIG_VIDEO_COLOR); +DECLARE_CONFIG(CONFIG_VIDEO_CONTRAST); +DECLARE_CONFIG(CONFIG_VIDEO_TIMEBITS); +DECLARE_CONFIG(CONFIG_V4L_CACHE_TIMESTAMP); +DECLARE_CONFIG(CONFIG_VIDEO_H261_QUALITY); +DECLARE_CONFIG(CONFIG_VIDEO_H261_QUALITY_ADJ_FRAMES); +DECLARE_CONFIG(CONFIG_VIDEO_CAP_BUFF_COUNT); + + +DECLARE_CONFIG(CONFIG_RECORD_ENABLE); +DECLARE_CONFIG(CONFIG_RECORD_RAW_AUDIO); +DECLARE_CONFIG(CONFIG_RECORD_RAW_VIDEO); +DECLARE_CONFIG(CONFIG_RECORD_ENCODED_AUDIO); +DECLARE_CONFIG(CONFIG_RECORD_ENCODED_VIDEO); +DECLARE_CONFIG(CONFIG_RECORD_MP4_FILE_NAME); +DECLARE_CONFIG(CONFIG_RECORD_MP4_HINT_TRACKS); +DECLARE_CONFIG(CONFIG_RECORD_MP4_OVERWRITE); +DECLARE_CONFIG(CONFIG_RECORD_MP4_OPTIMIZE); + +DECLARE_CONFIG(CONFIG_RTP_ENABLE); +DECLARE_CONFIG(CONFIG_RTP_DEST_ADDRESS); // for video +DECLARE_CONFIG(CONFIG_RTP_AUDIO_DEST_PORT); +DECLARE_CONFIG(CONFIG_RTP_VIDEO_DEST_PORT); +DECLARE_CONFIG(CONFIG_RTP_RECV_BUFFER_TIME); +DECLARE_CONFIG(CONFIG_RTP_PAYLOAD_SIZE); +DECLARE_CONFIG(CONFIG_RTP_MCAST_TTL); +DECLARE_CONFIG(CONFIG_RTP_DISABLE_TS_OFFSET); +DECLARE_CONFIG(CONFIG_RTP_USE_SSM); +DECLARE_CONFIG(CONFIG_SDP_FILE_NAME); +DECLARE_CONFIG(CONFIG_RTP_AUDIO_DEST_ADDRESS); +DECLARE_CONFIG(CONFIG_RTP_USE_MP3_PAYLOAD_14); +DECLARE_CONFIG(CONFIG_RTP_NO_B_RR_0); +DECLARE_CONFIG(CONFIG_RAW_ENABLE); +DECLARE_CONFIG(CONFIG_RAW_PCM_FILE_NAME); +DECLARE_CONFIG(CONFIG_RAW_PCM_FIFO); +DECLARE_CONFIG(CONFIG_RAW_YUV_FILE_NAME); +DECLARE_CONFIG(CONFIG_RAW_YUV_FIFO); + + +#ifdef DECLARE_CONFIG_VARIABLES +static SConfigVariable PdpConfigVariables[] = { + + CONFIG_BOOL(CONFIG_APP_REAL_TIME, "isRealTime", true), + CONFIG_BOOL(CONFIG_APP_REAL_TIME_SCHEDULER, "useRealTimeScheduler", true), + CONFIG_INT(CONFIG_APP_DURATION, "duration", 1), + CONFIG_INT(CONFIG_APP_DURATION_UNITS, "durationUnits", 60), + + CONFIG_STRING(CONFIG_APP_FILE_0, "file0", ""), + CONFIG_STRING(CONFIG_APP_FILE_1, "file1", ""), + CONFIG_STRING(CONFIG_APP_FILE_2, "file2", ""), + CONFIG_STRING(CONFIG_APP_FILE_3, "file3", ""), + CONFIG_STRING(CONFIG_APP_FILE_4, "file4", ""), + CONFIG_STRING(CONFIG_APP_FILE_5, "file5", ""), + CONFIG_STRING(CONFIG_APP_FILE_6, "file6", ""), + CONFIG_STRING(CONFIG_APP_FILE_7, "file7", ""), + + CONFIG_BOOL(CONFIG_APP_DEBUG, "debug", false), + CONFIG_INT(CONFIG_APP_LOGLEVEL, "logLevel", 0), + CONFIG_STRING(CONFIG_APP_SIGNAL_HALT, "signalHalt", "sighup"), + + // AUDIO + + CONFIG_BOOL(CONFIG_AUDIO_ENABLE, "audioEnable", true), + CONFIG_STRING(CONFIG_AUDIO_SOURCE_TYPE, "audioSourceType", AUDIO_SOURCE_PDP), + CONFIG_STRING(CONFIG_AUDIO_SOURCE_NAME, "audioDevice", "/dev/dsp"), + CONFIG_STRING(CONFIG_AUDIO_MIXER_NAME, "audioMixer", "/dev/mixer"), + CONFIG_STRING(CONFIG_AUDIO_INPUT_NAME, "audioInput", "mix"), + + CONFIG_INT(CONFIG_AUDIO_SOURCE_TRACK, "audioSourceTrack", 0), + CONFIG_INT(CONFIG_AUDIO_CHANNELS, "audioChannels", 2), + CONFIG_INT(CONFIG_AUDIO_SAMPLE_RATE, "audioSampleRate", 44100), + CONFIG_INT(CONFIG_AUDIO_BIT_RATE_KBPS, "audioBitRate", 128), + CONFIG_INT(CONFIG_AUDIO_BIT_RATE, "audioBitRateBps", 128000), + CONFIG_STRING(CONFIG_AUDIO_ENCODING, "audioEncoding", AUDIO_ENCODING_AAC), + CONFIG_STRING(CONFIG_AUDIO_ENCODER, "audioEncoder", AUDIO_ENCODER_FAAC), + + CONFIG_BOOL(CONFIG_AUDIO_OSS_USE_SMALL_FRAGS, "audioOssUseSmallFrags", true), + CONFIG_INT(CONFIG_AUDIO_OSS_FRAGMENTS, "audioOssFragments", 128), + CONFIG_INT(CONFIG_AUDIO_OSS_FRAG_SIZE, "audioOssFragSize", 8), + + // VIDEO + + CONFIG_BOOL(CONFIG_VIDEO_ENABLE, "videoEnable", true), + CONFIG_STRING(CONFIG_VIDEO_SOURCE_TYPE, "videoSourceType", VIDEO_SOURCE_PDP), + CONFIG_STRING(CONFIG_VIDEO_SOURCE_NAME, "videoDevice", "/dev/video0"), + CONFIG_INT(CONFIG_VIDEO_INPUT, "videoInput", 1), + CONFIG_INT(CONFIG_VIDEO_SIGNAL, "videoSignal", VIDEO_SIGNAL_NTSC), + + CONFIG_INT(CONFIG_VIDEO_TUNER, "videoTuner", -1), + CONFIG_INT(CONFIG_VIDEO_CHANNEL_LIST_INDEX, "videoChannelListIndex", 0), + CONFIG_INT(CONFIG_VIDEO_CHANNEL_INDEX, "videoChannelIndex", 1), + + CONFIG_INT(CONFIG_VIDEO_SOURCE_TRACK, "videoSourceTrack", 0), + + CONFIG_BOOL(CONFIG_VIDEO_PREVIEW, "videoPreview", true), + CONFIG_BOOL(CONFIG_VIDEO_RAW_PREVIEW, "videoRawPreview", false), + CONFIG_BOOL(CONFIG_VIDEO_ENCODED_PREVIEW, "videoEncodedPreview", true), + + CONFIG_STRING(CONFIG_VIDEO_ENCODER, "videoEncoder", VIDEO_ENCODER_XVID), + CONFIG_STRING(CONFIG_VIDEO_ENCODING, "videoEncoding", VIDEO_ENCODING_MPEG4), + + CONFIG_INT(CONFIG_VIDEO_RAW_WIDTH, "videoRawWidth", 320), + CONFIG_INT(CONFIG_VIDEO_RAW_HEIGHT, "videoRawHeight", 240), + CONFIG_FLOAT(CONFIG_VIDEO_ASPECT_RATIO, "videoAspectRatio", VIDEO_STD_ASPECT_RATIO), + CONFIG_FLOAT(CONFIG_VIDEO_FRAME_RATE, "videoFrameRate", VIDEO_PAL_FRAME_RATE), + CONFIG_FLOAT(CONFIG_VIDEO_KEY_FRAME_INTERVAL, "videoKeyFrameInterval", 2.0), + + CONFIG_INT(CONFIG_VIDEO_BIT_RATE, "videoBitRate", 128), + CONFIG_INT(CONFIG_VIDEO_PROFILE_ID, "videoProfileId", MPEG4_SP_L3), + + CONFIG_INT(CONFIG_VIDEO_BRIGHTNESS, "videoBrightness", 50), + CONFIG_INT(CONFIG_VIDEO_HUE, "videoHue", 50), + CONFIG_INT(CONFIG_VIDEO_COLOR, "videoColor", 50), + CONFIG_INT(CONFIG_VIDEO_CONTRAST, "videoContrast", 50), + + CONFIG_INT(CONFIG_VIDEO_TIMEBITS, "videoTimebits", 0), + + CONFIG_BOOL(CONFIG_V4L_CACHE_TIMESTAMP, "videoTimestampCache", true), + CONFIG_INT(CONFIG_VIDEO_H261_QUALITY, "videoH261Quality", 10), + CONFIG_INT(CONFIG_VIDEO_H261_QUALITY_ADJ_FRAMES, "videoH261QualityAdjFrames", 8), + + CONFIG_INT(CONFIG_VIDEO_CAP_BUFF_COUNT, "videoCaptureBuffersCount", 16), + + // RECORD + CONFIG_BOOL(CONFIG_RECORD_ENABLE, "recordEnable", true), + CONFIG_BOOL(CONFIG_RECORD_RAW_AUDIO, "recordRawAudio", false), + CONFIG_BOOL(CONFIG_RECORD_RAW_VIDEO, "recordRawVideo", false), + CONFIG_BOOL(CONFIG_RECORD_ENCODED_AUDIO, "recordEncodedAudio", true), + CONFIG_BOOL(CONFIG_RECORD_ENCODED_VIDEO, "recordEncodedVideo", true), + + CONFIG_STRING(CONFIG_RECORD_MP4_FILE_NAME, "recordMp4File", "capture.mp4"), + CONFIG_BOOL(CONFIG_RECORD_MP4_HINT_TRACKS, "recordMp4HintTracks", true), + CONFIG_BOOL(CONFIG_RECORD_MP4_OVERWRITE, "recordMp4Overwrite", true), + CONFIG_BOOL(CONFIG_RECORD_MP4_OPTIMIZE, "recordMp4Optimize", false), + + // RTP + + CONFIG_BOOL(CONFIG_RTP_ENABLE, "rtpEnable", true), + CONFIG_STRING(CONFIG_RTP_DEST_ADDRESS, "rtpDestAddress", "127.0.0.1"), + CONFIG_STRING(CONFIG_RTP_AUDIO_DEST_ADDRESS, "audioRtpDestAddress", "127.0.0.1"), + + CONFIG_INT(CONFIG_RTP_AUDIO_DEST_PORT, "rtpAudioDestPort", 8000), + CONFIG_INT(CONFIG_RTP_VIDEO_DEST_PORT, "rtpVideoDestPort", 7070), + + CONFIG_INT(CONFIG_RTP_PAYLOAD_SIZE, "rtpPayloadSize", 1460), + CONFIG_INT(CONFIG_RTP_MCAST_TTL, "rtpMulticastTtl", 15), + + CONFIG_BOOL(CONFIG_RTP_DISABLE_TS_OFFSET, "rtpDisableTimestampOffset", false), + CONFIG_BOOL(CONFIG_RTP_USE_SSM, "rtpUseSingleSourceMulticast", false), + + CONFIG_STRING(CONFIG_SDP_FILE_NAME, "sdpFile", "capture.sdp"), + + CONFIG_BOOL(CONFIG_RTP_USE_MP3_PAYLOAD_14, "rtpUseMp4RtpPayload14", false), + CONFIG_BOOL(CONFIG_RTP_NO_B_RR_0, "rtpNoBRR0", false), + + // RAW sink + + CONFIG_BOOL(CONFIG_RAW_ENABLE, "rawEnable", false), + CONFIG_STRING(CONFIG_RAW_PCM_FILE_NAME, "rawAudioFile", "capture.pcm"), + CONFIG_BOOL(CONFIG_RAW_PCM_FIFO, "rawAudioUseFifo", false), + + CONFIG_STRING(CONFIG_RAW_YUV_FILE_NAME, "rawVideoFile", "capture.yuv"), + CONFIG_BOOL(CONFIG_RAW_YUV_FIFO, "rawVideoUseFifo", false) + +}; +#endif + +// forward declarations +class CVideoCapabilities; +class CAudioCapabilities; +class CLiveConfig; + +// some configuration utility routines +void GenerateMpeg4VideoConfig(CLiveConfig* pConfig); +bool GenerateSdpFile(CLiveConfig* pConfig); +struct session_desc_t; + +session_desc_t *createSdpDescription(CLiveConfig *pConfig, + char *sAudioDestAddr, + char *sVideoDestAddr, + int ttl, + bool allow_rtcp, + int video_port, + int audio_port); + +class CLiveConfig : public CConfigSet { +public: + CLiveConfig(SConfigVariable* variables, + config_index_t numVariables, const char* defaultFileName); + + ~CLiveConfig(); + + // recalculate derived values + void Update(); + void UpdateFileHistory(const char* fileName); + void UpdateVideo(); + void CalculateVideoFrameSize(); + void UpdateAudio(); + void UpdateRecord(); + + bool IsOneSource(); + bool IsCaptureVideoSource(); + bool IsCaptureAudioSource(); + bool IsFileVideoSource(); + bool IsFileAudioSource(); + + bool SourceRawVideo() { + return false; + } + + bool SourceRawAudio() { + return false; + } + +public: + // command line configuration + bool m_appAutomatic; + + // derived, shared video configuration + CVideoCapabilities* m_videoCapabilities; + bool m_videoEncode; + u_int32_t m_videoPreviewWindowId; + u_int16_t m_videoWidth; + u_int16_t m_videoHeight; + u_int16_t m_videoMaxWidth; + u_int16_t m_videoMaxHeight; + u_int32_t m_ySize; + u_int32_t m_uvSize; + u_int32_t m_yuvSize; + bool m_videoNeedRgbToYuv; + u_int16_t m_videoMpeg4ConfigLength; + u_int8_t* m_videoMpeg4Config; + u_int32_t m_videoMaxVopSize; + u_int8_t m_videoTimeIncrBits; + + // derived, shared audio configuration + CAudioCapabilities* m_audioCapabilities; + bool m_audioEncode; + + // derived, shared file configuration + u_int64_t m_recordEstFileSize; +}; + +#endif /* __LIVE_CONFIG_H__ */ + diff --git a/include/pdp_mp4configset.h b/include/pdp_mp4configset.h new file mode 100644 index 0000000..a20d260 --- /dev/null +++ b/include/pdp_mp4configset.h @@ -0,0 +1,532 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is MPEG4IP. + * + * The Initial Developer of the Original Code is Cisco Systems Inc. + * Portions created by Cisco Systems Inc. are + * Copyright (C) Cisco Systems Inc. 2000, 2001. All Rights Reserved. + * + * Contributor(s): + * Dave Mackie dmackie@cisco.com + * Bill May wmay@cisco.com + * + * Adapted for PD/PDP by Yves Degoyon (ydegoyon@free.fr) + */ + +#ifndef __CONFIG_SET_H__ +#define __CONFIG_SET_H__ + +#include <mpeg4ip.h> + +#ifndef CONFIG_SAFETY +#define CONFIG_SAFETY 1 +#endif + +typedef u_int32_t config_integer_t; + +typedef u_int16_t config_index_t; + + +enum ConfigException { + CONFIG_ERR_INAME, + CONFIG_ERR_TYPE, + CONFIG_ERR_MEMORY, +}; + +// TBD type specific exception info and printing utility +class CConfigException { +public: + CConfigException(ConfigException e) { + type = e; + fprintf( stderr, "pdp_mp4configset : exception : type : %d\n", e ); + } + ConfigException type; +}; + +#define CONFIG_MAX_STRLEN 255 + +// TBD weld this in, and throw exception +inline char* stralloc(const char* src) { + char* dst = (char*)malloc(strlen(src)+1); + if (dst) { + strcpy(dst, src); + } + return dst; +} + +enum ConfigType { + CONFIG_TYPE_UNDEFINED, + CONFIG_TYPE_INTEGER, + CONFIG_TYPE_BOOL, + CONFIG_TYPE_STRING, + CONFIG_TYPE_FLOAT +}; + +union UConfigValue { + UConfigValue(void) { + m_svalue = NULL; + } + UConfigValue(config_integer_t ivalue) { + m_ivalue = ivalue; + } + UConfigValue(bool bvalue) { + m_bvalue = bvalue; + } + UConfigValue(char* svalue) { + m_svalue = svalue; + } + UConfigValue(float fvalue) { + m_fvalue = fvalue; + } + + config_integer_t m_ivalue; + bool m_bvalue; + char* m_svalue; + float m_fvalue; +}; + +struct SConfigVariable { + config_index_t *m_iName; + const char* m_sName; + ConfigType m_type; + UConfigValue m_defaultValue; + UConfigValue m_value; + + const char* ToAscii() { + static char sBuf[CONFIG_MAX_STRLEN+3]; + switch (m_type) { + case CONFIG_TYPE_INTEGER: + sprintf(sBuf, "%d", m_value.m_ivalue); + return sBuf; + case CONFIG_TYPE_BOOL: + sprintf(sBuf, "%d", m_value.m_bvalue); + return sBuf; + case CONFIG_TYPE_STRING: + if (strchr(m_value.m_svalue, ' ')) { + sBuf[0] = '"'; + strncpy(&sBuf[1], m_value.m_svalue, CONFIG_MAX_STRLEN); + strcpy(&sBuf[ + MIN(strlen(m_value.m_svalue), CONFIG_MAX_STRLEN)+1], "\""); + } + return m_value.m_svalue; + case CONFIG_TYPE_FLOAT: + sprintf(sBuf, "%f", m_value.m_fvalue); + return sBuf; + default: + return ""; + } + } + + bool FromAscii(const char* s) { + switch (m_type) { + case CONFIG_TYPE_INTEGER: + return (sscanf(s, " %i ", &m_value.m_ivalue) == 1); + case CONFIG_TYPE_BOOL: + // OPTION could add "yes/no", "true/false" + if (sscanf(s, " %u ", &m_value.m_ivalue) != 1) { + return false; + } + m_value.m_bvalue = m_value.m_ivalue ? true : false; + return true; + case CONFIG_TYPE_STRING: + // N.B. assuming m_svalue has been alloc'ed + { + size_t len = strlen(s); + free(m_value.m_svalue); + if (*s == '"' && s[len] == '"') { + m_value.m_svalue = strdup(s + 1); + m_value.m_svalue[len - 1] = '\0'; + } else { + m_value.m_svalue = strdup(s); + } + if (m_value.m_svalue == NULL) { + throw new CConfigException(CONFIG_ERR_MEMORY); + } + return true; + } + case CONFIG_TYPE_FLOAT: + return (sscanf(s, " %f ", &m_value.m_fvalue) == 1); + default: + return false; + } + } + + void SetToDefault(void) { + switch (m_type) { + case CONFIG_TYPE_INTEGER: + m_value.m_ivalue = m_defaultValue.m_ivalue; + break; + case CONFIG_TYPE_BOOL: + m_value.m_bvalue = m_defaultValue.m_bvalue; + break; + case CONFIG_TYPE_STRING: + // free(m_value.m_svalue); + m_value.m_svalue = stralloc(m_defaultValue.m_svalue); + if (m_value.m_svalue == NULL) { + throw new CConfigException(CONFIG_ERR_MEMORY); + } + break; + case CONFIG_TYPE_FLOAT: + m_value.m_fvalue = m_defaultValue.m_fvalue; + break; + default: + break; + } + } + + bool IsValueDefault(void) { + switch (m_type) { + case CONFIG_TYPE_INTEGER: + return m_value.m_ivalue == m_defaultValue.m_ivalue; + case CONFIG_TYPE_BOOL: + return m_value.m_bvalue == m_defaultValue.m_bvalue; + case CONFIG_TYPE_STRING: + return (strcmp(m_value.m_svalue, m_defaultValue.m_svalue) == 0); + case CONFIG_TYPE_FLOAT: + return m_value.m_fvalue == m_defaultValue.m_fvalue; + default: + return false; + } + } + void CleanUpConfig(void) { + if (m_type == CONFIG_TYPE_STRING) { + CHECK_AND_FREE(m_value.m_svalue); + } + } +}; + +struct SUnknownConfigVariable { + struct SUnknownConfigVariable *next; + char *value; +}; + +class CConfigSet { +public: + CConfigSet(SConfigVariable* variables, + config_index_t numVariables, + const char* defaultFileName) { + uint32_t size; + m_fileName = NULL; + m_debug = false; + m_variables = variables; + m_numVariables = numVariables; + size = sizeof(SConfigVariable) * numVariables; + m_variables = + (SConfigVariable*)malloc(size); + + memcpy(m_variables, variables, size); + m_defaultFileName = strdup(defaultFileName); + SetToDefaults(); + m_unknown_head = NULL; + }; + + ~CConfigSet() { + free(m_fileName); + for (config_index_t i = 0; i < m_numVariables; i++) { + m_variables[i].CleanUpConfig(); + } + free(m_variables); + m_variables = NULL; + SUnknownConfigVariable *ptr = m_unknown_head; + while (ptr != NULL) { + m_unknown_head = ptr->next; + free(ptr->value); + free(ptr); + ptr = m_unknown_head; + } + CHECK_AND_FREE(m_defaultFileName); + } + + void InitializeIndexes(void) { + for (config_index_t ix = 0; ix < m_numVariables; ix++) { + *m_variables[ix].m_iName = ix; + } + } + + void AddConfigVariables (SConfigVariable* vars, + config_index_t numVariables) { + config_index_t start = m_numVariables; + uint32_t size = sizeof(SConfigVariable) * + (m_numVariables + numVariables); + m_variables = (SConfigVariable*)realloc(m_variables, size); + memcpy(&m_variables[m_numVariables], vars, + numVariables * sizeof(SConfigVariable)); + m_numVariables += numVariables; + SetToDefaults(start); + } + + const char* GetFileName() { + return m_fileName; + } + + inline void CheckIName(config_index_t iName) { + if (iName >= m_numVariables) { + throw new CConfigException(CONFIG_ERR_INAME); + } + if (*m_variables[iName].m_iName != iName) { + throw new CConfigException(CONFIG_ERR_INAME); + } + } + + inline void CheckIntegerType(config_index_t iName) { + if (m_variables[iName].m_type != CONFIG_TYPE_INTEGER) { + throw new CConfigException(CONFIG_ERR_TYPE); + } + } + + inline void CheckBoolType(config_index_t iName) { + if (m_variables[iName].m_type != CONFIG_TYPE_BOOL) { + throw new CConfigException(CONFIG_ERR_TYPE); + } + } + + inline void CheckStringType(config_index_t iName) { + if (m_variables[iName].m_type != CONFIG_TYPE_STRING) { + throw new CConfigException(CONFIG_ERR_TYPE); + } + } + + inline void CheckFloatType(config_index_t iName) { + if (m_variables[iName].m_type != CONFIG_TYPE_FLOAT) { + throw new CConfigException(CONFIG_ERR_TYPE); + } + } + + inline bool IsDefault (const config_index_t iName) { +#if CONFIG_SAFETY + CheckIName(iName); + CheckIntegerType(iName); +#endif + return m_variables[iName].IsValueDefault(); + }; + + inline config_integer_t GetIntegerValue(const config_index_t iName) { +#if CONFIG_SAFETY + CheckIName(iName); + CheckIntegerType(iName); +#endif + return m_variables[iName].m_value.m_ivalue; + } + + inline void SetIntegerValue(const config_index_t iName, + config_integer_t ivalue) { +#if CONFIG_SAFETY + CheckIName(iName); + CheckIntegerType(iName); +#endif + m_variables[iName].m_value.m_ivalue = ivalue; + } + + inline bool GetBoolValue(const config_index_t iName) { +#if CONFIG_SAFETY + CheckIName(iName); + CheckBoolType(iName); +#endif + return m_variables[iName].m_value.m_bvalue;; + } + + inline void SetBoolValue(const config_index_t iName, bool bvalue) { +#if CONFIG_SAFETY + CheckIName(iName); + CheckBoolType(iName); +#endif + m_variables[iName].m_value.m_bvalue = bvalue; + } + + inline char* GetStringValue(const config_index_t iName) { +#if CONFIG_SAFETY + CheckIName(iName); + CheckStringType(iName); +#endif + return m_variables[iName].m_value.m_svalue; + } + + inline void SetStringValue(const config_index_t iName, const char* svalue) { + printf ( "setting variable : %d to : %s\n", iName, svalue ); +#if CONFIG_SAFETY + CheckIName(iName); + CheckStringType(iName); +#endif + if (svalue == m_variables[iName].m_value.m_svalue) { + return; + } + // free(m_variables[iName].m_value.m_svalue); + m_variables[iName].m_value.m_svalue = stralloc(svalue); + if (m_variables[iName].m_value.m_svalue == NULL) { + throw new CConfigException(CONFIG_ERR_MEMORY); + } + } + + inline float GetFloatValue(const config_index_t iName) { +#if CONFIG_SAFETY + CheckIName(iName); + CheckFloatType(iName); +#endif + return m_variables[iName].m_value.m_fvalue; + } + + inline void SetFloatValue(const config_index_t iName, float fvalue) { +#if CONFIG_SAFETY + CheckIName(iName); + CheckFloatType(iName); +#endif + m_variables[iName].m_value.m_fvalue = fvalue; + } + + void SetToDefaults(int start = 0) { + for (config_index_t i = start; i < m_numVariables; i++) { + m_variables[i].SetToDefault(); + } + } + + void SetToDefault(const config_index_t iName) { + m_variables[iName].SetToDefault(); + } + + void ProcessLine (char *line) { + // comment + if (line[0] == '#') { + return; + } + char* s = line; + while (*s != '\0') s++; + s--; + while (isspace(*s)) { + *s = '\0'; + s--; + } + s = line; + + SConfigVariable* var = FindByName(strsep(&s, "=")); + if (var == NULL || s == NULL) { + if (s != NULL) { + *(s - 1) = '='; // restore seperation character + SUnknownConfigVariable *ptr; + ptr = MALLOC_STRUCTURE(SUnknownConfigVariable); + ptr->next = m_unknown_head; + ptr->value = strdup(line); + m_unknown_head = ptr; + } + if (m_debug) { + fprintf(stderr, "bad config line %s\n", s); + } + return; + } + if (!var->FromAscii(s)) { + if (m_debug) { + fprintf(stderr, "bad config value in line %s\n", s); + } + } + } + + bool ReadFromFile(const char* fileName) { + free(m_fileName); + m_fileName = stralloc(fileName); + FILE* pFile = fopen(fileName, "r"); + if (pFile == NULL) { + if (m_debug) { + fprintf(stderr, "couldn't open file %s\n", fileName); + } + return false; + } + char line[256]; + while (fgets(line, sizeof(line), pFile)) { + ProcessLine(line); + } + fclose(pFile); + return true; + } + + bool WriteToFile(const char* fileName, bool allValues = false) { + FILE* pFile = fopen(fileName, "w"); + config_index_t i; + SConfigVariable *var; + SUnknownConfigVariable *ptr; + + if (pFile == NULL) { + if (m_debug) { + fprintf(stderr, "couldn't open file %s\n", fileName); + } + return false; + } + for (i = 0; i < m_numVariables; i++) { + var = &m_variables[i]; + if (allValues || !var->IsValueDefault()) { + fprintf(pFile, "%s=%s\n", var->m_sName, var->ToAscii()); + } + } + ptr = m_unknown_head; + while (ptr != NULL) { + fprintf(pFile, "%s\n", ptr->value); + ptr = ptr->next; + } + fclose(pFile); + return true; + } + + bool ReadDefaultFile(void) { + return ReadFromFile(m_defaultFileName); + } + bool WriteDefaultFile(void) { + return WriteToFile(m_defaultFileName); + } + + void SetDebug(bool debug = true) { + m_debug = debug; + } + +protected: + SConfigVariable* FindByName(const char* sName) { + for (config_index_t i = 0; i < m_numVariables; i++) { + if (!strcasecmp(sName, m_variables[i].m_sName)) { + return &m_variables[i]; + } + } + return NULL; + }; + +protected: + SConfigVariable* m_variables; + config_index_t m_numVariables; + const char* m_defaultFileName; + bool m_debug; + char* m_fileName; + SUnknownConfigVariable *m_unknown_head; +}; + +// To define configuration variables - first DECLARE_CONFIG in a +// .h file. Then in either a C++ or h file, define a static array +// of configuration variables using CONFIG_BOOL, CONFIG_FLOAT, CONFIG_INT +// or CONFIG_STRING. You can include the .h anywhere you use the variable - +// in a .cpp, you must include the .h file with DECLARE_CONFIG_VARIABLES +// defined before the .h file. Note - if you're already including mp4live.h, +// you need to #define the DECLARE_CONFIG_VARIABLES after the include. +// +// Note - you want to add the config variables BEFORE the ReadFromFile +// call +#ifdef DECLARE_CONFIG_VARIABLES +#define DECLARE_CONFIG(a) config_index_t (a); +#else +#define DECLARE_CONFIG(a) extern config_index_t (a); +#endif + +#define CONFIG_BOOL(var, name, defval) \ + { &(var), (name), CONFIG_TYPE_BOOL, (defval), } +#define CONFIG_FLOAT(var, name, defval) \ + { &(var), (name), CONFIG_TYPE_FLOAT,(float) (defval), } +#define CONFIG_INT(var, name, defval) \ + { &(var), (name), CONFIG_TYPE_INTEGER,(config_integer_t) (defval), } +#define CONFIG_STRING(var, name, defval) \ + { &(var), (name), CONFIG_TYPE_STRING, (defval), } + + +#endif /* __CONFIG_SET_H__ */ diff --git a/include/pdp_mp4playermedia.h b/include/pdp_mp4playermedia.h new file mode 100644 index 0000000..025fb57 --- /dev/null +++ b/include/pdp_mp4playermedia.h @@ -0,0 +1,232 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is MPEG4IP. + * + * The Initial Developer of the Original Code is Cisco Systems Inc. + * Portions created by Cisco Systems Inc. are + * Copyright (C) Cisco Systems Inc. 2000, 2001. All Rights Reserved. + * + * Contributor(s): + * Bill May wmay@cisco.com + * + * Adapted to PD/PDP by Yves Degoyon (ydegoyon@free.fr) + */ + +/* + * player_media.h - provides CPlayerMedia class, which defines the + * interface to a particular media steam. + */ + +#ifndef __PLAYER_MEDIA_H__ +#define __PLAYER_MEDIA_H__ + +#include <SDL.h> +#include <SDL_thread.h> +#include <sdp/sdp.h> +#include <rtsp/rtsp_client.h> +#include <rtp/rtp.h> +#include "our_bytestream.h" +#include "our_msg_queue.h" +#include "codec_plugin.h" + +class CPlayerSession; +class CPDPAudioSync; +class CPDPVideoSync; +class C2ConsecIpPort; +class COurInByteStream; +class CRtpByteStreamBase; + +class CPlayerMedia { + public: + CPlayerMedia(CPlayerSession *p); + ~CPlayerMedia(); + /* API routine - create - for RTP stream */ + int create_streaming(media_desc_t *sdp_media, + char *errmsg, + uint32_t errlen, + int on_demand, + int use_rtsp, + int media_number_in_session); + /* API routine - create - where we provide the bytestream */ + int create(COurInByteStream *b, + int is_video, + char *errmsg = NULL, + uint32_t errlen = 0, + int streaming = 0); + /* API routine - play, pause */ + int do_play(double start_time_offset, char *errmsg, uint32_t errlen); + int do_pause(void); + int is_video(void) { return (m_is_video); }; + double get_max_playtime(void); + /* API routine - interface for decoding start/continue */ + void start_decoding(void); + void bytestream_primed(void); + /* API routine - ip port information */ + uint16_t get_our_port (void) { return m_our_port; }; + void set_server_port (uint16_t port) { m_server_port = port; }; + uint16_t get_server_port (void) { return m_server_port; }; + + media_desc_t *get_sdp_media_desc (void) { return m_media_info; }; + void set_source_addr (char *s) + { + if (m_source_addr != NULL) free(m_source_addr); + m_source_addr = s; + } + const char *get_source_addr(void); + CPlayerMedia *get_next (void) { return m_next; }; + void set_next (CPlayerMedia *newone) { m_next = newone; }; + int decode_thread(void); + + /* Public RTP routines - receive thread, callback, and routines to + * pass information from rtsp to rtp byte stream + */ + int recv_thread(void); + void recv_callback(struct rtp *session, rtp_event *e); + void set_rtp_ssrc (uint32_t ssrc) + { m_rtp_ssrc = ssrc; m_rtp_ssrc_set = TRUE;}; + void set_rtp_base_ts(uint32_t time); + void set_rtp_base_seq(uint16_t seq); + + void set_video_sync(CPDPVideoSync *p) {m_video_sync = p;}; + void set_audio_sync(CPDPAudioSync *p) {m_audio_sync = p;}; + + const video_info_t *get_video_info (void) { return m_video_info; }; + const audio_info_t *get_audio_info (void) { return m_audio_info; }; + + int create_video_plugin(const codec_plugin_t *p, + const char *compressor, + int profile, + int type, + format_list_t *sdp_media, + video_info_t *video, + const uint8_t *user_data, + uint32_t userdata_size); + int create_audio_plugin(const codec_plugin_t *p, + const char *compressor, + int profile, + int type, + format_list_t *sdp_media, + audio_info_t *audio, + const uint8_t *user_data, + uint32_t userdata_size); + void set_plugin_data (const codec_plugin_t *p, + codec_data_t *d, + video_vft_t *v, + audio_vft_t *a); + int get_plugin_status(char *buffer, uint32_t buflen); + void set_user_data (const uint8_t *udata, int length) { + m_user_data = udata; + m_user_data_size = length; + } + rtsp_session_t *get_rtsp_session(void) { return m_rtsp_session; }; + void rtp_init_tcp(void); + void rtp_periodic(void); + void rtp_start(void); + void rtp_end(void); + int rtp_receive_packet(unsigned char interleaved, struct rtp_packet *, int len); + int rtcp_send_packet(uint8_t *buffer, int buflen); + int get_rtp_media_number (void) { return m_rtp_media_number_in_session; }; + void syncronize_rtp_bytestreams(rtcp_sync_t *sync); + private: + int create_common(int is_video, char *errmsg, uint32_t errlen); + void wait_on_bytestream(void); + int m_streaming; + int m_is_video; + int m_paused; + CPlayerMedia *m_next; + CPlayerSession *m_parent; + media_desc_t *m_media_info; + format_list_t *m_media_fmt; // format currently running. + rtsp_session_t *m_rtsp_session; + C2ConsecIpPort *m_ports; + in_port_t m_our_port; + in_port_t m_server_port; + char *m_source_addr; + + time_t m_start_time; + int m_stream_ondemand; + int m_sync_time_set; + uint64_t m_sync_time_offset; + uint32_t m_rtptime_tickpersec; + double m_play_start_time; + // Receive thread variables + SDL_Thread *m_recv_thread; + + /************************************************************************* + * RTP variables - used to pass info to the bytestream + *************************************************************************/ + int m_rtp_ondemand; + int m_rtp_use_rtsp; + int m_rtp_media_number_in_session; + int m_rtp_buffering; + struct rtp *m_rtp_session; + CRtpByteStreamBase *m_rtp_byte_stream; + CMsgQueue m_rtp_msg_queue; + + rtp_packet *m_head, *m_tail; + uint32_t m_rtp_queue_len; + + // from rtsp... + int m_rtp_ssrc_set; + uint32_t m_rtp_ssrc; + int m_rtsp_base_ts_received; + uint32_t m_rtp_base_ts; + int m_rtsp_base_seq_received; + uint16_t m_rtp_base_seq; + + int determine_payload_type_from_rtp(void); + void create_rtp_byte_stream(uint8_t payload, uint64_t tps, format_list_t *fmt); + void clear_rtp_packets(void); + + // from rtcp, for broadcast, in case we get an RTCP before we determine + // the payload type + uint32_t m_rtcp_ntp_frac; + uint32_t m_rtcp_ntp_sec; + uint32_t m_rtcp_rtp_ts; + int m_rtcp_received; + + volatile int m_rtp_inited; + + /************************************************************************* + * Decoder thread variables + *************************************************************************/ + SDL_Thread *m_decode_thread; + volatile int m_decode_thread_waiting; + SDL_sem *m_decode_thread_sem; + + const codec_plugin_t *m_plugin; + codec_data_t *m_plugin_data; + + // State change variable + CMsgQueue m_decode_msg_queue; + // Private routines + int process_rtsp_transport(char *transport); + CPDPAudioSync *m_audio_sync; + CPDPVideoSync *m_video_sync; + void parse_decode_message(int &thread_stop, int &decoding); + COurInByteStream *m_byte_stream; + video_info_t *m_video_info; + audio_info_t *m_audio_info; + + const uint8_t *m_user_data; + int m_user_data_size; + +}; + +int pdp_process_rtsp_rtpinfo(char *rtpinfo, CPlayerSession *session, CPlayerMedia *media); + +extern audio_vft_t audio_vft; +extern video_vft_t video_vft; + +#define media_message(loglevel, fmt...) message(loglevel, "media", fmt) + +#endif diff --git a/include/pdp_mp4playersession.h b/include/pdp_mp4playersession.h new file mode 100644 index 0000000..5979aad --- /dev/null +++ b/include/pdp_mp4playersession.h @@ -0,0 +1,249 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is MPEG4IP. + * + * The Initial Developer of the Original Code is Cisco Systems Inc. + * Portions created by Cisco Systems Inc. are + * Copyright (C) Cisco Systems Inc. 2000, 2001. All Rights Reserved. + * + * Contributor(s): + * Bill May wmay@cisco.com + * video aspect ratio by: + * Peter Maersk-Moller peter @maersk-moller.net + * + * Adapted for PD/PDP by Yves Degoyon (ydegoyon@free.fr) + * + */ + +/* + * pdp_mp4playersession.h - provides definitions for a CPlayerSession. + * CPlayerSession is the base class that provides a combination audio/video + * stream/file playback. + * This class should be the main interface between any outside functionality + * and the player window. + */ + + +#ifndef __PDP_MP4PLAYERSESSION_H__ +#define __PDP_MP4PLAYERSESSION_H__ + +struct rtcp_sync_t; +typedef struct rtcp_sync_t rtcp_sync_t; + +#include <rtsp/rtsp_client.h> +#include <sdp/sdp.h> +#include "our_msg_queue.h" +#include "ip_port.h" +#include "pdp_mp4player~.h" + +class CPlayerMedia; +class CPDPAudioSync; +class CPDPVideoSync; + +typedef enum { + SESSION_PAUSED, + SESSION_BUFFERING, + SESSION_PLAYING, + SESSION_DONE +} session_state_t; + +typedef struct rtcp_sync_t { + uint64_t first_pak_ts; + uint64_t rtcp_ts; + uint32_t first_pak_rtp_ts; + uint32_t rtcp_rtp_ts; + uint64_t timescale; +} rtcp_sync_t; + +typedef void (*media_close_callback_f)(void *); + +class CPlayerSession { + public: + /* + * API routine - create player session. + */ + CPlayerSession(CMsgQueue *master_queue, + SDL_sem *master_sem, + const char *name, + t_pdp_mp4player *pdp_father); + /* + * API routine - destroy session - free all sub-structures, cleans + * up rtsp, etc + */ + ~CPlayerSession(); + /* + * API routine - create a rtsp session with the url. After that, you + * need to associate media + */ + int create_streaming_broadcast(session_desc_t *sdp, + char *ermsg, + uint32_t errlen); + int create_streaming_ondemand(const char *url, + char *errmsg, + uint32_t errlen, + int use_rtp_tcp); + /* + * API routine - play at time. If start_from_begin is FALSE, start_time + * and we're paused, it will continue from where it left off. + */ + int play_all_media(int start_from_begin = FALSE, double start_time = 0.0, + char *errmsg = NULL, uint32_t errlen = 0); + /* + * API routine - pause + */ + int pause_all_media(void); + /* + * API routine for media set up - associate a created + * media with the session. + */ + void add_media(CPlayerMedia *m); + /* + * API routine - returns sdp info for streamed session + */ + session_desc_t *get_sdp_info (void) { return m_sdp_info;} ; + rtsp_client_t *get_rtsp_client (void) { return m_rtsp_client; }; + /* + * API routine - after setting up media, need to set up sync thread + */ + void set_up_sync_thread(void); + CPDPVideoSync *set_up_video_sync(void); + CPDPAudioSync *set_up_audio_sync(void); + /* + * API routine - get the current time + */ + uint64_t get_playing_time (void) { + if (m_streaming && session_is_seekable() == 0) { + return (m_current_time - m_first_time_played); + } + return (m_current_time); + }; + /* + * API routine - get max play time + */ + double get_max_time (void); + /* + * Other API routines + */ + int session_has_audio(void); + int session_has_video(void); + void set_audio_volume(int volume); + int get_audio_volume(void) { return m_audio_volume; }; + void session_set_seekable (int seekable) { + m_seekable = seekable; + }; + int session_is_seekable (void) { + return (m_seekable); + }; + session_state_t get_session_state(void) { + return (m_session_state); + } + void set_media_close_callback (media_close_callback_f mccf, + void *mccd) { + m_media_close_callback = mccf; + m_media_close_callback_data = mccd; + } + int session_is_network (int &on_demand, int &rtp_over_rtsp) { + if (m_streaming == 0) { + return 0; + } + if (m_seekable) { + on_demand = 1; + rtp_over_rtsp = m_rtp_over_rtsp; + } else { + on_demand = 0; + rtp_over_rtsp = 0; + } + return 1; + } + /* + * Non-API routines - used for c interfaces, for sync task APIs. + */ + void wake_sync_thread (void) { + SDL_SemPost(m_sync_sem); + } + int send_sync_thread_a_message(uint32_t msgval, + unsigned char *msg = NULL, + uint32_t msg_len = 0) + { + return (m_sync_thread_msg_queue.send_message(msgval, msg, msg_len, m_sync_sem)); + }; + int sync_thread(int state); + uint64_t get_current_time(void); + void audio_is_ready (uint64_t latency, uint64_t time); + void adjust_start_time(int64_t delta); + int session_control_is_aggregate (void) { + return m_session_control_is_aggregate; + }; + void set_session_control (int is_aggregate) { + m_session_control_is_aggregate = is_aggregate; + } + CPlayerMedia *rtsp_url_to_media (const char *url); + int set_session_desc(int line, const char *desc); + const char *get_session_desc(int line); + void streaming_media_set_up(void) { m_streaming_media_set_up = 1; }; + CIpPort **get_unused_ip_port_ptr(void) { return &m_unused_ports; }; + void syncronize_rtp_bytestreams(rtcp_sync_t *sync); + private: + int process_msg_queue(int state); + int sync_thread_init(void); + int sync_thread_wait_sync(void); + int sync_thread_wait_audio(void); + int sync_thread_playing(void); + int sync_thread_paused(void); + int sync_thread_done(void); + const char *m_session_name; + const char *m_content_base; + int m_paused; + int m_streaming; + uint64_t m_current_time; // current time playing + uint64_t m_start; + uint64_t m_latency; + int m_clock_wrapped; + uint64_t m_play_start_time; + session_desc_t *m_sdp_info; + rtsp_client_t *m_rtsp_client; + CPlayerMedia *m_my_media; + CPDPAudioSync *m_audio_sync; + CPDPVideoSync *m_video_sync; + SDL_Thread *m_sync_thread; + SDL_sem *m_sync_sem; + CMsgQueue *m_master_msg_queue; + SDL_sem *m_master_msg_queue_sem; + CMsgQueue m_sync_thread_msg_queue; + range_desc_t *m_range; + int m_session_control_is_aggregate; + int m_waiting_for_audio; + int m_audio_volume; + int m_screen_scale; + int m_fullscreen; + int m_pixel_height; + int m_pixel_width; + int m_seekable; + volatile int m_sync_pause_done; + session_state_t m_session_state; + int m_hardware_error; + #define SESSION_DESC_COUNT 4 + const char *m_session_desc[SESSION_DESC_COUNT]; + media_close_callback_f m_media_close_callback; + void *m_media_close_callback_data; + int m_streaming_media_set_up; + CIpPort *m_unused_ports; + int m_rtp_over_rtsp; + uint64_t m_first_time_played; + bool m_have_audio_rtcp_sync; + rtcp_sync_t m_audio_rtcp_sync; + t_pdp_mp4player *m_father; +}; + +int pdp_sync_thread(void *data); + +#endif diff --git a/include/pdp_mp4player~.h b/include/pdp_mp4player~.h new file mode 100644 index 0000000..05e6d14 --- /dev/null +++ b/include/pdp_mp4player~.h @@ -0,0 +1,118 @@ + +#ifndef __PDP_MP4PLAYER__ +#define __PDP_MP4PLAYER__ + +struct pdp_mp4player_struct; +typedef struct pdp_mp4player_struct t_pdp_mp4player; + +#include "pdp.h" +#include "yuv.h" +#include <pthread.h> +#include <stdio.h> +#include <unistd.h> +#include <math.h> +#include <time.h> +#include <sys/time.h> +#include <sys/resource.h> +#include "pdp_mp4playersession.h" +#include "pdp_mp4playermedia.h" +#include "pdp_mp4videosync.h" +#include "pdp_mp4audiosync.h" +#include "media_utils.h" +#include "codec_plugin_private.h" +#include "our_config_file.h" +#include "player_util.h" +#include <rtp/debug.h> +#include <libhttp/http.h> + + +/* mpeg4ip includes taken from the source tree ( not exported ) */ +#include <mp4.h> +#undef DECLARE_CONFIG_VARIABLES +#include "config_set.h" + +#undef CONFIG_BOOL +#define CONFIG_BOOL(var, name, defval) \ + { &(var), (name), CONFIG_TYPE_BOOL, (defval), (defval) } +#undef CONFIG_FLOAT +#define CONFIG_FLOAT(var, name, defval) \ + { &(var), (name), CONFIG_TYPE_FLOAT,(float) (defval), (float) (defval) } +#undef CONFIG_INT +#define CONFIG_INT(var, name, defval) \ + { &(var), (name), CONFIG_TYPE_INTEGER,(config_integer_t) (defval), (config_integer_t)(defval) } +#undef CONFIG_STRING +#define CONFIG_STRING(var, name, defval) \ + { &(var), (name), CONFIG_TYPE_STRING, (defval), (defval) } + +#include "pdp_mp4config.h" + +#undef DECLARE_CONFIG_VARIABLES +#ifndef debug_message +#define debug_message post +#endif +#include "rtp_transmitter.h" +#include "pdp_mp4videosource.h" +#include "pdp_mp4audiosource.h" + +#define DEFAULT_CHANNELS 1 +#define MIN_PRIORITY -20 +#define DEFAULT_PRIORITY 0 +#define MAX_PRIORITY 20 + +#define VIDEO_BUFFER_SIZE (1024*1024) +#define MAX_AUDIO_PACKET_SIZE (128 * 1024) +#define MIN_AUDIO_SIZE (64 * 1024) +#define AUDIO_PACKET_SIZE (2*1152) + +typedef struct pdp_mp4player_struct +{ + t_object x_obj; + t_float x_f; + + t_int x_packet0; + t_int x_dropped; + + t_pdp *x_header; + short int *x_data; + t_int x_vwidth; + t_int x_vheight; + t_int x_vsize; + + t_outlet *x_pdp_out; // output decoded pdp packets + t_outlet *x_outlet_left; // left audio output + t_outlet *x_outlet_right; // right audio output + t_outlet *x_outlet_streaming; // indicates the action of streaming + t_outlet *x_outlet_nbframes; // number of frames emitted + t_outlet *x_outlet_framerate; // real framerate + + char *x_url; + t_int x_rtpovertcp; // flag to bypass certain firewalls (tcp mode) + t_int x_streaming; // streaming flag + t_int x_nbframes; // number of frames emitted + t_int x_framerate; // framerate + t_int x_samplerate; // audio sample rate + t_int x_audiochannels; // audio channels + t_int x_audioon; // enough audio data to start playing + struct timeval x_starttime; // streaming starting time + t_int x_cursec; // current second + t_int x_secondcount; // number of frames received in the current second + pthread_t x_decodechild;// stream decoding thread + t_int x_priority; // priority of decoding thread + t_int x_newpicture; // flag indicating a new picture + + /* audio structures */ + t_int x_audio; // flag to activate the decoding of audio + short x_audio_buf[4*MAX_AUDIO_PACKET_SIZE]; /* buffer for audio from stream*/ + short x_audio_in[4*MAX_AUDIO_PACKET_SIZE]; /* buffer for resampled PCM audio */ + t_int x_audioin_position; // writing position for incoming audio + + /* mpeg4hippies structures */ + CPlayerSession *x_psession; + CMsgQueue x_queue; + SDL_sem *x_psem; + t_int x_decodingstate; // internal decoding state + +} t_pdp_mp4player; + +#endif + diff --git a/include/pdp_mp4rtpbytestream.h b/include/pdp_mp4rtpbytestream.h new file mode 100644 index 0000000..bc087ea --- /dev/null +++ b/include/pdp_mp4rtpbytestream.h @@ -0,0 +1,213 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is MPEG4IP. + * + * The Initial Developer of the Original Code is Cisco Systems Inc. + * Portions created by Cisco Systems Inc. are + * Copyright (C) Cisco Systems Inc. 2000, 2001. All Rights Reserved. + * + * Contributor(s): + * Bill May wmay@cisco.com + */ +/* + * player_rtp_bytestream.h - provides an RTP bytestream for the codecs + * to access + */ + +#ifndef __RTP_BYTESTREAM_H__ +#define __RTP_BYTESTREAM_H__ 1 +#include "our_bytestream.h" +#include "player_util.h" +#include "rtp/rtp.h" +#include <SDL.h> +#include <SDL_thread.h> +#include <sdp/sdp.h> + +class CRtpByteStreamBase : public COurInByteStream +{ + public: + CRtpByteStreamBase(const char *name, + format_list_t *fmt, + unsigned int rtp_pt, + int ondemand, + uint64_t tickpersec, + rtp_packet **head, + rtp_packet **tail, + int rtp_seq_set, + uint16_t rtp_base_seq, + int rtp_ts_set, + uint32_t rtp_base_ts, + int rtcp_received, + uint32_t ntp_frac, + uint32_t ntp_sec, + uint32_t rtp_ts); + + ~CRtpByteStreamBase(); + int eof (void) { return m_eof; }; + virtual void reset(void) { + player_debug_message("rtp bytestream reset"); + init(); + m_buffering = 0; + m_base_ts_set = 0; + m_rtp_base_seq_set = 0; + + }; + void set_skip_on_advance (uint32_t bytes_to_skip) { + m_skip_on_advance_bytes = bytes_to_skip; + }; + double get_max_playtime (void) { + if (m_fmt->media->media_range.have_range) { + return m_fmt->media->media_range.range_end; + } else if (m_fmt->media->parent->session_range.have_range) { + return m_fmt->media->parent->session_range.range_end; + } + return 0.0; + }; + + // various routines for RTP interface. + void set_rtp_base_ts(uint32_t t, uint64_t value = 0) { + m_base_ts_set = true; + m_base_rtp_ts = t; + m_base_ts = value; + }; + void set_rtp_base_seq(uint16_t s) { + m_rtp_base_seq_set = true; + m_rtp_base_seq = s; + }; + int can_skip_frame (void) { return 1; } ; + void set_wallclock_offset (uint64_t wclock, uint32_t rtp_ts); + int rtp_ready (void) { + return true; + }; + void recv_callback(struct rtp *session, rtp_event *e); + virtual void flush_rtp_packets(void); + int recv_task(int waiting); + uint32_t get_last_rtp_timestamp (void) {return m_rtptime_last; }; + void remove_packet_rtp_queue(rtp_packet *pak, int free); + void pause(void); + void set_sync(CPlayerSession *psptr); + + void syncronize(rtcp_sync_t *sync); + protected: + bool check_seq (uint16_t seq); + void set_last_seq(uint16_t seq); + void init(void); + // Make sure all classes call this to calculate real time. + uint64_t rtp_ts_to_msec(uint32_t rtp_ts, uint64_t uts, uint64_t &wrap_offset); + rtp_packet *m_head, *m_tail; + int m_offset_in_pak; + uint32_t m_skip_on_advance_bytes; + uint32_t m_ts; + uint64_t m_total; + bool m_base_ts_set; + uint32_t m_base_rtp_ts; + uint64_t m_base_ts; + bool m_rtp_base_seq_set; + uint16_t m_rtp_base_seq; + uint64_t m_timescale; + int m_stream_ondemand; + uint64_t m_wrap_offset; + bool m_rtcp_received; + uint64_t m_rtcp_ts; + uint32_t m_rtcp_rtp_ts; + uint64_t m_wallclock_offset_wrap; + void calculate_wallclock_offset_from_rtcp(uint32_t ntp_frac, + uint32_t ntp_sec, + uint32_t rtp_ts); + SDL_mutex *m_rtp_packet_mutex; + int m_buffering; + uint64_t m_rtp_buffer_time; + unsigned int m_rtp_pt; + virtual int check_rtp_frame_complete_for_payload_type(void); + virtual void rtp_done_buffering(void) {}; + uint32_t m_rtptime_last; + int m_recvd_pak; + int m_recvd_pak_timeout; + uint64_t m_recvd_pak_timeout_time; + uint64_t m_last_realtime; + format_list_t *m_fmt; + int m_eof; + int m_rtpinfo_set_from_pak; + uint16_t m_next_seq; + bool m_have_first_pak_ts; + uint64_t m_first_pak_ts; + uint32_t m_first_pak_rtp_ts; + CPlayerSession *m_psptr; + bool m_have_sync_info; + rtcp_sync_t m_sync_info; +}; + +class CRtpByteStream : public CRtpByteStreamBase +{ + public: + CRtpByteStream(const char *name, + format_list_t *fmt, + unsigned int rtp_pt, + int ondemand, + uint64_t tickpersec, + rtp_packet **head, + rtp_packet **tail, + int rtp_seq_set, + uint16_t rtp_base_seq, + int rtp_ts_set, + uint32_t rtp_base_ts, + int rtcp_received, + uint32_t ntp_frac, + uint32_t ntp_sec, + uint32_t rtp_ts); + ~CRtpByteStream(); + uint64_t start_next_frame(uint8_t **buffer, uint32_t *buflen, + void **userdata); + int skip_next_frame(uint64_t *ts, int *havesync, uint8_t **buffer, + uint32_t *buflen, void **userdata = NULL); + void used_bytes_for_frame(uint32_t bytes); + int have_no_data(void); + void flush_rtp_packets(void); + void reset(void); + protected: + uint8_t *m_buffer; + uint32_t m_buffer_len; + uint32_t m_buffer_len_max; + uint32_t m_bytes_used; +}; + +class CAudioRtpByteStream : public CRtpByteStream +{ + public: + CAudioRtpByteStream(unsigned int rtp_pt, + format_list_t *fmt, + int ondemand, + uint64_t tickpersec, + rtp_packet **head, + rtp_packet **tail, + int rtp_seq_set, + uint16_t rtp_base_seq, + int rtp_ts_set, + uint32_t rtp_base_ts, + int rtcp_received, + uint32_t ntp_frac, + uint32_t ntp_sec, + uint32_t rtp_ts); + ~CAudioRtpByteStream(); + int have_no_data(void); + int check_rtp_frame_complete_for_payload_type(void); + uint64_t start_next_frame(uint8_t **buffer, uint32_t *buflen, + void **userdata); + void reset(void); + private: + rtp_packet *m_working_pak; +}; +int add_rtp_packet_to_queue(rtp_packet *pak, + rtp_packet **head, + rtp_packet **tail, + const char *name); +#endif diff --git a/include/pdp_mp4videosource.h b/include/pdp_mp4videosource.h new file mode 100644 index 0000000..7433d33 --- /dev/null +++ b/include/pdp_mp4videosource.h @@ -0,0 +1,90 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is MPEG4IP. + * + * The Initial Developer of the Original Code is Cisco Systems Inc. + * Portions created by Cisco Systems Inc. are + * Copyright (C) Cisco Systems Inc. 2000, 2001. All Rights Reserved. + * + * Contributor(s): + * Dave Mackie dmackie@cisco.com + * Bill May wmay@cisco.com + * + * Adapted for PD/PDP by Yves Degoyon (ydegoyon@free.fr) + */ + +#ifndef __PDP_MP4VIDEOSOURCE__ +#define __PDP_MP4VIDEOSOURCE__ + +#include <sys/types.h> +#include <sys/ioctl.h> +#include <linux/videodev.h> + +#include "media_source.h" +#include "video_encoder.h" + +class CPDPVideoSource : public CMediaSource { +public: + CPDPVideoSource() : CMediaSource() { + m_videoMap = NULL; + m_videoFrameMap = NULL; + } + + bool IsDone() { + return false; + } + + float GetProgress() { + return 0.0; + } + + void ProcessVideo(u_int8_t *pY, u_int8_t *pU, u_int8_t *pV); + + void DoStart(void); + + void DoStop(void); + +protected: + int ThreadMain(void); + + bool Init(void); + + int8_t StartTimeStamp(Timestamp &frameTimestamp); + + bool EndTimeStamp(int8_t frameNumber); + + Timestamp CalculateVideoTimestampFromFrames (uint64_t frame) { + double duration = frame; + duration *= TimestampTicks; + duration /= m_videoSrcFrameRate; + return m_videoCaptureStartTimestamp + (Timestamp)duration; + } +protected: + u_int8_t m_maxPasses; + + struct video_mbuf m_videoMbuf; + void* m_videoMap; + struct video_mmap* m_videoFrameMap; + Timestamp m_videoCaptureStartTimestamp; + uint64_t m_videoFrames; + Duration m_videoSrcFrameDuration; + int8_t m_captureHead; + int8_t m_encodeHead; + float m_videoSrcFrameRate; + uint64_t *m_videoFrameMapFrame; + Timestamp *m_videoFrameMapTimestamp; + uint64_t m_lastVideoFrameMapFrameLoaded; + Timestamp m_lastVideoFrameMapTimestampLoaded; + bool m_cacheTimestamp; +}; + +#endif /* __PDP_MP4VIDEOSOURCE__ */ diff --git a/include/pdp_mp4videosync.h b/include/pdp_mp4videosync.h new file mode 100644 index 0000000..a1bac85 --- /dev/null +++ b/include/pdp_mp4videosync.h @@ -0,0 +1,90 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is MPEG4IP. + * + * The Initial Developer of the Original Code is Cisco Systems Inc. + * Portions created by Cisco Systems Inc. are + * Copyright (C) Cisco Systems Inc. 2000, 2001. All Rights Reserved. + * + * Contributor(s): + * Bill May wmay@cisco.com + * video aspect ratio by: + * Peter Maersk-Moller peter@maersk-moller.net + * + * Adapted for PD/PDP by Yves Degoyon (ydegoyon@free.fr) + */ + +/* + * video.h - contains the interface class between the codec and the video + * display hardware. + */ + +#ifndef __PDP_MP4VIDEOSYNC__ +#define __PDP_MP4VIDEOSYNC__ + +#include "video.h" +#include "pdp_mp4player~.h" + +class CPDPVideoSync : public CVideoSync { + public: + CPDPVideoSync(CPlayerSession *psptr, t_pdp_mp4player *pdp_father); + ~CPDPVideoSync(void); + int initialize_video(const char *name); // from sync task + int is_video_ready(uint64_t &disptime); // from sync task + int64_t play_video_at(uint64_t current_time, // from sync task + int &have_eof); + int get_video_buffer(uint8_t **y, + uint8_t **u, + uint8_t **v); + void filled_video_buffers(uint64_t time); + void set_video_frame(const uint8_t *y, // from codec + const uint8_t *u, + const uint8_t *v, + int m_pixelw_y, + int m_pixelw_uv, + uint64_t time); + void config (int w, int h); // from codec + void set_wait_sem (SDL_sem *p) { m_decode_sem = p; }; // from set up + void flush_decode_buffers(void); // from decoder task in response to stop + void flush_sync_buffers(void); // from sync task in response to stop + void play_video(void); + private: + int m_video_bpp; + int m_video_scale; + int m_fullscreen; + unsigned int m_width, m_height; + int m_video_initialized; + int m_config_set; + int m_paused; + int m_double_width; + volatile int m_have_data; + SDL_Surface *m_screen; + SDL_Overlay *m_image; + SDL_Rect m_dstrect; + uint32_t m_fill_index, m_play_index; + int m_decode_waiting; + volatile int m_buffer_filled[MAX_VIDEO_BUFFERS]; + uint8_t *m_y_buffer[MAX_VIDEO_BUFFERS]; + uint8_t *m_u_buffer[MAX_VIDEO_BUFFERS]; + uint8_t *m_v_buffer[MAX_VIDEO_BUFFERS]; + uint64_t m_play_this_at[MAX_VIDEO_BUFFERS]; + int m_dont_fill; + int m_pixel_width; + int m_pixel_height; + int m_max_width; + int m_max_height; + t_pdp_mp4player *m_father; +}; + +CPDPVideoSync *pdp_create_video_sync(CPlayerSession *psptr, t_pdp_mp4player *pdp_father); + +#endif diff --git a/modules/pdp_ieee1394.c b/modules/pdp_ieee1394.c new file mode 100644 index 0000000..189398f --- /dev/null +++ b/modules/pdp_ieee1394.c @@ -0,0 +1,483 @@ +/* + * PiDiP module. + * Copyright (c) by Yves Degoyon (ydegoyon@free.fr) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +/* This object is a ieee1394 video input object for OSX, using QuickTime + * Some code is inspired by pix_video from Gem + * Written by Yves Degoyon + */ + +#include "pdp_config.h" +#include "pdp.h" +#include "pdp_llconv.h" +#include "pdp_imageproc.h" +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <unistd.h> +#include <string.h> +#include <ctype.h> +#include <fcntl.h> +#include <errno.h> +#include <sys/ioctl.h> +#include <sys/types.h> +#include <sys/time.h> +#include <sys/mman.h> +#include <sched.h> +#include <pthread.h> +#include <Carbon/Carbon.h> +#include <Quicktime/QuickTime.h> +#include <Quicktime/QuickTimeComponents.h> + +#define DEFAULT_WIDTH 320 +#define DEFAULT_HEIGHT 240 + +typedef struct pdp_ieee1394_struct +{ + t_object x_obj; + t_float x_f; + + t_outlet *x_outlet0; + + bool x_initialized; + bool x_auto_open; + + t_int x_packet; + t_pdp* x_header; + short int *x_data; + unsigned char *x_sdata; // static data to hold the grabbed images + + unsigned int x_width; + unsigned int x_height; + unsigned int x_size; + int x_channel; + pthread_t x_thread_id; + int x_continue_thread; + unsigned int x_framerate; + int x_frame_ready; + t_int x_quality; + + SeqGrabComponent x_sg; + SGChannel x_vc; + short x_pixelDepth; + Rect x_srcRect; + GWorldPtr x_srcGWorld; + PixMapHandle x_pixMap; + Ptr x_baseAddr; + long x_rowBytes; + +} t_pdp_ieee1394; + +static void pdp_ieee1394_close(t_pdp_ieee1394 *x) +{ + void *dummy; + + /* terminate thread if there is one */ + if(x->x_continue_thread) + { + x->x_continue_thread = 0; + // pthread_join (x->x_thread_id, &dummy); + } + + // free sequence grabber + // if (x->x_vc) + // { + // if (SGDisposeChannel(x->x_sg, x->x_vc)) + // { + // post("pdp_ieee1394: unable to dispose video channel"); + // } + // x->x_vc = NULL; + // post("pdp_ieee1394: disposed video channel"); + // } + // if (x->x_sg) + // { + // if (CloseComponent(x->x_sg)) + // { + // post("pdp_ieee1394: unable to free sequence grabber."); + // } + // x->x_sg = NULL; + // post("pdp_ieee1394: freed sequence grabber."); + // } + // if (x->x_srcGWorld) + // { + // DisposeGWorld(x->x_srcGWorld); + // post("pdp_ieee1394: disposed world."); + // x->x_srcGWorld = NULL; + // } + +} + +static void pdp_ieee1394_capture_frame(t_pdp_ieee1394* x) +{ + OSErr err; + + err = SGIdle(x->x_sg); + if (err != noErr) + { + post("pdp_ieee1394: SGIdle failed."); + x->x_frame_ready = 0; + } + else + { + x->x_frame_ready = 1; + } +} + + +static void *pdp_ieee1394_thread(void *voidx) +{ + t_pdp_ieee1394 *x = ((t_pdp_ieee1394 *)voidx); + + /* capture with a double buffering scheme */ + while (true) + { + if (x->x_continue_thread) + { + /* schedule capture command for next frame */ + pdp_ieee1394_capture_frame(x); + } + else + { + sleep(1); + } + } + + x->x_thread_id = 0; + return 0; +} + +static void pdp_ieee1394_reset(t_pdp_ieee1394 *x) +{ + OSErr anErr; + + if ( !x->x_initialized ) + { + post("pdp_ieee1394: trying to reset but the sequence grabber is not initialized"); + return; + } + + post("pdp_ieee1394: resetting...."); + + switch (x->x_quality) + { + case 0: + anErr = SGSetChannelPlayFlags(x->x_vc, channelPlayNormal); + post("pdp_ieee1394: set sequence grabber to : normal quality"); + break; + case 1: + anErr = SGSetChannelPlayFlags(x->x_vc, channelPlayHighQuality); + post("pdp_ieee1394: set sequence grabber to : high quality"); + break; + case 2: + anErr = SGSetChannelPlayFlags(x->x_vc, channelPlayFast); + post("pdp_ieee1394: set sequence grabber to : fast quality"); + break; + case 3: + anErr = SGSetChannelPlayFlags(x->x_vc, channelPlayAllData); + post("pdp_ieee1394: set sequence grabber to : play all data"); + break; + } + + post("pdp_ieee1394: done."); +} + +static void pdp_ieee1394_quality(t_pdp_ieee1394 *x, t_floatarg fquality) +{ + if ( ( (t_int)fquality < 0 ) || ( (t_int)fquality > 3 ) ) + { + post("pdp_ieee1394: wrong quality %d", (t_int)fquality ); + return; + } + else + { + x->x_quality = (t_int)fquality; + } +} + +static void pdp_ieee1394_free(t_pdp_ieee1394 *x) +{ + pdp_ieee1394_close(x); +} + +static t_int pdp_ieee1394_init_grabber(t_pdp_ieee1394 *x) +{ + OSErr anErr; + x->x_srcRect.top = 0; + x->x_srcRect.left = 0; + x->x_srcRect.bottom = x->x_height; + x->x_srcRect.right = x->x_width; + + x->x_sg = OpenDefaultComponent(SeqGrabComponentType, 0); + if(x->x_sg==NULL) + { + post("pdp_ieee1394: could not open default component"); + return -1; + } + else + { + post("pdp_ieee1394: opened default component"); + } + + anErr = SGInitialize(x->x_sg); + if(anErr!=noErr) + { + post("pdp_ieee1394: could not initialize sequence grabber"); + return -1; + } + else + { + post("pdp_ieee1394: initialized sequence grabber"); + } + + anErr = SGSetDataRef(x->x_sg, 0, 0, seqGrabDontMakeMovie); + if (anErr != noErr) + { + post("pdp_ieee1394: couldn't set data ref"); + return -1; + } + else + { + post("pdp_ieee1394: set data ref ok."); + } + + anErr = SGNewChannel(x->x_sg, VideoMediaType, &x->x_vc); + if(anErr!=noErr) + { + post("pdp_ieee1394: could not create new sequence grabber channnel"); + return -1; + } + else + { + post("pdp_ieee1394: created new sequence grabber channnel."); + } + + anErr = SGSetChannelBounds(x->x_vc, &x->x_srcRect); + if(anErr!=noErr) + { + post("pdp_ieee1394: could not set sequence grabber ChannelBounds "); + return -1; + } + else + { + post("pdp_ieee1394: set sequence grabber ChannelBounds"); + } + + anErr = SGSetChannelUsage(x->x_vc, seqGrabPreview); + if(anErr!=noErr) + { + post("pdp_ieee1394: could not set sequence grabber ChannelUsage "); + return -1; + } + else + { + post("pdp_ieee1394: set sequence grabber ChannelUsage"); + } + + switch (x->x_quality) + { + case 0: + anErr = SGSetChannelPlayFlags(x->x_vc, channelPlayNormal); + post("pdp_ieee1394: set sequence grabber to : normal quality"); + break; + case 1: + anErr = SGSetChannelPlayFlags(x->x_vc, channelPlayHighQuality); + post("pdp_ieee1394: set sequence grabber to : high quality"); + break; + case 2: + anErr = SGSetChannelPlayFlags(x->x_vc, channelPlayFast); + post("pdp_ieee1394: set sequence grabber to : fast quality"); + break; + case 3: + anErr = SGSetChannelPlayFlags(x->x_vc, channelPlayAllData); + post("pdp_ieee1394: set sequence grabber to : play all data"); + break; + } + + anErr = QTNewGWorldFromPtr (&x->x_srcGWorld, + k422YpCbCr8CodecType, + &x->x_srcRect, + NULL, + NULL, + 0, + x->x_sdata, + x->x_width*2); + if (anErr!= noErr) + { + post ("pdp_ieee1394: QTNewGWorldFromPtr returned %d", anErr); + return -1; + } + + if (NULL == x->x_srcGWorld) + { + post ("pdp_ieee1394: could not allocate off screen"); + return -1; + } + SGSetGWorld(x->x_sg,(CGrafPtr)x->x_srcGWorld, NULL); + SGStartPreview(x->x_sg); + + return 0; +} + +static void pdp_ieee1394_open(t_pdp_ieee1394 *x) +{ + + x->x_initialized = true; + x->x_continue_thread = 1; + + /* create thread */ + if ( x->x_thread_id == 0 ) + { + if ( pdp_ieee1394_init_grabber( x ) != 0 ) + { + post("pdp_ieee1394: grabber initialization failed"); + return; + } + x->x_frame_ready = 0; + pthread_create(&x->x_thread_id, 0, pdp_ieee1394_thread, x); + } +} + +static void pdp_ieee1394_bang(t_pdp_ieee1394 *x) +{ + unsigned char *pQ; + short int *pY, *pU, *pV; + t_int px, py; + + if (!(x->x_continue_thread)) + { + post("pdp_ieee1394: not initialized."); + + if (x->x_auto_open) + { + post("pdp_ieee1394: attempting auto open"); + pdp_ieee1394_open(x); + if (!(x->x_initialized)) + { + post("pdp_ieee1394: auto open failed"); + return; + } + } + else return; + } + + /* do nothing if there is no frame ready */ + if (!x->x_frame_ready) return; + + x->x_packet = pdp_packet_new_image_YCrCb(x->x_width, x->x_height); + x->x_header = pdp_packet_header(x->x_packet); + + if (!x->x_header) + { + post("pdp_ieee1394: FATAL: can't allocate packet"); + return; + } + + x->x_data = (short int *) pdp_packet_data(x->x_packet); + memset( x->x_data, 0x0, (x->x_size+(x->x_size>>1))<<1 ); + pQ = x->x_sdata; + pY = x->x_data; + pV = x->x_data+x->x_size; + pU = x->x_data+x->x_size+(x->x_size>>2); + for ( py=0; py<(t_int)x->x_height; py++ ) + { + for ( px=0; px<(t_int)x->x_width; px++ ) + { + *(pY+py*x->x_width+px) = (*(pQ+1+2*(py*x->x_width+px)))<<7; + if ( px%2 == 0 ) + { + *(pU+((py>>1)*(x->x_width>>1)+(px>>1))) = (*(pQ+2*(py*x->x_width+px))-128)<<8; + } + if ( px%2 == 1 ) + { + *(pV+((py>>1)*(x->x_width>>1)+(px>>1))) = (*(pQ+2*(py*x->x_width+px))-128)<<8; + } + } + } + + pdp_packet_pass_if_valid(x->x_outlet0, &x->x_packet); + + x->x_frame_ready = 0; +} + +t_class *pdp_ieee1394_class; + +void *pdp_ieee1394_new(t_floatarg fwidth, t_floatarg fheight) +{ + t_pdp_ieee1394 *x = (t_pdp_ieee1394 *)pd_new(pdp_ieee1394_class); + + x->x_outlet0 = outlet_new(&x->x_obj, &s_anything); + x->x_initialized = false; + + x->x_auto_open = true; + + x->x_continue_thread = 0; + + if (fwidth > 0.) + { + x->x_width = (int)fwidth; + } + else + { + x->x_width = DEFAULT_WIDTH; + } + + if (fheight > 0.) + { + x->x_height = (int)fheight; + } + else + { + x->x_height = DEFAULT_WIDTH; + } + x->x_size = x->x_width*x->x_height; + x->x_sdata = (unsigned char*) getbytes( (x->x_size+(x->x_size>>1))<<1 ); + if ( !x->x_sdata ) + { + post ("pdp_ieee1394: FATAL : couldn't allocate static data."); + return NULL; + } + + x->x_quality = 1; + x->x_thread_id = 0; + + return (void *)x; +} + + +#ifdef __cplusplus +extern "C" +{ +#endif + + +void pdp_ieee1394_setup(void) +{ + pdp_ieee1394_class = class_new(gensym("pdp_ieee1394"), (t_newmethod)pdp_ieee1394_new, + (t_method)pdp_ieee1394_free, sizeof(t_pdp_ieee1394), 0, A_DEFFLOAT, A_DEFFLOAT, A_NULL); + + class_addmethod(pdp_ieee1394_class, (t_method)pdp_ieee1394_bang, gensym("bang"), A_NULL); + class_addmethod(pdp_ieee1394_class, (t_method)pdp_ieee1394_close, gensym("close"), A_NULL); + class_addmethod(pdp_ieee1394_class, (t_method)pdp_ieee1394_open, gensym("open"), A_NULL); + class_addmethod(pdp_ieee1394_class, (t_method)pdp_ieee1394_reset, gensym("reset"), A_NULL); + class_addmethod(pdp_ieee1394_class, (t_method)pdp_ieee1394_quality, gensym("quality"), A_DEFFLOAT, A_NULL); +} + +#ifdef __cplusplus +} +#endif diff --git a/modules/pdp_mp4audiosource.cpp b/modules/pdp_mp4audiosource.cpp new file mode 100644 index 0000000..7ae18f4 --- /dev/null +++ b/modules/pdp_mp4audiosource.cpp @@ -0,0 +1,204 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is MPEG4IP. + * + * The Initial Developer of the Original Code is Cisco Systems Inc. + * Portions created by Cisco Systems Inc. are + * Copyright (C) Cisco Systems Inc. 2000-2002. All Rights Reserved. + * + * Contributor(s): + * Dave Mackie dmackie@cisco.com + * Bill May wmay@cisco.com + * + * Adapted for PD/PDP by Yves Degoyon (ydegoyon@free.fr) + */ + +#include "m_pd.h" + +#ifndef debug_message +#define debug_message post +#endif +#include "pdp_mp4audiosource.h" + +//#define DEBUG_TIMESTAMPS 1 + +CPDPAudioSource::CPDPAudioSource(CLiveConfig *pConfig) : CMediaSource() +{ + SetConfig(pConfig); + + m_pcmFrameBuffer = NULL; + m_prevTimestamp = 0; + m_timestampOverflowArray = NULL; + m_timestampOverflowArrayIndex = 0; + m_audioOssMaxBufferSize = 0; +} + +int CPDPAudioSource::ThreadMain(void) +{ + // just a stub, we don't use the threaded mode + return 0; +} + +void CPDPAudioSource::DoStart() +{ + if (m_source) { + return; + } + + if (!Init()) { + return; + } + + m_source = true; +} + +void CPDPAudioSource::DoStop() +{ + if (!m_source) { + return; + } + + CMediaSource::DoStopAudio(); + + CHECK_AND_FREE(m_timestampOverflowArray); + free(m_pcmFrameBuffer); + m_pcmFrameBuffer = NULL; + + m_source = false; +} + +bool CPDPAudioSource::Init(void) +{ + bool rc = InitAudio(true); + if (!rc) { + return false; + } + + m_channelsConfigured = m_pConfig->GetIntegerValue(CONFIG_AUDIO_CHANNELS); + + debug_message("pdp_mp4live~ : init audio : (m_audioDstChannels=%d m_audioDstSampleRate=%d )", + m_audioDstChannels, m_audioDstSampleRate); + + rc = SetAudioSrc(PCMAUDIOFRAME, + m_channelsConfigured, + m_pConfig->GetIntegerValue(CONFIG_AUDIO_SAMPLE_RATE)); + + if (!rc) { + return false; + } + + debug_message("pdp_mp4live~ : set audio src : (m_audioDstSamplesPerFrame=%d m_audioSrcChannels=%d)", + m_audioDstSamplesPerFrame, m_audioSrcChannels); + + // for live capture we can match the source to the destination + m_audioSrcSamplesPerFrame = m_audioDstSamplesPerFrame; + m_pcmFrameSize = + m_audioSrcSamplesPerFrame * m_audioSrcChannels * sizeof(u_int16_t); + + if (m_audioOssMaxBufferSize > 0) { + size_t array_size; + m_audioOssMaxBufferFrames = m_audioOssMaxBufferSize / m_pcmFrameSize; + array_size = m_audioOssMaxBufferFrames * sizeof(*m_timestampOverflowArray); + m_timestampOverflowArray = (Timestamp *)malloc(array_size); + memset(m_timestampOverflowArray, 0, array_size); + } + + m_pcmFrameBuffer = (u_int8_t*)malloc(m_pcmFrameSize); + if (!m_pcmFrameBuffer) { + goto init_failure; + } + + // maximum number of passes in ProcessAudio, approx 1 sec. + m_maxPasses = m_audioSrcSampleRate / m_audioSrcSamplesPerFrame; + + debug_message("pdp_mp4live~ : audio source initialization done : ( frame size=%d )", m_pcmFrameSize ); + + return true; + + init_failure: + debug_message("pdp_mp4live~ : audio initialization failed"); + + free(m_pcmFrameBuffer); + m_pcmFrameBuffer = NULL; + + return false; +} + +void CPDPAudioSource::ProcessAudio(u_int8_t* pcmBuffer, u_int32_t pcmBufferSize) +{ + audio_buf_info info; + Timestamp currentTime = GetTimestamp(); + Timestamp timestamp; + + if ( pcmBufferSize > m_pcmFrameSize ) + { + debug_message( "pdp_mp4live~ : too many audio samples : %d should be %d", + pcmBufferSize, m_pcmFrameSize ); + memcpy( m_pcmFrameBuffer, pcmBuffer, m_pcmFrameSize ); + } + else if ( pcmBufferSize < m_pcmFrameSize ) + { + debug_message( "pdp_mp4live~ : too little audio samples : %d should be %d", + pcmBufferSize, m_pcmFrameSize ); + memcpy( m_pcmFrameBuffer, pcmBuffer, pcmBufferSize ); + } + else + { + memcpy( m_pcmFrameBuffer, pcmBuffer, pcmBufferSize ); + } + + if (info.bytes == m_audioOssMaxBufferSize) { + // means the audio buffer is full, and not capturing + // we want to make the timestamp based on the previous one + // When we hit this case, we start using the m_timestampOverflowArray + // This will give us a timestamp for when the array is full. + // + // In other words, if we have a full audio buffer (ie: it's not loading + // any more), we start storing the current timestamp into the array. + // This will let us "catch up", and have a somewhat accurate timestamp + // when we loop around + // + // wmay - I'm not convinced that this actually works - if the buffer + // cleans up, we'll ignore m_timestampOverflowArray + if (m_timestampOverflowArray != NULL && + m_timestampOverflowArray[m_timestampOverflowArrayIndex] != 0) { + timestamp = m_timestampOverflowArray[m_timestampOverflowArrayIndex]; + } else { + timestamp = m_prevTimestamp + SrcSamplesToTicks(m_audioSrcSamplesPerFrame); + } + + if (m_timestampOverflowArray != NULL) + m_timestampOverflowArray[m_timestampOverflowArrayIndex] = currentTime; + + debug_message("pdp_mp4live~ : audio buffer full !"); + + } else { + // buffer is not full - so, we make the timestamp based on the number + // of bytes in the buffer that we read. + timestamp = currentTime - SrcSamplesToTicks(SrcBytesToSamples(info.bytes)); + if (m_timestampOverflowArray != NULL) + m_timestampOverflowArray[m_timestampOverflowArrayIndex] = 0; + } + +#ifdef DEBUG_TIMESTAMPS + debug_message("pdp_mp4live~ : info.bytes=%d t=%llu timestamp=%llu delta=%llu", + info.bytes, currentTime, timestamp, timestamp - m_prevTimestamp); +#endif + + m_prevTimestamp = timestamp; + if (m_timestampOverflowArray != NULL) { + m_timestampOverflowArrayIndex = (m_timestampOverflowArrayIndex + 1) % + m_audioOssMaxBufferFrames; + } + + ProcessAudioFrame(m_pcmFrameBuffer, m_pcmFrameSize, timestamp, false); +} diff --git a/modules/pdp_mp4audiosync.cpp b/modules/pdp_mp4audiosync.cpp new file mode 100644 index 0000000..e364c0f --- /dev/null +++ b/modules/pdp_mp4audiosync.cpp @@ -0,0 +1,505 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is MPEG4IP. + * + * The Initial Developer of the Original Code is Cisco Systems Inc. + * Portions created by Cisco Systems Inc. are + * Copyright (C) Cisco Systems Inc. 2000, 2001. All Rights Reserved. + * + * Contributor(s): + * Bill May wmay@cisco.com + * + * Adapted to PD/PDP by Yves Degoyon (ydegoyon@free.fr) + */ + +/* + * audio.cpp provides an interface (CPDPAudioSync) between the codec and + * the SDL audio APIs. + */ +#include <stdlib.h> +#include <string.h> +#include "pdp_mp4playersession.h" +#include "pdp_mp4audiosync.h" +#include "player_util.h" +#include "our_config_file.h" +#include "m_pd.h" + +#define audio_message(loglevel, fmt...) message(loglevel, "audiosync", fmt) + +static void pdp_audio_callback (void *userdata, Uint8 *stream, int len) +{ + CPDPAudioSync *a = (CPDPAudioSync *)userdata; + a->audio_callback(stream, len); +} + +CPDPAudioSync::CPDPAudioSync (CPlayerSession *psptr, t_pdp_mp4player *pdp_father) : CAudioSync(psptr) +{ + m_fill_index = m_play_index = 0; + for (int ix = 0; ix < DECODE_BUFFERS_MAX; ix++) { + m_buffer_filled[ix] = 0; + m_sample_buffer[ix] = NULL; + } + m_buffer_size = 0; + m_config_set = 0; + m_audio_initialized = 0; + m_audio_paused = 1; + m_resync_required = 0; + m_dont_fill = 0; + m_consec_no_buffers = 0; + m_audio_waiting_buffer = 0; + m_skipped_buffers = 0; + m_didnt_fill_buffers = 0; + m_play_time = 0 ; + m_buffer_latency = 0; + m_first_time = 1; + m_first_filled = 1; + m_buffer_offset_on = 0; + m_buffer_ts = 0; + m_load_audio_do_next_resync = 0; + m_convert_buffer = NULL; + m_father = pdp_father; +} + +CPDPAudioSync::~CPDPAudioSync (void) +{ + for (int ix = 0; ix < DECODE_BUFFERS_MAX; ix++) { + if (m_sample_buffer[ix] != NULL) + free(m_sample_buffer[ix]); + m_sample_buffer[ix] = NULL; + } + CHECK_AND_FREE(m_convert_buffer); + audio_message(LOG_NOTICE, + "Audio sync skipped %u buffers", + m_skipped_buffers); + audio_message(LOG_NOTICE, "didn't fill %u buffers", m_didnt_fill_buffers); +} + +void CPDPAudioSync::set_config (int freq, + int channels, + int format, + uint32_t sample_size) +{ + if (m_config_set != 0) + return; + + if (format == AUDIO_U8 || format == AUDIO_S8) + m_bytes_per_sample = 1; + else + m_bytes_per_sample = 2; + + if (sample_size == 0) { + int temp; + temp = freq; + while ((temp & 0x1) == 0) temp >>= 1; + sample_size = temp; + while (sample_size < 1024) sample_size *= 2; + while (((sample_size * 1000) % freq) != 0) sample_size *= 2; + } + + m_buffer_size = channels * sample_size * m_bytes_per_sample; + + for (int ix = 0; ix < DECODE_BUFFERS_MAX; ix++) { + m_buffer_filled[ix] = 0; + m_sample_buffer[ix] = (uint8_t *)malloc(2 * m_buffer_size); + } + m_freq = freq; + m_channels = channels; + m_format = format; + if (m_format == AUDIO_U8) { + m_silence = 0x80; + } else { + m_silence = 0x00; + } + m_config_set = 1; + m_msec_per_frame = (sample_size * 1000) / m_freq; + audio_message(LOG_DEBUG, "buffer size %d msec per frame %d", m_buffer_size, m_msec_per_frame); +}; + +uint8_t *CPDPAudioSync::get_audio_buffer (void) +{ + int ret; + int locked = 0; + if (m_dont_fill == 1) { + return (NULL); + } + + if (m_audio_initialized != 0) { + locked = 1; + } + ret = m_buffer_filled[m_fill_index]; + if (ret == 1) { + m_audio_waiting_buffer = 1; + m_audio_waiting_buffer = 0; + if (m_dont_fill != 0) { + return (NULL); + } + locked = 0; + if (m_audio_initialized != 0) { + locked = 1; + } + ret = m_buffer_filled[m_fill_index]; + if (locked) + if (ret == 1) { + post("pdp_mp4audiosync : no buffer"); + return (NULL); + } + } + return (m_sample_buffer[m_fill_index]); +} + +void CPDPAudioSync::load_audio_buffer (uint8_t *from, + uint32_t bytes, + uint64_t ts, + int resync) +{ + uint8_t *to; + uint32_t copied; + copied = 0; + if (m_buffer_offset_on == 0) { + int64_t diff = ts - m_buffer_ts; + + if (m_buffer_ts != 0 && diff > 1) { + m_load_audio_do_next_resync = 1; + audio_message(LOG_DEBUG, "timeslot doesn't match - %llu %llu", + ts, m_buffer_ts); + } + m_buffer_ts = ts; + } else { + int64_t check; + check = ts - m_loaded_next_ts; + if (check > m_msec_per_frame) { + audio_message(LOG_DEBUG, "potential resync at ts "U64" should be ts "U64, + ts, m_loaded_next_ts); + uint32_t left; + left = m_buffer_size - m_buffer_offset_on; + to = get_audio_buffer(); + memset(to + m_buffer_offset_on, 0, left); + filled_audio_buffer(m_buffer_ts, 0); + m_buffer_offset_on = 0; + m_load_audio_do_next_resync = 1; + m_buffer_ts = ts; + } + } + m_loaded_next_ts = bytes * M_64; + m_loaded_next_ts /= m_bytes_per_sample; + m_loaded_next_ts /= m_freq; + m_loaded_next_ts += ts; + + while ( bytes > 0) { + to = get_audio_buffer(); + if (to == NULL) { + return; + } + int copy; + uint32_t left; + + left = m_buffer_size - m_buffer_offset_on; + copy = MIN(left, bytes); + memcpy(to + m_buffer_offset_on, from, copy); + bytes -= copy; + copied += copy; + from += copy; + m_buffer_offset_on += copy; + if (m_buffer_offset_on >= m_buffer_size) { + m_buffer_offset_on = 0; + filled_audio_buffer(m_buffer_ts, resync | m_load_audio_do_next_resync); + m_buffer_ts += m_msec_per_frame; + resync = 0; + m_load_audio_do_next_resync = 0; + } + } + return; +} + +void CPDPAudioSync::filled_audio_buffer (uint64_t ts, int resync) +{ + uint32_t fill_index; + int locked; + // m_dont_fill will be set when we have a pause + if (m_dont_fill == 1) { + return; + } + // resync = 0; + fill_index = m_fill_index; + m_fill_index++; + m_fill_index %= DECODE_BUFFERS_MAX; + + locked = 0; + if (m_audio_initialized != 0) { + locked = 1; + } + if (m_first_filled != 0) { + m_first_filled = 0; + resync = 0; + m_resync_required = 0; + } else { + int64_t diff; + diff = ts - m_last_fill_timestamp; + if (diff - m_msec_per_frame > m_msec_per_frame) { + // have a hole here - don't want to resync + if (diff > ((m_msec_per_frame + 1) * 4)) { + resync = 1; + } else { + // try to fill the holes + m_last_fill_timestamp += m_msec_per_frame + 1; // fill plus extra + int64_t ts_diff; + do { + uint8_t *retbuffer; + // Get and swap buffers. + retbuffer = get_audio_buffer(); + if (retbuffer == NULL) { + return; + } + if (retbuffer != m_sample_buffer[m_fill_index]) { + audio_message(LOG_ERR, "retbuffer not fill index in audio sync"); + return; + } + locked = 0; + if (m_audio_initialized != 0) { + locked = 1; + } + m_sample_buffer[m_fill_index] = m_sample_buffer[fill_index]; + m_sample_buffer[fill_index] = retbuffer; + memset(retbuffer, m_silence, m_buffer_size); + m_buffer_time[fill_index] = m_last_fill_timestamp; + m_buffer_filled[fill_index] = 1; + m_samples_loaded += m_buffer_size; + fill_index++; + fill_index %= DECODE_BUFFERS_MAX; + m_fill_index++; + m_fill_index %= DECODE_BUFFERS_MAX; + audio_message(LOG_NOTICE, "Filling timestamp %llu with silence", + m_last_fill_timestamp); + m_last_fill_timestamp += m_msec_per_frame + 1; // fill plus extra + ts_diff = ts - m_last_fill_timestamp; + audio_message(LOG_DEBUG, "diff is %lld", ts_diff); + } while (ts_diff > 0); + locked = 0; + if (m_audio_initialized != 0) { + locked = 1; + } + } + } else { + if (m_last_fill_timestamp == ts) { + audio_message(LOG_NOTICE, "Repeat timestamp with audio %llu", ts); + return; + } + } + } + m_last_fill_timestamp = ts; + m_buffer_filled[fill_index] = 1; + m_samples_loaded += m_buffer_size; + m_buffer_time[fill_index] = ts; + if (resync) { + m_resync_required = 1; + m_resync_buffer = fill_index; +#ifdef DEBUG_AUDIO_FILL + audio_message(LOG_DEBUG, "Resync from filled_audio_buffer"); +#endif + } + + // Check this - we might not want to do this unless we're resyncing + if (resync) m_psptr->wake_sync_thread(); +#ifdef DEBUG_AUDIO_FILL + audio_message(LOG_DEBUG, "Filling " LLU " %u %u", ts, fill_index, m_samples_loaded); +#endif +} + +void CPDPAudioSync::set_eof(void) +{ + uint8_t *to; + if (m_buffer_offset_on != 0) { + to = get_audio_buffer(); + if (to != NULL) { + uint32_t left; + left = m_buffer_size - m_buffer_offset_on; + memset(to + m_buffer_offset_on, 0, left); + m_buffer_offset_on = 0; + filled_audio_buffer(m_buffer_ts, 0); + m_buffer_ts += m_msec_per_frame; + } + } + CAudioSync::set_eof(); +} + +int CPDPAudioSync::initialize_audio (int have_video) +{ + return (1); +} + +int CPDPAudioSync::is_audio_ready (uint64_t &disptime) +{ + disptime = m_buffer_time[m_play_index]; + return (m_dont_fill == 0 && m_buffer_filled[m_play_index] == 1); +} + +uint64_t CPDPAudioSync::check_audio_sync (uint64_t current_time, int &have_eof) +{ + return (0); +} + +void CPDPAudioSync::audio_callback (Uint8 *stream, int ilen) +{ + int freed_buffer = 0; + uint32_t bufferBytes = (uint32_t)ilen; + uint64_t this_time; + int delay = 0; + int playtime; + +} + +void CPDPAudioSync::play_audio (void) +{ + m_first_time = 1; + m_audio_paused = 0; + m_play_sample_index = 0; +} + +void CPDPAudioSync::flush_sync_buffers (void) +{ + clear_eof(); + m_dont_fill = 1; + if (m_audio_waiting_buffer) { + m_audio_waiting_buffer = 0; + } +} + +void CPDPAudioSync::flush_decode_buffers (void) +{ + int locked = 0; + if (m_audio_initialized != 0) { + locked = 1; + } + m_dont_fill = 0; + m_first_filled = 1; + for (int ix = 0; ix < DECODE_BUFFERS_MAX; ix++) { + m_buffer_filled[ix] = 0; + } + m_buffer_offset_on = 0; + m_play_index = m_fill_index = 0; + m_audio_paused = 1; + m_resync_buffer = 0; + m_samples_loaded = 0; +} + +void CPDPAudioSync::set_volume (int volume) +{ + m_volume = (volume * SDL_MIX_MAXVOLUME)/100; +} + +void CPDPAudioSync::audio_convert_data (void *from, uint32_t samples) +{ + if (m_obtained.format == AUDIO_U8 || m_obtained.format == AUDIO_S8) { + // bytewise - easy + int8_t *src, *dst; + src = (int8_t *) from; + dst = (int8_t *) m_convert_buffer; + if (m_channels == 2) { + // we got 1, wanted 2 + for (uint32_t ix = 0; ix < samples; ix++) { + int16_t sum = *src++; + sum += *src++; + sum /= 2; + if (sum < -128) sum = -128; + else if (sum > 128) sum = 128; + *dst++ = sum & 0xff; + } + } else { + // we got 2, wanted 1 + for (uint32_t ix = 0; ix < samples; ix++) { + *dst++ = *src; + *dst++ = *src++; + } + } + } else { + int16_t *src, *dst; + src = (int16_t *) from; + dst = (int16_t *) m_convert_buffer; + samples /= 2; + if (m_channels == 1) { + // 1 channel to 2 + for (uint32_t ix = 0; ix < samples; ix++) { + *dst++ = *src; + *dst++ = *src; + src++; + } + } else { + // 2 channels to 1 + for (uint32_t ix = 0; ix < samples; ix++) { + int32_t sum = *src++; + sum += *src++; + sum /= 2; + if (sum < -32768) sum = -32768; + else if (sum > 32767) sum = 32767; + *dst++ = sum & 0xffff; + } + } + + } +} + +static void pdp_audio_config (void *ifptr, int freq, + int chans, int format, uint32_t max_buffer_size) +{ + ((CPDPAudioSync *)ifptr)->set_config(freq, + chans, + format, + max_buffer_size); +} + +static uint8_t *pdp_get_audio_buffer (void *ifptr) +{ + return ((CPDPAudioSync *)ifptr)->get_audio_buffer(); +} + +static void pdp_filled_audio_buffer (void *ifptr, + uint64_t ts, + int resync_req) +{ + ((CPDPAudioSync *)ifptr)->filled_audio_buffer(ts, + resync_req); +} + +static void pdp_load_audio_buffer (void *ifptr, + uint8_t *from, + uint32_t bytes, + uint64_t ts, + int resync) +{ + ((CPDPAudioSync *)ifptr)->load_audio_buffer(from, + bytes, + ts, + resync); +} + +audio_vft_t audio_vft = { + message, + pdp_audio_config, + pdp_get_audio_buffer, + pdp_filled_audio_buffer, + pdp_load_audio_buffer +}; + +audio_vft_t *get_audio_vft (void) +{ + return &audio_vft; +} + +CPDPAudioSync *pdp_create_audio_sync (CPlayerSession *psptr, t_pdp_mp4player *pdp_father) +{ + return new CPDPAudioSync(psptr, pdp_father); +} + +int do_we_have_audio (void) +{ + return 1; +} diff --git a/modules/pdp_mp4config.cpp b/modules/pdp_mp4config.cpp new file mode 100644 index 0000000..3fbcb9f --- /dev/null +++ b/modules/pdp_mp4config.cpp @@ -0,0 +1,136 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is MPEG4IP. + * + * The Initial Developer of the Original Code is Cisco Systems Inc. + * Portions created by Cisco Systems Inc. are + * Copyright (C) Cisco Systems Inc. 2000, 2001. All Rights Reserved. + * + * Contributor(s): + * Dave Mackie dmackie@cisco.com + * Bill May wmay@cisco.com + * + * Adapted for PD/PDP by Yves Degoyon (ydegoyon@free.fr) + */ + +#include "pdp_mp4config.h" + +CLiveConfig::CLiveConfig( + SConfigVariable* variables, + config_index_t numVariables, + const char* defaultFileName) +: CConfigSet(variables, numVariables, defaultFileName) +{ + m_appAutomatic = false; + m_videoEncode = true; + m_videoMaxWidth = 768; + m_videoMaxHeight = 576; + m_videoNeedRgbToYuv = false; + m_videoMpeg4ConfigLength = 0; + m_videoMpeg4Config = NULL; + m_videoMaxVopSize = 128 * 1024; + m_audioEncode = true; +} + +CLiveConfig::~CLiveConfig() +{ + CHECK_AND_FREE(m_videoMpeg4Config); +} + +// recalculate derived values +void CLiveConfig::Update() +{ + UpdateVideo(); + UpdateAudio(); +} + +void CLiveConfig::UpdateVideo() +{ + m_videoEncode = true; + + CalculateVideoFrameSize(); + + GenerateMpeg4VideoConfig(this); +} + +void CLiveConfig::UpdateFileHistory(const char* fileName) +{ +} + +void CLiveConfig::CalculateVideoFrameSize() +{ + u_int16_t frameHeight; + float aspectRatio = GetFloatValue(CONFIG_VIDEO_ASPECT_RATIO); + + // crop video to appropriate aspect ratio modulo 16 pixels + if ((aspectRatio - VIDEO_STD_ASPECT_RATIO) < 0.1) { + frameHeight = GetIntegerValue(CONFIG_VIDEO_RAW_HEIGHT); + } else { + frameHeight = (u_int16_t)( + (float)GetIntegerValue(CONFIG_VIDEO_RAW_WIDTH) + / aspectRatio); + + if ((frameHeight % 16) != 0) { + frameHeight += 16 - (frameHeight % 16); + } + + if (frameHeight > GetIntegerValue(CONFIG_VIDEO_RAW_HEIGHT)) { + // OPTION might be better to insert black lines + // to pad image but for now we crop down + frameHeight = GetIntegerValue(CONFIG_VIDEO_RAW_HEIGHT); + if ((frameHeight % 16) != 0) { + frameHeight -= (frameHeight % 16); + } + } + } + + m_videoWidth = GetIntegerValue(CONFIG_VIDEO_RAW_WIDTH); + m_videoHeight = frameHeight; + + m_ySize = m_videoWidth * m_videoHeight; + m_uvSize = m_ySize / 4; + m_yuvSize = (m_ySize * 3) / 2; +} + +void CLiveConfig::UpdateAudio() +{ + m_audioEncode = true; +} + +void CLiveConfig::UpdateRecord() +{ +} + +bool CLiveConfig::IsOneSource() +{ + return true; +} + +bool CLiveConfig::IsCaptureVideoSource() +{ + return false; +} + +bool CLiveConfig::IsCaptureAudioSource() +{ + return false; +} + +bool CLiveConfig::IsFileVideoSource() +{ + return false; +} + +bool CLiveConfig::IsFileAudioSource() +{ + return false; +} diff --git a/modules/pdp_mp4live~.cpp b/modules/pdp_mp4live~.cpp new file mode 100644 index 0000000..dfa55a5 --- /dev/null +++ b/modules/pdp_mp4live~.cpp @@ -0,0 +1,638 @@ +/* + * PiDiP module. + * Copyright (c) by Yves Degoyon (ydegoyon@free.fr ) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +/* This object is a mpeg4ip streaming object towards a Darwin or Quicktime streaming server + * A lot of this object code is inspired by the code from mpeg4ip + * Copyright (c) 2000, 2001, 2002 Dave Mackie, Bill May & others + * The rest is written by Yves Degoyon ( ydegoyon@free.fr ) + */ + + +#include "pdp.h" +#include <math.h> +#include <time.h> +#include <sys/time.h> + +/* mpeg4ip includes taken from the source tree ( not exported ) */ +#include <mp4.h> +#define DECLARE_CONFIG_VARIABLES +#include "config_set.h" + +#undef CONFIG_BOOL +#define CONFIG_BOOL(var, name, defval) \ + { &(var), (name), CONFIG_TYPE_BOOL, (defval), (defval) } +#undef CONFIG_FLOAT +#define CONFIG_FLOAT(var, name, defval) \ + { &(var), (name), CONFIG_TYPE_FLOAT,(float) (defval), (float) (defval) } +#undef CONFIG_INT +#define CONFIG_INT(var, name, defval) \ + { &(var), (name), CONFIG_TYPE_INTEGER,(config_integer_t) (defval), (config_integer_t)(defval) } +#undef CONFIG_STRING +#define CONFIG_STRING(var, name, defval) \ + { &(var), (name), CONFIG_TYPE_STRING, (defval), (defval) } + +#include "pdp_mp4config.h" + +#undef DECLARE_CONFIG_VARIABLES +#ifndef debug_message +#define debug_message post +#endif +#include "rtp_transmitter.h" +#include "pdp_mp4videosource.h" +#include "pdp_mp4audiosource.h" + +#define VIDEO_BUFFER_SIZE (1024*1024) +#define MAX_AUDIO_PACKET_SIZE (128 * 1024) +#define AUDIO_PACKET_SIZE (2*1024) /* using aac encoding */ + +static char *pdp_mp4live_version = "pdp_mp4live~: version 0.1, an mpeg4ip video streaming object ( ydegoyon@free.fr )"; + +typedef struct pdp_mp4live_struct +{ + t_object x_obj; + t_float x_f; + + t_int x_packet0; + t_int x_dropped; + t_int x_queue_id; + + t_int x_vwidth; + t_int x_vheight; + t_int x_vsize; + + t_outlet *x_outlet_streaming; // indicates the status of streaming + t_outlet *x_outlet_nbframes; // number of frames emitted + t_outlet *x_outlet_framerate; // current frame rate + + t_int x_streaming; // streaming flag + t_int x_nbframes; // number of frames emitted + t_int x_framerate; // framerate + + t_int x_cursec; // current second + t_int x_secondcount; // number of frames emitted in the current second + + /* audio structures */ + short x_audio_buf[2*MAX_AUDIO_PACKET_SIZE]; /* buffer for incoming audio */ + short x_audio_enc_buf[2*MAX_AUDIO_PACKET_SIZE]; /* buffer for audio to be encoded */ + uint8_t x_audio_out[4*MAX_AUDIO_PACKET_SIZE]; /* buffer for encoded audio */ + t_int x_audioin_position; // writing position for incoming audio + t_int x_audio_per_frame; // number of audio samples to transmit for each frame + + /* mpeg4ip data */ + CLiveConfig *x_mp4Config; + CRtpTransmitter *x_rtpTransmitter; + CPDPVideoSource *x_videosource; + CPDPAudioSource *x_audiosource; + +} t_pdp_mp4live; + +#ifdef __cplusplus +extern "C" +{ +#endif + +static void pdp_mp4live_add_sink(t_pdp_mp4live *x, CMediaSink* pSink) +{ + if (x->x_videosource) + { + x->x_videosource->AddSink(pSink); + } + if (x->x_audiosource) + { + x->x_audiosource->AddSink(pSink); + } +} + +static void pdp_mp4live_remove_sink(t_pdp_mp4live *x, CMediaSink* pSink) +{ + if (x->x_videosource) + { + x->x_videosource->RemoveSink(pSink); + } + if (x->x_audiosource) + { + x->x_audiosource->RemoveSink(pSink); + } +} + +static void pdp_mp4live_disconnect(t_pdp_mp4live *x) +{ + t_int ret, i; + + if (!x->x_streaming) + { + post("pdp_mp4live~ : disconnect request but not connected ... ignored" ); + return; + } + + if (x->x_audiosource) + { + x->x_audiosource->DoStop(); + delete x->x_audiosource; + x->x_audiosource = NULL; + } + + if (x->x_videosource) + { + x->x_videosource->DoStop(); + delete x->x_videosource; + x->x_videosource = NULL; + } + + if (x->x_rtpTransmitter) + { + pdp_mp4live_remove_sink(x, x->x_rtpTransmitter); + x->x_rtpTransmitter->StopThread(); + delete x->x_rtpTransmitter; + x->x_rtpTransmitter = NULL; + } + + x->x_streaming = 0; + outlet_float( x->x_outlet_streaming, x->x_streaming ); + x->x_nbframes = 0; + outlet_float( x->x_outlet_nbframes, x->x_nbframes ); + x->x_framerate = 0; + outlet_float( x->x_outlet_framerate, x->x_framerate ); +} + +static void pdp_mp4live_connect(t_pdp_mp4live *x ) +{ + t_int ret, i; + + if (x->x_streaming) + { + post("pdp_mp4live~ : connect request but already connected ... ignored" ); + return; + } + + post("pdp_mp4live~ : creating video source"); + if ( x->x_videosource == NULL ) + { + x->x_videosource = new CPDPVideoSource(); + x->x_videosource->SetConfig(x->x_mp4Config); + } + + post("pdp_mp4live~ : creating audio source"); + if ( x->x_audiosource == NULL ) + { + x->x_audiosource = new CPDPAudioSource(x->x_mp4Config); + } + + post("pdp_mp4live~ : creating rtp transmitter"); + x->x_rtpTransmitter = new CRtpTransmitter(x->x_mp4Config); + x->x_rtpTransmitter->StartThread(); + + post("pdp_mp4live~ : creating audio destination"); + x->x_rtpTransmitter->CreateAudioRtpDestination(0, + x->x_mp4Config->GetStringValue(CONFIG_RTP_AUDIO_DEST_ADDRESS), + x->x_mp4Config->GetIntegerValue(CONFIG_RTP_AUDIO_DEST_PORT), + 0); + + post("pdp_mp4live~ : creating video destination"); + x->x_rtpTransmitter->CreateVideoRtpDestination(0, + x->x_mp4Config->GetStringValue(CONFIG_RTP_DEST_ADDRESS), + x->x_mp4Config->GetIntegerValue(CONFIG_RTP_VIDEO_DEST_PORT), + 0); + + post("pdp_mp4live~ : starting rtp"); + if ( x->x_rtpTransmitter ) + { + pdp_mp4live_add_sink(x, x->x_rtpTransmitter); + x->x_rtpTransmitter->Start(); + } + + if (x->x_videosource) + { + post("pdp_mp4live~ : starting video source"); + x->x_videosource->DoStart(); + post("pdp_mp4live~ : generating key frame"); + x->x_videosource->GenerateKeyFrame(); + } + + if (x->x_audiosource) + { + post("pdp_mp4live~ : starting audio source"); + x->x_audiosource->DoStart(); + } + + x->x_streaming = 1; + outlet_float( x->x_outlet_streaming, x->x_streaming ); + x->x_nbframes = 0; + outlet_float( x->x_outlet_nbframes, x->x_nbframes ); + x->x_framerate = 0; + outlet_float( x->x_outlet_framerate, x->x_framerate ); + +} + +static void pdp_mp4live_ipaddr(t_pdp_mp4live *x, t_symbol *sIpAddr ) +{ + t_int a, b, c, d; + + if ( !strcmp( sIpAddr->s_name, "" ) ) + { + post("pdp_mp4live~ : wrong ip address" ); + return; + } + + if ( sscanf( sIpAddr->s_name, "%d.%d.%d.%d", &a, &b, &c, &d ) < 4 ) + { + post("pdp_mp4live~ : wrong ip address : %s", sIpAddr->s_name ); + return; + } + + post( "pdp_mp4live~ : setting ip address: %s", sIpAddr->s_name ); + x->x_mp4Config->SetStringValue( CONFIG_RTP_DEST_ADDRESS, sIpAddr->s_name ); + x->x_mp4Config->SetStringValue( CONFIG_RTP_AUDIO_DEST_ADDRESS, sIpAddr->s_name ); +} + +static void pdp_mp4live_aport(t_pdp_mp4live *x, t_floatarg fAudioPort ) +{ + if ( ( (t_int) fAudioPort <= 0 ) || ( (t_int) fAudioPort > 65535 ) ) + { + post("pdp_mp4live~ : wrong audio port : %d", fAudioPort ); + return; + } + + post( "pdp_mp4live~ : setting audio port: %d", (t_int) fAudioPort ); + x->x_mp4Config->SetIntegerValue( CONFIG_RTP_AUDIO_DEST_PORT, (t_int) fAudioPort ); + +} + +static void pdp_mp4live_vport(t_pdp_mp4live *x, t_floatarg fVideoPort ) +{ + if ( ( (t_int) fVideoPort <= 0 ) || ( (t_int) fVideoPort > 65535 ) ) + { + post("pdp_mp4live~ : wrong video port : %d", fVideoPort ); + return; + } + + post( "pdp_mp4live~ : setting video port: %d", (t_int) fVideoPort ); + x->x_mp4Config->SetIntegerValue( CONFIG_RTP_VIDEO_DEST_PORT, (t_int) fVideoPort ); + +} + +static void pdp_mp4live_ttl(t_pdp_mp4live *x, t_floatarg fTtl ) +{ + if ( ( (t_int) fTtl <= 0 ) || ( (t_int) fTtl > 255 ) ) + { + post("pdp_mp4live~ : wrong ttl : %d", fTtl ); + return; + } + + post( "pdp_mp4live~ : setting ttl : %d", (t_int) fTtl ); + x->x_mp4Config->SetIntegerValue( CONFIG_RTP_MCAST_TTL, (t_int) fTtl ); + +} + +static void pdp_mp4live_vwidth(t_pdp_mp4live *x, t_floatarg fWidth ) +{ + if ( ( (t_int) fWidth <= 0 ) ) + { + post("pdp_mp4live~ : wrong width : %d", fWidth ); + return; + } + + post( "pdp_mp4live~ : setting width : %d", (t_int) fWidth ); + x->x_mp4Config->SetIntegerValue( CONFIG_VIDEO_RAW_WIDTH, (t_int) fWidth ); + +} + +static void pdp_mp4live_vheight(t_pdp_mp4live *x, t_floatarg fHeight ) +{ + if ( ( (t_int) fHeight <= 0 ) ) + { + post("pdp_mp4live~ : wrong height : %d", fHeight ); + return; + } + + post( "pdp_mp4live~ : setting height : %d", (t_int) fHeight ); + x->x_mp4Config->SetIntegerValue( CONFIG_VIDEO_RAW_HEIGHT, (t_int) fHeight ); + +} + +static void pdp_mp4live_framerate(t_pdp_mp4live *x, t_floatarg fFrameRate ) +{ + if ( ( (t_int) fFrameRate <= 0 ) ) + { + post("pdp_mp4live~ : wrong framerate : %d", fFrameRate ); + return; + } + + post( "pdp_mp4live~ : setting framerate : %d", (t_int) fFrameRate ); + x->x_mp4Config->SetFloatValue( CONFIG_VIDEO_FRAME_RATE, (t_float) fFrameRate ); + +} + +static void pdp_mp4live_vbitrate(t_pdp_mp4live *x, t_floatarg fVBitrate ) +{ + if ( ( (t_int) fVBitrate <= 0 ) ) + { + post("pdp_mp4live~ : wrong video bit rate : %d", fVBitrate ); + return; + } + + post( "pdp_mp4live~ : setting video bit rate : %d", (t_int) fVBitrate ); + x->x_mp4Config->SetIntegerValue( CONFIG_VIDEO_BIT_RATE, (t_int) fVBitrate ); + +} + +static void pdp_mp4live_samplerate(t_pdp_mp4live *x, t_floatarg fSampleRate ) +{ + if ( ( (t_int) fSampleRate != 44100 ) && + ( (t_int) fSampleRate != 22050 ) && + ( (t_int) fSampleRate != 11025 ) && + ( (t_int) fSampleRate != 8000 ) + ) + { + post("pdp_mp4live~ : wrong samplerate : %d", fSampleRate ); + return; + } + + post( "pdp_mp4live~ : setting samplerate : %d", (t_int) fSampleRate ); + x->x_mp4Config->SetIntegerValue( CONFIG_AUDIO_SAMPLE_RATE, (t_int) fSampleRate ); + +} + +static void pdp_mp4live_abitrate(t_pdp_mp4live *x, t_floatarg fABitrate ) +{ + if ( ( (t_int) fABitrate <= 0 ) ) + { + post("pdp_mp4live~ : wrong audio bit rate : %d", fABitrate ); + return; + } + + post( "pdp_mp4live~ : setting audio bit rate : %d", (t_int) fABitrate ); + x->x_mp4Config->SetIntegerValue( CONFIG_AUDIO_BIT_RATE_KBPS, (t_int) fABitrate ); + x->x_mp4Config->SetIntegerValue( CONFIG_AUDIO_BIT_RATE, ((t_int) fABitrate)*1000 ); + +} + +static void pdp_mp4live_sdp(t_pdp_mp4live *x, t_symbol *sSdpFile ) +{ + t_int ret; + + post( "pdp_mp4live~ : setting sdp filename : %s", (char *) sSdpFile->s_name ); + x->x_mp4Config->SetStringValue( CONFIG_SDP_FILE_NAME, (char *) sSdpFile->s_name ); + + post( "pdp_mp4live~ : writing sdp file : %s", (char *) sSdpFile->s_name ); + if ( ( ret = GenerateSdpFile( x->x_mp4Config ) ) ) + { + post( "pdp_mp4live~ : written sdp file : %s", (char *) sSdpFile->s_name ); + } + else + { + post( "pdp_mp4live~ : could not write sdp file : %s", + (char *) sSdpFile->s_name ); + } +} + +static void pdp_mp4live_process_yv12(t_pdp_mp4live *x) +{ + t_pdp *header = pdp_packet_header(x->x_packet0); + u_int8_t *data = (uint8_t *)pdp_packet_data(x->x_packet0); + u_int8_t *pY, *pU, *pV; + struct timeval etime; + + /* allocate all ressources */ + if ( ((int)header->info.image.width != x->x_vwidth) || + ((int)header->info.image.height != x->x_vheight) ) + { + x->x_vwidth = header->info.image.width; + x->x_vheight = header->info.image.height; + x->x_vsize = x->x_vwidth*x->x_vheight; + } + + if ( x->x_streaming ) + { + pY = data; + pU = data+x->x_vsize; + pV = data+x->x_vsize+(x->x_vsize>>2); + + x->x_videosource->ProcessVideo( pY, pV, pU ); + + /* update frames counter */ + + if ( gettimeofday(&etime, NULL) == -1) + { + post("pdp_ffmpeg~ : could not read time" ); + } + if ( etime.tv_sec != x->x_cursec ) + { + x->x_cursec = etime.tv_sec; + x->x_framerate = x->x_secondcount; + x->x_secondcount = 0; + } + x->x_nbframes++; + x->x_secondcount++; + + /* send an audio frame */ + if ( x->x_audioin_position > x->x_audio_per_frame ) + { + x->x_audiosource->ProcessAudio( (u_int8_t*)x->x_audio_buf, + (u_int32_t)x->x_audio_per_frame*sizeof(short) ); + + /* output resampled raw samples */ + memcpy( x->x_audio_buf, x->x_audio_buf+x->x_audio_per_frame, + x->x_audioin_position-x->x_audio_per_frame ); + x->x_audioin_position-=x->x_audio_per_frame; + } + } + return; +} + +static void pdp_mp4live_killpacket(t_pdp_mp4live *x) +{ + /* delete source packet */ + pdp_packet_mark_unused(x->x_packet0); + x->x_packet0 = -1; +} + + /* store audio data in PCM format and stream it */ +static t_int *pdp_mp4live_perform(t_int *w) +{ + t_float *in1 = (t_float *)(w[1]); // left audio inlet + t_float *in2 = (t_float *)(w[2]); // right audio inlet + t_pdp_mp4live *x = (t_pdp_mp4live *)(w[3]); + int n = (int)(w[4]); // number of samples + t_float fsample; + t_int isample, i; + + // just fills the buffer ( a pcm buffer ) + while (n--) + { + fsample=*(in1++); + if (fsample > 1.0) { fsample = 1.0; } + if (fsample < -1.0) { fsample = -1.0; } + isample=(short) (32767.0 * fsample); + *(x->x_audio_buf+x->x_audioin_position)=isample; + x->x_audioin_position=(x->x_audioin_position+1)%(2*MAX_AUDIO_PACKET_SIZE); + if ( x->x_audioin_position == 2*MAX_AUDIO_PACKET_SIZE-1 ) + { + // post( "pdp_mp4live~ : reaching end of audio buffer" ); + } + fsample=*(in2++); + if (fsample > 1.0) { fsample = 1.0; } + if (fsample < -1.0) { fsample = -1.0; } + isample=(short) (32767.0 * fsample); + *(x->x_audio_buf+x->x_audioin_position)=isample; + x->x_audioin_position=(x->x_audioin_position+1)%(2*MAX_AUDIO_PACKET_SIZE); + if ( x->x_audioin_position == 2*MAX_AUDIO_PACKET_SIZE-1 ) + { + // post( "pdp_mp4live~ : reaching end of audio buffer" ); + } + } + + return (w+5); +} + +static void pdp_mp4live_dsp(t_pdp_mp4live *x, t_signal **sp) +{ + dsp_add(pdp_mp4live_perform, 4, sp[0]->s_vec, sp[1]->s_vec, x, sp[0]->s_n); +} + +static void pdp_mp4live_process(t_pdp_mp4live *x) +{ + int encoding; + t_pdp *header = 0; + + /* check if image data packets are compatible */ + if ( (header = pdp_packet_header(x->x_packet0)) + && (PDP_BITMAP == header->type)){ + + /* pdp_mp4live_process inputs and write into active inlet */ + switch(pdp_packet_header(x->x_packet0)->info.image.encoding) + { + + case PDP_BITMAP_YV12: + pdp_queue_add(x, (void*) pdp_mp4live_process_yv12, (void*) pdp_mp4live_killpacket, &x->x_queue_id); + outlet_float( x->x_outlet_nbframes, x->x_nbframes ); + outlet_float( x->x_outlet_framerate, x->x_framerate ); + break; + + default: + /* don't know the type, so dont pdp_mp4live_process */ + post( "pdp_mp4live~ : hey!! i don't know about that type of image : %d", + pdp_packet_header(x->x_packet0)->info.image.encoding ); + break; + + } + } + +} + +static void pdp_mp4live_input_0(t_pdp_mp4live *x, t_symbol *s, t_floatarg f) +{ + + /* if this is a register_ro message or register_rw message, register with packet factory */ + + if (s== gensym("register_rw")) + { + x->x_dropped = pdp_packet_convert_ro_or_drop(&x->x_packet0, (int)f, pdp_gensym("bitmap/yv12/*") ); + } + + if ((s == gensym("process")) && (-1 != x->x_packet0) && (!x->x_dropped)) + { + /* add the process method and callback to the process queue */ + pdp_mp4live_process(x); + } + +} + +static void pdp_mp4live_free(t_pdp_mp4live *x) +{ + int i; + + pdp_queue_finish(x->x_queue_id); + pdp_packet_mark_unused(x->x_packet0); +} + +t_class *pdp_mp4live_class; + +void *pdp_mp4live_new(void) +{ + int i; + + t_pdp_mp4live *x = (t_pdp_mp4live *)pd_new(pdp_mp4live_class); + inlet_new (&x->x_obj, &x->x_obj.ob_pd, gensym ("signal"), gensym ("signal")); + + x->x_outlet_streaming = outlet_new(&x->x_obj, &s_float); + x->x_outlet_nbframes = outlet_new(&x->x_obj, &s_float); + x->x_outlet_framerate = outlet_new(&x->x_obj, &s_float); + + x->x_packet0 = -1; + x->x_queue_id = -1; + x->x_nbframes = 0; + x->x_framerate = 0; + x->x_secondcount = 0; + x->x_audioin_position = 0; + + x->x_mp4Config = new CLiveConfig(PdpConfigVariables, + sizeof(PdpConfigVariables) / sizeof(SConfigVariable), + "none"); + if ( x->x_mp4Config == NULL ) + { + post( "pdp_mp4live~ : couldn't allocate default config" ); + return NULL; + } + + x->x_mp4Config->InitializeIndexes(); + + x->x_mp4Config->Update(); + + // update sample rate with the actual sample rate + x->x_mp4Config->SetIntegerValue( CONFIG_AUDIO_SAMPLE_RATE, (t_int) sys_getsr() ); + + x->x_videosource = NULL; + x->x_audiosource = NULL; + + x->x_audio_per_frame = AUDIO_PACKET_SIZE; + + return (void *)x; +} + + +void pdp_mp4live_tilde_setup(void) +{ + // post( pdp_mp4live_version ); + pdp_mp4live_class = class_new(gensym("pdp_mp4live~"), (t_newmethod)pdp_mp4live_new, + (t_method)pdp_mp4live_free, sizeof(t_pdp_mp4live), 0, A_NULL); + + CLASS_MAINSIGNALIN(pdp_mp4live_class, t_pdp_mp4live, x_f ); + class_addmethod(pdp_mp4live_class, (t_method)pdp_mp4live_input_0, gensym("pdp"), A_SYMBOL, A_DEFFLOAT, A_NULL); + class_addmethod(pdp_mp4live_class, (t_method)pdp_mp4live_dsp, gensym("dsp"), A_NULL); + class_addmethod(pdp_mp4live_class, (t_method)pdp_mp4live_connect, gensym("connect"), A_NULL); + class_addmethod(pdp_mp4live_class, (t_method)pdp_mp4live_ipaddr, gensym("ipaddr"), A_SYMBOL, A_NULL); + class_addmethod(pdp_mp4live_class, (t_method)pdp_mp4live_aport, gensym("audioport"), A_DEFFLOAT, A_NULL); + class_addmethod(pdp_mp4live_class, (t_method)pdp_mp4live_vport, gensym("videoport"), A_DEFFLOAT, A_NULL); + class_addmethod(pdp_mp4live_class, (t_method)pdp_mp4live_ttl, gensym("ttl"), A_DEFFLOAT, A_NULL); + class_addmethod(pdp_mp4live_class, (t_method)pdp_mp4live_vwidth, gensym("vwidth"), A_DEFFLOAT, A_NULL); + class_addmethod(pdp_mp4live_class, (t_method)pdp_mp4live_vheight, gensym("vheight"), A_DEFFLOAT, A_NULL); + class_addmethod(pdp_mp4live_class, (t_method)pdp_mp4live_framerate, gensym("framerate"), A_DEFFLOAT, A_NULL); + class_addmethod(pdp_mp4live_class, (t_method)pdp_mp4live_vbitrate, gensym("vbitrate"), A_DEFFLOAT, A_NULL); + class_addmethod(pdp_mp4live_class, (t_method)pdp_mp4live_samplerate, gensym("samplerate"), A_DEFFLOAT, A_NULL); + class_addmethod(pdp_mp4live_class, (t_method)pdp_mp4live_abitrate, gensym("abitrate"), A_DEFFLOAT, A_NULL); + class_addmethod(pdp_mp4live_class, (t_method)pdp_mp4live_disconnect, gensym("disconnect"), A_NULL); + class_addmethod(pdp_mp4live_class, (t_method)pdp_mp4live_sdp, gensym("sdp"), A_SYMBOL, A_NULL); + class_sethelpsymbol( pdp_mp4live_class, gensym("pdp_mp4live~.pd") ); +} + +#ifdef __cplusplus +} +#endif diff --git a/modules/pdp_mp4playermedia.cpp b/modules/pdp_mp4playermedia.cpp new file mode 100644 index 0000000..75235e7 --- /dev/null +++ b/modules/pdp_mp4playermedia.cpp @@ -0,0 +1,2029 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is MPEG4IP. + * + * The Initial Developer of the Original Code is Cisco Systems Inc. + * Portions created by Cisco Systems Inc. are + * Copyright (C) Cisco Systems Inc. 2000, 2001. All Rights Reserved. + * + * Contributor(s): + * Bill May wmay@cisco.com + * + * Adapted to PD/PDP by Yves Degoyon (ydegoyon@free.fr) + */ + +/* + * player_media.cpp - handle generic information about a stream + */ +#include "mpeg4ip.h" +#include "pdp_mp4playersession.h" +#include "pdp_mp4playermedia.h" +#include "pdp_mp4videosync.h" +#include "pdp_mp4audiosync.h" +#include "player_sdp.h" +#include "player_util.h" +#include <rtp/memory.h> +#include "pdp_mp4rtpbytestream.h" +#include "our_config_file.h" +#include "media_utils.h" +#include "ip_port.h" +#include "codec_plugin.h" +#include "audio.h" +#include <time.h> +#include <rtp/memory.h> +#include "our_config_file.h" +#include "rtp_plugin.h" +#include "media_utils.h" +#include "rfc3119_bytestream.h" +#include "mpeg3_rtp_bytestream.h" +#include "codec/mp3/mp3_rtp_bytestream.h" +#include "rtp_bytestream_plugin.h" +#include "codec_plugin_private.h" + +static int pdp_recv_thread (void *data) +{ + CPlayerMedia *media; + + media = (CPlayerMedia *)data; + return (media->recv_thread()); +} + +static int pdp_decode_thread (void *data) +{ + CPlayerMedia *media; + media = (CPlayerMedia *)data; + return (media->decode_thread()); +} + +static void pdp_rtp_packet_callback (void *data, + unsigned char interleaved, + struct rtp_packet *pak, + int len) +{ + ((CPlayerMedia *)data)->rtp_receive_packet(interleaved, pak, len); +} + +static int pdp_init_rtp_tcp (void *data) +{ + ((CPlayerMedia *)data)->rtp_init_tcp(); + return 0; +} + +static int pdp_rtp_start (void *data) +{ + ((CPlayerMedia *)data)->rtp_start(); + return 0; +} + +static int pdp_rtp_periodic (void *data) +{ + ((CPlayerMedia *)data)->rtp_periodic(); + return 0; +} + +static void pdp_recv_callback (struct rtp *session, rtp_event *e) +{ + CPlayerMedia *m = (CPlayerMedia *)rtp_get_userdata(session); + m->recv_callback(session, e); +} + +static int pdp_rtcp_send_packet (void *ud, uint8_t *buffer, int buflen) +{ + return ((CPlayerMedia *)ud)->rtcp_send_packet(buffer, buflen); +} + +CPlayerMedia::CPlayerMedia (CPlayerSession *p) +{ + m_plugin = NULL; + m_plugin_data = NULL; + m_next = NULL; + m_parent = p; + m_media_info = NULL; + m_media_fmt = NULL; + m_our_port = 0; + m_ports = NULL; + m_server_port = 0; + m_source_addr = NULL; + m_recv_thread = NULL; + m_rtptime_tickpersec = 0; + m_rtsp_base_seq_received = 0; + m_rtsp_base_ts_received = 0; + + m_head = NULL; + m_rtp_queue_len = 0; + + m_rtp_ssrc_set = FALSE; + + m_rtsp_session = NULL; + m_decode_thread_waiting = 0; + m_sync_time_set = FALSE; + m_decode_thread = NULL; + m_decode_thread_sem = NULL; + m_video_sync = NULL; + m_audio_sync = NULL; + m_paused = 0; + m_byte_stream = NULL; + m_rtp_byte_stream = NULL; + m_video_info = NULL; + m_audio_info = NULL; + m_user_data = NULL; + m_rtcp_received = 0; + m_streaming = 0; + m_stream_ondemand = 0; + m_rtp_use_rtsp = 0; +} + +CPlayerMedia::~CPlayerMedia() +{ + rtsp_decode_t *rtsp_decode; + + media_message(LOG_DEBUG, "closing down media %d", m_is_video); + if (m_rtsp_session) { + // If control is aggregate, m_rtsp_session will be freed by + // CPDPPlayerSession + if (m_parent->session_control_is_aggregate() == 0) { + rtsp_send_teardown(m_rtsp_session, NULL, &rtsp_decode); + free_decode_response(rtsp_decode); + } + m_rtsp_session = NULL; + } + + if (m_recv_thread) { + m_rtp_msg_queue.send_message(MSG_STOP_THREAD); + SDL_WaitThread(m_recv_thread, NULL); + m_recv_thread = NULL; + } + + if (m_decode_thread) { + m_decode_msg_queue.send_message(MSG_STOP_THREAD, + NULL, + 0, + m_decode_thread_sem); + SDL_WaitThread(m_decode_thread, NULL); + m_decode_thread = NULL; + } + + + + if (m_source_addr != NULL) free(m_source_addr); + m_next = NULL; + m_parent = NULL; + + if (m_ports) { + delete m_ports; + m_ports = NULL; + } + if (m_rtp_byte_stream) { + double diff; + diff = difftime(time(NULL), m_start_time); + media_message(LOG_INFO, "Media %s", m_media_info->media); + + media_message(LOG_INFO, "Time: %g seconds", diff); +#if 0 + double div; + player_debug_message("Packets received: %u", m_rtp_packet_received); + player_debug_message("Payload received: "LLU" bytes", m_rtp_data_received); + div = m_rtp_packet_received / diff; + player_debug_message("Packets per sec : %g", div); + div = UINT64_TO_DOUBLE(m_rtp_data_received); + div *= 8.0; + div /= diff; + media_message(LOG_INFO, "Bits per sec : %g", div); +#endif + + } + if (m_byte_stream) { + delete m_byte_stream; + m_byte_stream = NULL; + m_rtp_byte_stream = NULL; + } + if (m_video_info) { + free(m_video_info); + m_video_info = NULL; + } + if (m_audio_info) { + free(m_audio_info); + m_audio_info = NULL; + } + if (m_user_data) { + free((void *)m_user_data); + m_user_data = NULL; + } + if (m_decode_thread_sem) { + SDL_DestroySemaphore(m_decode_thread_sem); + m_decode_thread_sem = NULL; + } +} + +void CPlayerMedia::clear_rtp_packets (void) +{ + if (m_head != NULL) { + m_tail->rtp_next = NULL; + while (m_head != NULL) { + rtp_packet *p; + p = m_head; + m_head = m_head->rtp_next; + p->rtp_next = p->rtp_prev = NULL; + xfree(p); + } + } + m_tail = NULL; + m_rtp_queue_len = 0; +} + +int CPlayerMedia::create_common (int is_video, char *errmsg, uint32_t errlen) +{ + m_parent->add_media(this); + m_is_video = is_video; + + m_decode_thread_sem = SDL_CreateSemaphore(0); + m_decode_thread = SDL_CreateThread(pdp_decode_thread, this); + if (m_decode_thread_sem == NULL || m_decode_thread == NULL) { + const char *outmedia; + if (m_media_info == NULL) { + outmedia = m_is_video ? "video" : "audio"; + } else outmedia = m_media_info->media; + + if (errmsg != NULL) + snprintf(errmsg, errlen, "Couldn't start media thread for %s", + outmedia); + media_message(LOG_ERR, "Failed to create decode thread for media %s", + outmedia); + return (-1); + } + return 0; +} +/* + * CPlayerMedia::create - create when we've already got a + * bytestream + */ +int CPlayerMedia::create (COurInByteStream *b, + int is_video, + char *errmsg, + uint32_t errlen, + int streaming) +{ + m_byte_stream = b; + m_streaming = streaming; + return create_common(is_video, errmsg, errlen); +} + +/* + * CPlayerMedia::create_streaming - create a streaming media session, + * including setting up rtsp session, rtp and rtp bytestream + */ +int CPlayerMedia::create_streaming (media_desc_t *sdp_media, + char *errmsg, + uint32_t errlen, + int ondemand, + int use_rtsp, + int media_number_in_session) +{ + char buffer[80]; + rtsp_command_t cmd; + rtsp_decode_t *decode; + + m_streaming = 1; + if (sdp_media == NULL) { + snprintf(errmsg, errlen, "Internal media error - sdp is NULL"); + return(-1); + } + + if (strncasecmp(sdp_media->proto, "RTP", strlen("RTP")) != 0) { + snprintf(errmsg, errlen, "Media %s doesn't use RTP", sdp_media->media); + media_message(LOG_ERR, "%s doesn't use RTP", sdp_media->media); + return (-1); + } + if (sdp_media->fmt == NULL) { + snprintf(errmsg, errlen, "Media %s doesn't have any usuable formats", + sdp_media->media); + media_message(LOG_ERR, "%s doesn't have any formats", + sdp_media->media); + return (-1); + } + + m_media_info = sdp_media; + m_stream_ondemand = ondemand; + if (ondemand != 0) { + /* + * Get 2 consecutive IP ports. If we don't have this, don't even + * bother + */ + if (use_rtsp == 0) { + m_ports = new C2ConsecIpPort(m_parent->get_unused_ip_port_ptr()); + if (m_ports == NULL || !m_ports->valid()) { + snprintf(errmsg, errlen, "Could not find any valid IP ports"); + media_message(LOG_ERR, "Couldn't get valid IP ports"); + return (-1); + } + m_our_port = m_ports->first_port(); + + /* + * Send RTSP setup message - first create the transport string for that + * message + */ + create_rtsp_transport_from_sdp(m_parent->get_sdp_info(), + m_media_info, + m_our_port, + buffer, + sizeof(buffer)); + } else { + m_rtp_use_rtsp = 1; + m_rtp_media_number_in_session = media_number_in_session; + snprintf(buffer, sizeof(buffer), "RTP/AVP/TCP;unicast;interleaved=%d-%d", + media_number_in_session * 2, (media_number_in_session * 2) + 1); + } + memset(&cmd, 0, sizeof(rtsp_command_t)); + cmd.transport = buffer; + int err = + rtsp_send_setup(m_parent->get_rtsp_client(), + m_media_info->control_string, + &cmd, + &m_rtsp_session, + &decode, + m_parent->session_control_is_aggregate()); + if (err != 0) { + snprintf(errmsg, errlen, "Couldn't set up session %s", + m_media_info->control_string); + media_message(LOG_ERR, "Can't create session %s - error code %d", + m_media_info->media, err); + if (decode != NULL) + free_decode_response(decode); + return (-1); + } + cmd.transport = NULL; + media_message(LOG_INFO, "Transport returned is %s", decode->transport); + + /* + * process the transport they sent. They need to send port numbers, + * addresses, rtptime information, that sort of thing + */ + if (m_source_addr == NULL) { + m_source_addr = rtsp_get_server_ip_address_string(m_rtsp_session); + media_message(LOG_INFO, "setting default source address from rtsp %s", m_source_addr); + } + + if (process_rtsp_transport(decode->transport) != 0) { + snprintf(errmsg, errlen, "Couldn't process transport information in RTSP response: %s", decode->transport); + free_decode_response(decode); + return(-1); + } + free_decode_response(decode); + } else { + m_server_port = m_our_port = m_media_info->port; + } + connect_desc_t *cptr; + cptr = get_connect_desc_from_media(m_media_info); + if (cptr == NULL) { + snprintf(errmsg, errlen, "Server did not return address"); + return (-1); + } + + // + // okay - here we want to check that the server port is set up, and + // go ahead and init rtp, and the associated task + // + m_start_time = time(NULL); + + if (create_common(strcmp(sdp_media->media, "video") == 0, + errmsg, errlen) < 0) { + return -1; + } + + if (ondemand == 0 || use_rtsp == 0) { + m_rtp_inited = 0; + m_recv_thread = SDL_CreateThread(pdp_recv_thread, this); + if (m_recv_thread == NULL) { + snprintf(errmsg, errlen, "Couldn't create media %s RTP recv thread", + m_media_info->media); + media_message(LOG_ERR, errmsg); + return (-1); + } + while (m_rtp_inited == 0) { + SDL_Delay(10); + } + if (m_rtp_session == NULL) { + snprintf(errmsg, errlen, "Could not start RTP - check debug log"); + media_message(LOG_ERR, errmsg); + return (-1); + } + } else { + int ret; + ret = rtsp_thread_set_rtp_callback(m_parent->get_rtsp_client(), + pdp_rtp_packet_callback, + pdp_rtp_periodic, + m_rtp_media_number_in_session, + this); + if (ret < 0) { + snprintf(errmsg, errlen, "Can't setup TCP/RTP callback"); + return -1; + } + ret = rtsp_thread_perform_callback(m_parent->get_rtsp_client(), + pdp_init_rtp_tcp, + this); + if (ret < 0) { + snprintf(errmsg, errlen, "Can't init RTP in RTSP thread"); + return -1; + } + } + if (m_rtp_session == NULL) { + snprintf(errmsg, errlen, "Couldn't create RTP session for media %s", + m_media_info->media); + media_message(LOG_ERR, errmsg); + return (-1); + } + return (0); +} + +int CPlayerMedia::create_video_plugin (const codec_plugin_t *p, + const char *compressor, + int type, + int profile, + format_list_t *sdp_media, + video_info_t *video, + const uint8_t *user_data, + uint32_t userdata_size) +{ + if (m_video_sync == NULL) { + m_video_sync = m_parent->set_up_video_sync(); + } + if (m_video_sync == NULL) return -1; + + m_plugin = p; + m_video_info = video; + m_plugin_data = (p->vc_create)(compressor, + type, + profile, sdp_media, + video, + user_data, + userdata_size, + get_video_vft(), + m_video_sync); + if (m_plugin_data == NULL) + return -1; + + if (user_data != NULL) + set_user_data(user_data, userdata_size); + return 0; +} + +void CPlayerMedia::set_plugin_data (const codec_plugin_t *p, + codec_data_t *d, + video_vft_t *v, + audio_vft_t *a) +{ + m_plugin = p; + m_plugin_data = d; + if (is_video()) { + if (m_video_sync == NULL) { + m_video_sync = m_parent->set_up_video_sync(); + } + d->ifptr = m_video_sync; + d->v.video_vft = v; + } else { + if (m_audio_sync == NULL) { + m_audio_sync = m_parent->set_up_audio_sync(); + } + d->ifptr = m_audio_sync; + d->v.audio_vft = a; + } + +} + +int CPlayerMedia::get_plugin_status (char *buffer, uint32_t buflen) +{ + if (m_plugin == NULL) return -1; + + if (m_plugin->c_print_status == NULL) return -1; + + return ((m_plugin->c_print_status)(m_plugin_data, buffer, buflen)); +} + +int CPlayerMedia::create_audio_plugin (const codec_plugin_t *p, + const char *compressor, + int type, + int profile, + format_list_t *sdp_media, + audio_info_t *audio, + const uint8_t *user_data, + uint32_t userdata_size) +{ + if (m_audio_sync == NULL) { + m_audio_sync = m_parent->set_up_audio_sync(); + } + if (m_audio_sync == NULL) return -1; + + m_audio_info = audio; + m_plugin = p; + m_plugin_data = (p->ac_create)(compressor, + type, + profile, + sdp_media, + audio, + user_data, + userdata_size, + get_audio_vft(), + m_audio_sync); + if (m_plugin_data == NULL) return -1; + + if (user_data != NULL) + set_user_data(user_data, userdata_size); + return 0; +} + +/* + * CPlayerMedia::do_play - get play command + */ +int CPlayerMedia::do_play (double start_time_offset, + char *errmsg, + uint32_t errlen) +{ + + if (m_streaming != 0) { + if (m_stream_ondemand != 0) { + /* + * We're streaming - send the RTSP play command + */ + if (m_parent->session_control_is_aggregate() == 0) { + char buffer[80]; + rtsp_command_t cmd; + rtsp_decode_t *decode; + range_desc_t *range; + memset(&cmd, 0, sizeof(rtsp_command_t)); + + // only do range if we're not paused + range = get_range_from_media(m_media_info); + if (range != NULL) { + if (start_time_offset < range->range_start || + start_time_offset > range->range_end) + start_time_offset = range->range_start; + // need to check for smpte + sprintf(buffer, "npt=%g-%g", start_time_offset, range->range_end); + cmd.range = buffer; + } + + if (rtsp_send_play(m_rtsp_session, &cmd, &decode) != 0) { + media_message(LOG_ERR, "RTSP play command failed"); + if (errmsg != NULL) { + snprintf(errmsg, errlen, "RTSP Play Error %s-%s", + decode->retcode, + decode->retresp != NULL ? decode->retresp : ""); + } + free_decode_response(decode); + return (-1); + } + + /* + * process the return information + */ + int ret = pdp_process_rtsp_rtpinfo(decode->rtp_info, m_parent, this); + if (ret < 0) { + media_message(LOG_ERR, "rtsp rtpinfo failed"); + free_decode_response(decode); + if (errmsg != NULL) { + snprintf(errmsg, errlen, "RTSP aggregate RtpInfo response failure"); + } + return (-1); + } + free_decode_response(decode); + } + if (m_source_addr == NULL) { + // get the ip address of the server from the rtsp stack + m_source_addr = rtsp_get_server_ip_address_string(m_rtsp_session); + media_message(LOG_INFO, "Setting source address from rtsp - %s", + m_source_addr); + } + // ASDF - probably need to do some stuff here for no rtpinfo... + /* + * set the various play times, and send a message to the recv task + * that it needs to start + */ + m_play_start_time = start_time_offset; + } + if (m_byte_stream != NULL) { + m_byte_stream->play((uint64_t)(start_time_offset * 1000.0)); + } + m_paused = 0; + if (m_rtp_use_rtsp) { + rtsp_thread_perform_callback(m_parent->get_rtsp_client(), + pdp_rtp_start, + this); + } + } else { + /* + * File (or other) playback. + */ + if (m_paused == 0 || start_time_offset == 0.0) { + m_byte_stream->reset(); + } + m_byte_stream->play((uint64_t)(start_time_offset * 1000.0)); + m_play_start_time = start_time_offset; + m_paused = 0; + start_decoding(); + } + return (0); +} + +/* + * CPlayerMedia::do_pause - stop what we're doing + */ +int CPlayerMedia::do_pause (void) +{ + + if (m_streaming != 0) { + if (m_stream_ondemand != 0) { + /* + * streaming - send RTSP pause + */ + if (m_parent->session_control_is_aggregate() == 0) { + rtsp_command_t cmd; + rtsp_decode_t *decode; + memset(&cmd, 0, sizeof(rtsp_command_t)); + + if (rtsp_send_pause(m_rtsp_session, &cmd, &decode) != 0) { + media_message(LOG_ERR, "RTSP play command failed"); + free_decode_response(decode); + return (-1); + } + free_decode_response(decode); + } + } + if (m_recv_thread != NULL) { + m_rtp_msg_queue.send_message(MSG_PAUSE_SESSION); + } + } + + if (m_byte_stream != NULL) + m_byte_stream->pause(); + /* + * Pause the various threads + */ + m_decode_msg_queue.send_message(MSG_PAUSE_SESSION, + NULL, + 0, + m_decode_thread_sem); + m_paused = 1; + return (0); +} + +double CPlayerMedia::get_max_playtime (void) +{ + if (m_byte_stream) { + return (m_byte_stream->get_max_playtime()); + } + return (0.0); +} + +/*************************************************************************** + * Transport and RTP-Info RTSP header line parsing. + ***************************************************************************/ +#define ADV_SPACE(a) {while (isspace(*(a)) && (*(a) != '\0'))(a)++;} + +#define TTYPE(a,b) {a, sizeof(a), b} + +static char *transport_parse_unicast (char *transport, CPlayerMedia *m) +{ + ADV_SPACE(transport); + if (*transport == '\0') return (transport); + + if (*transport != ';') + return (NULL); + transport++; + ADV_SPACE(transport); + return (transport); +} + +static char *transport_parse_multicast (char *transport, CPlayerMedia *m) +{ + media_message(LOG_ERR,"Received multicast indication during SETUP"); + return (NULL); +} + +static char *convert_number (char *transport, uint32_t &value) +{ + value = 0; + while (isdigit(*transport)) { + value *= 10; + value += *transport - '0'; + transport++; + } + return (transport); +} + +static char *convert_hex (char *transport, uint32_t &value) +{ + value = 0; + while (isxdigit(*transport)) { + value *= 16; + if (isdigit(*transport)) + value += *transport - '0'; + else + value += tolower(*transport) - 'a' + 10; + transport++; + } + return (transport); +} + +static char *transport_parse_client_port (char *transport, CPlayerMedia *m) +{ + uint32_t port; + uint16_t our_port, our_port_max; + if (*transport++ != '=') { + return (NULL); + } + ADV_SPACE(transport); + transport = convert_number(transport, port); + ADV_SPACE(transport); + our_port = m->get_our_port(); + our_port_max = our_port + 1; + + if (port != our_port) { + media_message(LOG_ERR, "Returned client port %u doesn't match sent %u", + port, our_port); + return (NULL); + } + if (*transport == ';') { + transport++; + return (transport); + } + if (*transport == '\0') { + return (transport); + } + if (*transport != '-') { + return (NULL); + } + transport++; + ADV_SPACE(transport); + transport = convert_number(transport, port); + if ((port < our_port) || + (port > our_port_max)) { + media_message(LOG_ERR, "Illegal client to port %u, range %u to %u", + port, our_port, our_port_max); + return (NULL); + } + ADV_SPACE(transport); + if (*transport == ';') { + transport++; + } + return(transport); +} + +static char *transport_parse_server_port (char *transport, CPlayerMedia *m) +{ + uint32_t fromport, toport; + + if (*transport++ != '=') { + return (NULL); + } + ADV_SPACE(transport); + transport = convert_number(transport, fromport); + ADV_SPACE(transport); + + m->set_server_port((uint16_t)fromport); + + if (*transport == ';') { + transport++; + return (transport); + } + if (*transport == '\0') { + return (transport); + } + if (*transport != '-') { + return (NULL); + } + transport++; + ADV_SPACE(transport); + transport = convert_number(transport, toport); + if (toport < fromport || toport > fromport + 1) { + media_message(LOG_ERR, "Illegal server to port %u, from is %u", + toport, fromport); + return (NULL); + } + ADV_SPACE(transport); + if (*transport == ';') { + transport++; + } + return(transport); +} + +static char *transport_parse_source (char *transport, CPlayerMedia *m) +{ + char *ptr, *newone; + uint32_t addrlen; + + if (*transport != '=') { + return (NULL); + } + transport++; + ADV_SPACE(transport); + ptr = transport; + while (*transport != ';' && *transport != '\0') transport++; + addrlen = transport - ptr; + if (addrlen == 0) { + return (NULL); + } + newone = (char *)malloc(addrlen + 1); + if (newone == NULL) { + media_message(LOG_ERR, "Can't alloc memory for transport source"); + return (NULL); + } + strncpy(newone, ptr, addrlen); + newone[addrlen] = '\0'; + m->set_source_addr(newone); + if (*transport == ';') transport++; + return (transport); +} + +static char *transport_parse_ssrc (char *transport, CPlayerMedia *m) +{ + uint32_t ssrc; + if (*transport != '=') { + return (NULL); + } + transport++; + ADV_SPACE(transport); + transport = convert_hex(transport, ssrc); + ADV_SPACE(transport); + if (*transport != '\0') { + if (*transport != ';') { + return (NULL); + } + transport++; + } + m->set_rtp_ssrc(ssrc); + return (transport); +} + +static char *transport_parse_interleave (char *transport, CPlayerMedia *m) +{ + uint32_t chan, chan2; + if (*transport != '=') { + return (NULL); + } + transport++; + ADV_SPACE(transport); + transport = convert_number(transport, chan); + chan2 = m->get_rtp_media_number() * 2; + if (chan != chan2) { + media_message(LOG_ERR, "Transport interleave not what was requested %d %d", + chan, chan2); + return NULL; + } + ADV_SPACE(transport); + if (*transport != '\0') { + if (*transport != '-') { + return (NULL); + } + transport++; + transport = convert_number(transport, chan2); + if (chan + 1 != chan2) { + media_message(LOG_ERR, "Error in transport interleaved field"); + return (NULL); + } + + if (*transport == '\0') return (transport); + } + if (*transport != ';') return (NULL); + transport++; + return (transport); +} + +static char *rtpinfo_parse_ssrc (char *transport, CPlayerMedia *m, int &end) +{ + uint32_t ssrc; + if (*transport != '=') { + return (NULL); + } + transport++; + ADV_SPACE(transport); + transport = convert_hex(transport, ssrc); + ADV_SPACE(transport); + if (*transport != '\0') { + if (*transport == ',') { + end = 1; + } else if (*transport != ';') { + return (NULL); + } + transport++; + } + m->set_rtp_ssrc(ssrc); + return (transport); +} + +static char *rtpinfo_parse_seq (char *rtpinfo, CPlayerMedia *m, int &endofurl) +{ + uint32_t seq; + if (*rtpinfo != '=') { + return (NULL); + } + rtpinfo++; + ADV_SPACE(rtpinfo); + rtpinfo = convert_number(rtpinfo, seq); + ADV_SPACE(rtpinfo); + if (*rtpinfo != '\0') { + if (*rtpinfo == ',') { + endofurl = 1; + } else if (*rtpinfo != ';') { + return (NULL); + } + rtpinfo++; + } + m->set_rtp_base_seq(seq); + return (rtpinfo); +} + +static char *rtpinfo_parse_rtptime (char *rtpinfo, + CPlayerMedia *m, + int &endofurl) +{ + uint32_t rtptime; + int neg = 0; + if (*rtpinfo != '=') { + return (NULL); + } + rtpinfo++; + ADV_SPACE(rtpinfo); + if (*rtpinfo == '-') { + neg = 1; + rtpinfo++; + ADV_SPACE(rtpinfo); + } + rtpinfo = convert_number(rtpinfo, rtptime); + ADV_SPACE(rtpinfo); + if (*rtpinfo != '\0') { + if (*rtpinfo == ',') { + endofurl = 1; + } else if (*rtpinfo != ';') { + return (NULL); + } + rtpinfo++; + } + if (neg != 0) { + player_error_message("Warning - negative time returned in rtpinfo"); + rtptime = 0 - rtptime; + } + m->set_rtp_base_ts(rtptime); + return (rtpinfo); +} +struct { + const char *name; + uint32_t namelen; + char *(*routine)(char *transport, CPlayerMedia *); +} transport_types[] = +{ + TTYPE("unicast", transport_parse_unicast), + TTYPE("multicast", transport_parse_multicast), + TTYPE("client_port", transport_parse_client_port), + TTYPE("server_port", transport_parse_server_port), + TTYPE("source", transport_parse_source), + TTYPE("ssrc", transport_parse_ssrc), + TTYPE("interleaved", transport_parse_interleave), + {NULL, 0, NULL}, +}; + +int CPlayerMedia::process_rtsp_transport (char *transport) +{ + uint32_t protolen; + int ix; + + if (transport == NULL) + return (-1); + + protolen = strlen(m_media_info->proto); + + if (strncasecmp(transport, m_media_info->proto, protolen) != 0) { + media_message(LOG_ERR, "transport %s doesn't match %s", transport, + m_media_info->proto); + return (-1); + } + transport += protolen; + if (*transport == '/') { + transport++; + if (m_rtp_use_rtsp) { + if (strncasecmp(transport, "TCP", strlen("TCP")) != 0) { + media_message(LOG_ERR, "Transport is not TCP"); + return (-1); + } + transport += strlen("TCP"); + } else { + if (strncasecmp(transport, "UDP", strlen("UDP")) != 0) { + media_message(LOG_ERR, "Transport is not UDP"); + return (-1); + } + transport += strlen("UDP"); + } + } + if (*transport != ';') { + return (-1); + } + transport++; + do { + ADV_SPACE(transport); + for (ix = 0; transport_types[ix].name != NULL; ix++) { + if (strncasecmp(transport, + transport_types[ix].name, + transport_types[ix].namelen - 1) == 0) { + transport += transport_types[ix].namelen - 1; + ADV_SPACE(transport); + transport = (transport_types[ix].routine)(transport, this); + break; + } + } + if (transport_types[ix].name == NULL) { + media_message(LOG_INFO, "Illegal mime type in transport - skipping %s", + transport); + while (*transport != ';' && *transport != '\0') transport++; + if (*transport != '\0') transport++; + } + } while (transport != NULL && *transport != '\0'); + + if (transport == NULL) { + return (-1); + } + return (0); +} + +struct { + const char *name; + uint32_t namelen; + char *(*routine)(char *transport, CPlayerMedia *, int &end_for_url); +} rtpinfo_types[] = +{ + TTYPE("seq", rtpinfo_parse_seq), + TTYPE("rtptime", rtpinfo_parse_rtptime), + TTYPE("ssrc", rtpinfo_parse_ssrc), + {NULL, 0, NULL}, +}; + +int pdp_process_rtsp_rtpinfo (char *rtpinfo, + CPlayerSession *session, + CPlayerMedia *media) +{ + int ix; + CPlayerMedia *newmedia; + if (rtpinfo == NULL) + return (0); + + do { + int no_mimes = 0; + ADV_SPACE(rtpinfo); + if (strncasecmp(rtpinfo, "url", strlen("url")) != 0) { + media_message(LOG_ERR, "Url not found"); + return (-1); + } + rtpinfo += strlen("url"); + ADV_SPACE(rtpinfo); + if (*rtpinfo != '=') { + media_message(LOG_ERR, "Can't find = after url"); + return (-1); + } + rtpinfo++; + ADV_SPACE(rtpinfo); + char *url = rtpinfo; + while (*rtpinfo != '\0' && *rtpinfo != ';' && *rtpinfo != ',') { + rtpinfo++; + } + if (*rtpinfo == '\0') { + no_mimes = 1; + } else { + if (*rtpinfo == ',') { + no_mimes = 1; + } + *rtpinfo++ = '\0'; + } + char *temp = url; + newmedia = session->rtsp_url_to_media(url); + if (newmedia == NULL) { + media_message(LOG_ERR, "Can't find media from %s", url); + return -1; + } else if (media != NULL && media != newmedia) { + media_message(LOG_ERR, "Url in rtpinfo does not match media %s", url); + return -1; + } + if (temp != url) + free(url); + + if (no_mimes == 0) { + int endofurl = 0; + do { + ADV_SPACE(rtpinfo); + for (ix = 0; rtpinfo_types[ix].name != NULL; ix++) { + if (strncasecmp(rtpinfo, + rtpinfo_types[ix].name, + rtpinfo_types[ix].namelen - 1) == 0) { + rtpinfo += rtpinfo_types[ix].namelen - 1; + ADV_SPACE(rtpinfo); + rtpinfo = (rtpinfo_types[ix].routine)(rtpinfo, newmedia, endofurl); + break; + } + } + if (rtpinfo_types[ix].name == NULL) { +#if 1 + media_message(LOG_INFO, "Unknown mime-type in RtpInfo - skipping %s", + rtpinfo); +#endif + while (*rtpinfo != ';' && *rtpinfo != '\0') rtpinfo++; + if (*rtpinfo != '\0') rtpinfo++; + } + } while (endofurl == 0 && rtpinfo != NULL && *rtpinfo != '\0'); + } + newmedia = NULL; + } while (rtpinfo != NULL && *rtpinfo != '\0'); + + if (rtpinfo == NULL) { + return (-1); + } + + return (1); +} + +int CPlayerMedia::rtp_receive_packet (unsigned char interleaved, + struct rtp_packet *pak, + int len) +{ + int ret; + if ((interleaved & 1) == 0) { + ret = rtp_process_recv_data(m_rtp_session, 0, pak, len); + if (ret < 0) { + xfree(pak); + } + } else { + uint8_t *pakbuf = (uint8_t *)pak; + pakbuf += sizeof(rtp_packet_data); + + rtp_process_ctrl(m_rtp_session, pakbuf, len); + xfree(pak); + ret = 0; + } + return ret; +} + +void CPlayerMedia::rtp_periodic (void) +{ + rtp_send_ctrl(m_rtp_session, + m_rtp_byte_stream != NULL ? + m_rtp_byte_stream->get_last_rtp_timestamp() : 0, + NULL); + rtp_update(m_rtp_session); + if (m_rtp_byte_stream != NULL) { + int ret = m_rtp_byte_stream->recv_task(m_decode_thread_waiting); + if (ret > 0) { + if (m_rtp_buffering == 0) { + m_rtp_buffering = 1; + start_decoding(); + } else { + bytestream_primed(); + } + } + return; + } + if (m_head != NULL) { + /* + * Make sure that the payload type is the same + */ + if (m_head->rtp_pak_pt == m_tail->rtp_pak_pt) { + if (m_rtp_queue_len > 10) { // 10 packets consecutive proto same + if (determine_payload_type_from_rtp() == FALSE) { + clear_rtp_packets(); + } + } + } else { + clear_rtp_packets(); + } + } +} + +void CPlayerMedia::rtp_start (void) +{ + if (m_rtp_ssrc_set == TRUE) { + rtp_set_my_ssrc(m_rtp_session, m_rtp_ssrc); + } else { + // For now - we'll set up not to wait for RTCP validation + // before indicating if rtp library should accept. + rtp_set_option(m_rtp_session, RTP_OPT_WEAK_VALIDATION, FALSE); + rtp_set_option(m_rtp_session, RTP_OPT_PROMISC, TRUE); + } + if (m_rtp_byte_stream != NULL) { + //m_rtp_byte_stream->reset(); - gets called when pausing + m_rtp_byte_stream->flush_rtp_packets(); + } + m_rtp_buffering = 0; +} + +void CPlayerMedia::rtp_end(void) +{ + if (m_rtp_session != NULL) { + rtp_send_bye(m_rtp_session); + rtp_done(m_rtp_session); + } + m_rtp_session = NULL; +} + +int CPlayerMedia::rtcp_send_packet (uint8_t *buffer, int buflen) +{ + if (config.get_config_value(CONFIG_SEND_RTCP_IN_RTP_OVER_RTSP) != 0) { + return rtsp_thread_send_rtcp(m_parent->get_rtsp_client(), + m_rtp_media_number_in_session, + buffer, + buflen); + } + return buflen; +} + +int CPlayerMedia::recv_thread (void) +{ + struct timeval timeout; + int retcode; + CMsg *newmsg; + int recv_thread_stop = 0; + connect_desc_t *cptr; + cptr = get_connect_desc_from_media(m_media_info); + + + m_rtp_buffering = 0; + if (m_stream_ondemand != 0) { + /* + * We need to free up the ports that we got before RTP tries to set + * them up, so we don't have any re-use conflicts. There is a small + * window here that they might get used... + */ + delete m_ports; // free up the port numbers + m_ports = NULL; + } + + double bw; + + if (find_rtcp_bandwidth_from_media(m_media_info, &bw) < 0) { + bw = 5000.0; + } else { + media_message(LOG_DEBUG, "Using bw from sdp %g", bw); + } + m_rtp_session = rtp_init(m_source_addr == NULL ? + cptr->conn_addr : m_source_addr, + m_our_port, + m_server_port, + cptr == NULL ? 1 : cptr->ttl, // need ttl here + bw, // rtcp bandwidth ? + pdp_recv_callback, + (uint8_t *)this); + if (m_rtp_session != NULL) { + rtp_set_option(m_rtp_session, RTP_OPT_WEAK_VALIDATION, FALSE); + rtp_set_option(m_rtp_session, RTP_OPT_PROMISC, TRUE); + rtp_start(); + } + m_rtp_inited = 1; + + while (recv_thread_stop == 0) { + if ((newmsg = m_rtp_msg_queue.get_message()) != NULL) { + //player_debug_message("recv thread message %d", newmsg->get_value()); + switch (newmsg->get_value()) { + case MSG_STOP_THREAD: + recv_thread_stop = 1; + break; + case MSG_PAUSE_SESSION: + // Indicate that we need to restart the session. + // But keep going... + rtp_start(); + break; + } + delete newmsg; + newmsg = NULL; + } + if (recv_thread_stop == 1) { + continue; + } + if (m_rtp_session == NULL) { + SDL_Delay(50); + } else { + timeout.tv_sec = 0; + timeout.tv_usec = 500000; + retcode = rtp_recv(m_rtp_session, &timeout, 0); + // player_debug_message("rtp_recv return %d", retcode); + // Run rtp periodic after each packet received or idle time. + if (m_paused == 0 || m_stream_ondemand != 0) + rtp_periodic(); + } + + } + /* + * When we're done, send a bye, close up rtp, and go home + */ + rtp_end(); + return (0); +} + +void CPlayerMedia::recv_callback (struct rtp *session, rtp_event *e) +{ + if (e == NULL) return; + /* + * If we're paused, just dump the packet. Multicast case + */ + if (m_paused != 0) { + if (e->type == RX_RTP) { + xfree(e->data); + return; + } + } +#if DROP_PAKS + if (e->type == RX_RTP && dropcount >= 50) { + xfree((rtp_packet *)e->data); + dropcount = 0; + return; + } else { + dropcount++; + } +#endif + if (m_rtp_byte_stream != NULL) { + m_rtp_byte_stream->recv_callback(session, e); + return; + } + switch (e->type) { + case RX_RTP: + /* regular rtp packet - add it to the queue */ + rtp_packet *rpak; + + + rpak = (rtp_packet *)e->data; + if (rpak->rtp_data_len == 0) { + xfree(rpak); + } else { + rpak->pd.rtp_pd_timestamp = get_time_of_day(); + rpak->pd.rtp_pd_have_timestamp = true; + add_rtp_packet_to_queue(rpak, &m_head, &m_tail, m_is_video ? "video" : "audio"); + m_rtp_queue_len++; + } + break; + case RX_SR: + rtcp_sr *srpak; + srpak = (rtcp_sr *)e->data; + + m_rtcp_ntp_frac = srpak->ntp_frac; + m_rtcp_ntp_sec = srpak->ntp_sec; + m_rtcp_rtp_ts = srpak->rtp_ts; + m_rtcp_received = 1; + break; + case RX_APP: + free(e->data); + break; + default: +#if 0 + media_message(LOG_DEBUG, "Thread %u - Callback from rtp with %d %p", + SDL_ThreadID(),e->type, e->data); +#endif + break; + } +} + +int CPlayerMedia::determine_payload_type_from_rtp(void) +{ + uint8_t payload_type = m_head->rtp_pak_pt, temp; + format_list_t *fmt; + uint64_t tickpersec; + + fmt = m_media_info->fmt; + while (fmt != NULL) { + // rtp payloads are all numeric + temp = atoi(fmt->fmt); + if (temp == payload_type) { + m_media_fmt = fmt; + if (fmt->rtpmap != NULL) { + tickpersec = fmt->rtpmap->clock_rate; + } else { + if (payload_type >= 96) { + media_message(LOG_ERR, "Media %s, rtp payload type of %u, no rtp map", + m_media_info->media, payload_type); + return (FALSE); + } else { + // generic payload type. between 0 and 23 are audio - most + // are 8000 + // all video (above 25) are 90000 + tickpersec = 90000; + // this will handle the >= 0 case as well. + if (payload_type <= 23) { + tickpersec = 8000; + if (payload_type == 6) { + tickpersec = 16000; + } else if (payload_type == 10 || payload_type == 11) { + tickpersec = 44100; + } else if (payload_type == 14) + tickpersec = 90000; + } + } + } + + create_rtp_byte_stream(payload_type, + tickpersec, + fmt); + m_rtp_byte_stream->play((uint64_t)(m_play_start_time * 1000.0)); + m_byte_stream = m_rtp_byte_stream; + if (!is_video()) { + m_rtp_byte_stream->set_sync(m_parent); + } else { + m_parent->syncronize_rtp_bytestreams(NULL); + } +#if 1 + media_message(LOG_DEBUG, "media %s - rtp tps %u ntp per rtp ", + m_media_info->media, + m_rtptime_tickpersec); +#endif + + return (TRUE); + } + fmt = fmt->next; + } + media_message(LOG_ERR, "Payload type %d not in format list for media %s", + payload_type, m_is_video ? "video" : "audio"); + return (FALSE); +} + +/* + * set up rtptime + */ +void CPlayerMedia::set_rtp_base_ts (uint32_t time) +{ + m_rtsp_base_ts_received = 1; + m_rtp_base_ts = time; + if (m_rtp_byte_stream != NULL) { + m_rtp_byte_stream->set_rtp_base_ts(time); + } +} + +void CPlayerMedia::set_rtp_base_seq (uint16_t seq) +{ + m_rtsp_base_seq_received = 1; + m_rtp_base_seq = seq; + if (m_rtp_byte_stream != NULL) { + m_rtp_byte_stream->set_rtp_base_seq(seq); + } +} + +void CPlayerMedia::rtp_init_tcp (void) +{ + connect_desc_t *cptr; + double bw; + + if (find_rtcp_bandwidth_from_media(m_media_info, &bw) < 0) { + bw = 5000.0; + } + cptr = get_connect_desc_from_media(m_media_info); + m_rtp_session = rtp_init_extern_net(m_source_addr == NULL ? + cptr->conn_addr : m_source_addr, + m_our_port, + m_server_port, + cptr->ttl, + bw, // rtcp bandwidth ? + pdp_recv_callback, + pdp_rtcp_send_packet, + (uint8_t *)this); + rtp_set_option(m_rtp_session, RTP_OPT_WEAK_VALIDATION, FALSE); + rtp_set_option(m_rtp_session, RTP_OPT_PROMISC, TRUE); + m_rtp_inited = 1; + +} + +void CPlayerMedia::create_rtp_byte_stream (uint8_t rtp_pt, + uint64_t tps, + format_list_t *fmt) +{ + int codec; + rtp_check_return_t plugin_ret; + rtp_plugin_t *rtp_plugin; + + rtp_plugin = NULL; + plugin_ret = check_for_rtp_plugins(fmt, rtp_pt, &rtp_plugin); + + if (plugin_ret != RTP_PLUGIN_NO_MATCH) { + switch (plugin_ret) { + case RTP_PLUGIN_MATCH: + player_debug_message("Starting rtp bytestream %s from plugin", + rtp_plugin->name); + m_rtp_byte_stream = new CPluginRtpByteStream(rtp_plugin, + fmt, + rtp_pt, + m_stream_ondemand, + tps, + &m_head, + &m_tail, + m_rtsp_base_seq_received, + m_rtp_base_seq, + m_rtsp_base_ts_received, + m_rtp_base_ts, + m_rtcp_received, + m_rtcp_ntp_frac, + m_rtcp_ntp_sec, + m_rtcp_rtp_ts); + return; + case RTP_PLUGIN_MATCH_USE_VIDEO_DEFAULT: + // just fall through... + break; + case RTP_PLUGIN_MATCH_USE_AUDIO_DEFAULT: + m_rtp_byte_stream = + new CAudioRtpByteStream(rtp_pt, + fmt, + m_stream_ondemand, + tps, + &m_head, + &m_tail, + m_rtsp_base_seq_received, + m_rtp_base_seq, + m_rtsp_base_ts_received, + m_rtp_base_ts, + m_rtcp_received, + m_rtcp_ntp_frac, + m_rtcp_ntp_sec, + m_rtcp_rtp_ts); + if (m_rtp_byte_stream != NULL) { + player_debug_message("Starting generic audio byte stream"); + return; + } + + default: + break; + } + } else { + if (is_video() && (rtp_pt == 32)) { + codec = VIDEO_MPEG12; + m_rtp_byte_stream = new CMpeg3RtpByteStream(rtp_pt, + fmt, + m_stream_ondemand, + tps, + &m_head, + &m_tail, + m_rtsp_base_seq_received, + m_rtp_base_seq, + m_rtsp_base_ts_received, + m_rtp_base_ts, + m_rtcp_received, + m_rtcp_ntp_frac, + m_rtcp_ntp_sec, + m_rtcp_rtp_ts); + if (m_rtp_byte_stream != NULL) { + return; + } + } else { + if (rtp_pt == 14) { + codec = MPEG4IP_AUDIO_MP3; + } else if (rtp_pt <= 23) { + codec = MPEG4IP_AUDIO_GENERIC; + } else { + if (fmt->rtpmap == NULL) return; + + codec = lookup_audio_codec_by_name(fmt->rtpmap->encode_name); + if (codec < 0) { + codec = MPEG4IP_AUDIO_NONE; // fall through everything to generic + } + } + switch (codec) { + case MPEG4IP_AUDIO_MP3: { + m_rtp_byte_stream = + new CAudioRtpByteStream(rtp_pt, fmt, + m_stream_ondemand, + tps, + &m_head, + &m_tail, + m_rtsp_base_seq_received, + m_rtp_base_seq, + m_rtsp_base_ts_received, + m_rtp_base_ts, + m_rtcp_received, + m_rtcp_ntp_frac, + m_rtcp_ntp_sec, + m_rtcp_rtp_ts); + if (m_rtp_byte_stream != NULL) { + m_rtp_byte_stream->set_skip_on_advance(4); + player_debug_message("Starting mp3 2250 audio byte stream"); + return; + } + } + break; + case MPEG4IP_AUDIO_MP3_ROBUST: + m_rtp_byte_stream = + new CRfc3119RtpByteStream(rtp_pt, fmt, + m_stream_ondemand, + tps, + &m_head, + &m_tail, + m_rtsp_base_seq_received, + m_rtp_base_seq, + m_rtsp_base_ts_received, + m_rtp_base_ts, + m_rtcp_received, + m_rtcp_ntp_frac, + m_rtcp_ntp_sec, + m_rtcp_rtp_ts); + if (m_rtp_byte_stream != NULL) { + player_debug_message("Starting mpa robust byte stream"); + return; + } + break; + case MPEG4IP_AUDIO_GENERIC: + m_rtp_byte_stream = + new CAudioRtpByteStream(rtp_pt, fmt, + m_stream_ondemand, + tps, + &m_head, + &m_tail, + m_rtsp_base_seq_received, + m_rtp_base_seq, + m_rtsp_base_ts_received, + m_rtp_base_ts, + m_rtcp_received, + m_rtcp_ntp_frac, + m_rtcp_ntp_sec, + m_rtcp_rtp_ts); + if (m_rtp_byte_stream != NULL) { + player_debug_message("Starting generic audio byte stream"); + return; + } + default: + break; + } + } + m_rtp_byte_stream = new CRtpByteStream(fmt->media->media, + fmt, + rtp_pt, + m_stream_ondemand, + tps, + &m_head, + &m_tail, + m_rtsp_base_seq_received, + m_rtp_base_seq, + m_rtsp_base_ts_received, + m_rtp_base_ts, + m_rtcp_received, + m_rtcp_ntp_frac, + m_rtcp_ntp_sec, + m_rtcp_rtp_ts); + } +} + +void CPlayerMedia::syncronize_rtp_bytestreams (rtcp_sync_t *sync) +{ + if (!is_video()) { + player_error_message("Attempt to syncronize audio byte stream"); + return; + } + if (m_rtp_byte_stream != NULL) + m_rtp_byte_stream->syncronize(sync); +} + +void CPlayerMedia::start_decoding (void) +{ + m_decode_msg_queue.send_message(MSG_START_DECODING, + NULL, + 0, + m_decode_thread_sem); +} + +void CPlayerMedia::bytestream_primed (void) +{ + if (m_decode_thread_waiting != 0) { + m_decode_thread_waiting = 0; + SDL_SemPost(m_decode_thread_sem); + } +} + +void CPlayerMedia::wait_on_bytestream (void) +{ + m_decode_thread_waiting = 1; +#ifdef DEBUG_DECODE + if (m_media_info) + media_message(LOG_INFO, "decode thread %s waiting", m_media_info->media); + else + media_message(LOG_INFO, "decode thread waiting"); +#endif + SDL_SemWait(m_decode_thread_sem); + m_decode_thread_waiting = 0; +} + +void CPlayerMedia::parse_decode_message (int &thread_stop, int &decoding) +{ + CMsg *newmsg; + + if ((newmsg = m_decode_msg_queue.get_message()) != NULL) { +#ifdef DEBUG_DECODE_MSGS + media_message(LOG_DEBUG, "decode thread message %d",newmsg->get_value()); +#endif + switch (newmsg->get_value()) { + case MSG_STOP_THREAD: + thread_stop = 1; + break; + case MSG_PAUSE_SESSION: + decoding = 0; + if (m_video_sync != NULL) { + m_video_sync->flush_decode_buffers(); + } + if (m_audio_sync != NULL) { + m_audio_sync->flush_decode_buffers(); + } + break; + case MSG_START_DECODING: + if (m_video_sync != NULL) { + m_video_sync->flush_decode_buffers(); + } + if (m_audio_sync != NULL) { + m_audio_sync->flush_decode_buffers(); + } + decoding = 1; + break; + } + delete newmsg; + } +} + +int CPlayerMedia::decode_thread (void) +{ + // uint32_t msec_per_frame = 0; + int ret = 0; + int thread_stop = 0, decoding = 0; + uint32_t decode_skipped_frames = 0; + uint64_t ourtime; + // Tell bytestream we're starting the next frame - they'll give us + // the time. + uint8_t *frame_buffer; + uint32_t frame_len; + void *ud = NULL; + + uint32_t frames_decoded; + uint64_t bytes_decoded; + uint32_t frames_decoded_last_sec; + uint64_t bytes_decoded_last_sec; + uint64_t current_second; + uint32_t total_secs; + uint32_t last_div = 0; + + total_secs = 0; + frames_decoded = 0; + bytes_decoded = 0; + frames_decoded_last_sec = 0; + bytes_decoded_last_sec = 0; + current_second = 0; + + while (thread_stop == 0) { + // waiting here for decoding or thread stop + ret = SDL_SemWait(m_decode_thread_sem); +#ifdef DEBUG_DECODE + media_message(LOG_DEBUG, "%s Decode thread awake", + is_video() ? "video" : "audio"); +#endif + parse_decode_message(thread_stop, decoding); + + if (decoding == 1) { + // We've been told to start decoding - if we don't have a codec, + // create one + if (is_video()) { + if (m_video_sync == NULL) { + m_video_sync = m_parent->set_up_video_sync(); + } + m_video_sync->set_wait_sem(m_decode_thread_sem); + } else { + if (m_audio_sync == NULL) { + m_audio_sync = m_parent->set_up_audio_sync(); + } + m_audio_sync->set_wait_sem(m_decode_thread_sem); + } + if (m_plugin == NULL) { + if (is_video()) { + m_plugin = check_for_video_codec(NULL, + m_media_fmt, + -1, + -1, + m_user_data, + m_user_data_size); + if (m_plugin != NULL) { + m_plugin_data = (m_plugin->vc_create)(NULL, // must figure from sdp + -1, + -1, + m_media_fmt, + m_video_info, + m_user_data, + m_user_data_size, + get_video_vft(), + m_video_sync); + if (m_plugin_data == NULL) { + m_plugin = NULL; + } else { + media_message(LOG_DEBUG, "Starting %s codec from decode thread", + m_plugin->c_name); + } + } + } else { + m_plugin = check_for_audio_codec(NULL, + m_media_fmt, + -1, + -1, + m_user_data, + m_user_data_size); + if (m_plugin != NULL) { + m_plugin_data = (m_plugin->ac_create)(NULL, + -1, + -1, + m_media_fmt, + m_audio_info, + m_user_data, + m_user_data_size, + get_audio_vft(), + m_audio_sync); + if (m_plugin_data == NULL) { + m_plugin = NULL; + } else { + media_message(LOG_DEBUG, "Starting %s codec from decode thread", + m_plugin->c_name); + } + } + } + } + if (m_plugin != NULL) { + m_plugin->c_do_pause(m_plugin_data); + } else { + while (thread_stop == 0 && decoding) { + SDL_Delay(100); + if (m_rtp_byte_stream) { + m_rtp_byte_stream->flush_rtp_packets(); + } + parse_decode_message(thread_stop, decoding); + } + } + } + /* + * this is our main decode loop + */ +#ifdef DEBUG_DECODE + media_message(LOG_DEBUG, "%s Into decode loop", + is_video() ? "video" : "audio"); +#endif + frames_decoded_last_sec = 0; + bytes_decoded_last_sec = 0; + current_second = 0; + while ((thread_stop == 0) && decoding) { + parse_decode_message(thread_stop, decoding); + if (thread_stop != 0) + continue; + if (decoding == 0) { + m_plugin->c_do_pause(m_plugin_data); + continue; + } + if (m_byte_stream->eof()) { + media_message(LOG_INFO, "%s hit eof", m_is_video ? "video" : "audio"); + if (m_audio_sync) m_audio_sync->set_eof(); + if (m_video_sync) m_video_sync->set_eof(); + decoding = 0; + continue; + } + if (m_byte_stream->have_no_data()) { + // Indicate that we're waiting, and wait for a message from RTP + // task. + wait_on_bytestream(); + continue; + } + + frame_buffer = NULL; + ourtime = m_byte_stream->start_next_frame(&frame_buffer, + &frame_len, + &ud); + /* + * If we're decoding video, see if we're playing - if so, check + * if we've fallen significantly behind the audio + */ + if (is_video() && + (m_parent->get_session_state() == SESSION_PLAYING)) { + uint64_t current_time = m_parent->get_playing_time(); + if (current_time >= ourtime) { +#if 1 + media_message(LOG_INFO, "Candidate for skip decode "U64" our "U64, + current_time, ourtime); +#endif + // If the bytestream can skip ahead, let's do so + if (m_byte_stream->can_skip_frame() != 0) { + int ret; + int hassync; + int count; + current_time += 200; + count = 0; + // Skip up to the current time + 200 msec + ud = NULL; + do { + if (ud != NULL) free(ud); + ret = m_byte_stream->skip_next_frame(&ourtime, &hassync, + &frame_buffer, &frame_len, + &ud); + decode_skipped_frames++; + } while (ret != 0 && + !m_byte_stream->eof() && + current_time > ourtime); + if (m_byte_stream->eof() || ret == 0) continue; +#if 1 + media_message(LOG_INFO, "Skipped ahead "U64 " to "U64, + current_time - 200, ourtime); +#endif + /* + * Ooh - fun - try to match to the next sync value - if not, + * 15 frames + */ + do { + if (ud != NULL) free(ud); + ret = m_byte_stream->skip_next_frame(&ourtime, &hassync, + &frame_buffer, &frame_len, + &ud); + if (hassync < 0) { + uint64_t diff = ourtime - current_time; + if (diff > (2 * C_64)) { + hassync = 1; + } + } + decode_skipped_frames++; + count++; + } while (ret != 0 && + hassync <= 0 && + count < 30 && + !m_byte_stream->eof()); + if (m_byte_stream->eof() || ret == 0) continue; +#ifdef DEBUG_DECODE + media_message(LOG_INFO, "Matched ahead - count %d, sync %d time "U64, + count, hassync, ourtime); +#endif + } + } + } +#ifdef DEBUG_DECODE + media_message(LOG_DEBUG, "Decoding %c frame " U64, + m_is_video ? 'v' : 'a', ourtime); +#endif + if (frame_buffer != NULL && frame_len != 0) { + int sync_frame; + ret = m_plugin->c_decode_frame(m_plugin_data, + ourtime, + m_streaming != 0, + &sync_frame, + frame_buffer, + frame_len, + ud); +#ifdef DEBUG_DECODE + media_message(LOG_DEBUG, "Decoding %c frame return %d", + m_is_video ? 'v' : 'a', ret); +#endif + if (ret > 0) { + frames_decoded++; + m_byte_stream->used_bytes_for_frame(ret); + bytes_decoded += ret; + last_div = ourtime % 1000; + if ((ourtime / 1000) > current_second) { + if (frames_decoded_last_sec != 0) { +#if 0 + media_message(LOG_DEBUG, "%s - Second "U64", frames %d bytes "U64, + m_is_video ? "video" : "audio", + current_second, + frames_decoded_last_sec, + bytes_decoded_last_sec); +#endif + } + current_second = ourtime / 1000; + total_secs++; + frames_decoded_last_sec = 1; + bytes_decoded_last_sec = ret; + } else { + frames_decoded_last_sec++; + bytes_decoded_last_sec += ret; + } + } else { + m_byte_stream->used_bytes_for_frame(frame_len); + } + + } + } + // calculate frame rate for session + } + if (m_is_video) + media_message(LOG_NOTICE, "Video decoder skipped %u frames", + decode_skipped_frames); + if (total_secs != 0) { + double fps, bps; + double secs; + secs = last_div; + secs /= 1000.0; + secs += total_secs; + + fps = frames_decoded; + fps /= secs; + bps = UINT64_TO_DOUBLE(bytes_decoded); + bps *= 8.0 / secs; + media_message(LOG_NOTICE, "%s - bytes "U64", seconds %g, fps %g bps "U64, + m_is_video ? "video" : "audio", + bytes_decoded, secs, + fps, bytes_decoded * 8 / total_secs); + } + if (m_plugin) { + m_plugin->c_close(m_plugin_data); + m_plugin_data = NULL; + } + return (0); +} diff --git a/modules/pdp_mp4playersession.cpp b/modules/pdp_mp4playersession.cpp new file mode 100644 index 0000000..3bd9486 --- /dev/null +++ b/modules/pdp_mp4playersession.cpp @@ -0,0 +1,1045 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is MPEG4IP. + * + * The Initial Developer of the Original Code is Cisco Systems Inc. + * Portions created by Cisco Systems Inc. are + * Copyright (C) Cisco Systems Inc. 2000, 2001. All Rights Reserved. + * + * Contributor(s): + * Bill May wmay@cisco.com + * video aspect ratio by: + * Peter Maersk-Moller peter @maersk-moller.net + * + * Adapted for PD/PDP by Yves Degoyon (ydegoyon@free.fr) + */ + +/* + * pdp_mp4playersession.cpp - describes player session class, which is the + * main access point for the player + */ + +#include "mpeg4ip.h" +#include "pdp_mp4playersession.h" +#include "pdp_mp4playermedia.h" +#include "pdp_mp4audiosync.h" +#include "pdp_mp4videosync.h" +#include "pdp_mp4player~.h" +#include "player_sdp.h" +#include "player_util.h" +#include <stdlib.h> +#include <SDL.h> +#include <SDL_thread.h> +#include "player_util.h" +#include "m_pd.h" + +#define sync_message(loglevel, fmt...) message(loglevel, "avsync", fmt) + +enum { + SYNC_STATE_INIT = 0, + SYNC_STATE_WAIT_SYNC = 1, + SYNC_STATE_WAIT_AUDIO = 2, + SYNC_STATE_PLAYING = 3, + SYNC_STATE_PAUSED = 4, + SYNC_STATE_DONE = 5, + SYNC_STATE_EXIT = 6, +}; + +#ifdef DEBUG_SYNC_STATE +const char *sync_state[] = { + "Init", + "Wait Sync", + "Wait Audio", + "Playing", + "Paused", + "Done", + "Exit" +}; +#endif + +CPlayerSession::CPlayerSession (CMsgQueue *master_mq, + SDL_sem *master_sem, + const char *name, + t_pdp_mp4player *pdp_father) +{ + m_sdp_info = NULL; + m_my_media = NULL; + m_rtsp_client = NULL; + m_video_sync = NULL; + m_audio_sync = NULL; + m_sync_thread = NULL; + m_sync_sem = NULL; + m_content_base = NULL; + m_master_msg_queue = master_mq; + m_master_msg_queue_sem = master_sem; + m_paused = 0; + m_streaming = 0; + m_session_name = strdup(name); + m_audio_volume = 75; + m_current_time = 0; + m_seekable = 0; + m_session_state = SESSION_PAUSED; + m_clock_wrapped = -1; + m_hardware_error = 0; + m_pixel_height = -1; + m_pixel_width = -1; + m_session_control_is_aggregate = 0; + for (int ix = 0; ix < SESSION_DESC_COUNT; ix++) { + m_session_desc[ix] = NULL; + } + m_media_close_callback = NULL; + m_media_close_callback_data = NULL; + m_streaming_media_set_up = 0; + m_unused_ports = NULL; + m_first_time_played = 0; + m_latency = 0; + m_have_audio_rtcp_sync = false; + m_father = pdp_father; +} + +CPlayerSession::~CPlayerSession () +{ + int hadthread = 0; +#ifndef NEED_SDL_VIDEO_IN_MAIN_THREAD + if (m_sync_thread) { + send_sync_thread_a_message(MSG_STOP_THREAD); + SDL_WaitThread(m_sync_thread, NULL); + m_sync_thread = NULL; + hadthread = 1; + } +#else + send_sync_thread_a_message(MSG_STOP_THREAD); + hadthread = 1; +#endif + + + + if (m_streaming_media_set_up != 0 && + session_control_is_aggregate()) { + rtsp_command_t cmd; + rtsp_decode_t *decode; + memset(&cmd, 0, sizeof(rtsp_command_t)); + rtsp_send_aggregate_teardown(m_rtsp_client, + m_sdp_info->control_string, + &cmd, + &decode); + free_decode_response(decode); + } + + + if (m_rtsp_client) { + free_rtsp_client(m_rtsp_client); + m_rtsp_client = NULL; + } + + while (m_my_media != NULL) { + CPlayerMedia *p; + p = m_my_media; + m_my_media = p->get_next(); + delete p; + } + + if (m_sdp_info) { + sdp_free_session_desc(m_sdp_info); + m_sdp_info = NULL; + } + + if (m_sync_sem) { + SDL_DestroySemaphore(m_sync_sem); + m_sync_sem = NULL; + } + + if (m_video_sync != NULL) { + delete m_video_sync; + m_video_sync = NULL; + } + + if (m_audio_sync != NULL) { + delete m_audio_sync; + m_audio_sync = NULL; + } + if (m_session_name) { + free((void *)m_session_name); + m_session_name = NULL; + } + if (m_content_base) { + free((void *) m_content_base); + m_content_base = NULL; + } + for (int ix = 0; ix < SESSION_DESC_COUNT; ix++) { + if (m_session_desc[ix] != NULL) + free((void *)m_session_desc[ix]); + m_session_desc[ix] = NULL; + } + + if (m_media_close_callback != NULL) { + m_media_close_callback(m_media_close_callback_data); + } + + while (m_unused_ports != NULL) { + CIpPort *first; + first = m_unused_ports; + m_unused_ports = first->get_next(); + delete first; + } + if (hadthread != 0) { + SDL_Quit(); + } +} + +int CPlayerSession::create_streaming_broadcast (session_desc_t *sdp, + char *ermsg, + uint32_t errlen) +{ + session_set_seekable(0); + m_sdp_info = sdp; + m_streaming = 1; + m_rtp_over_rtsp = 0; + return (0); +} + +/* + * create_streaming - create a session for streaming. Create an + * RTSP session with the server, get the SDP information from it. + */ +int CPlayerSession::create_streaming_ondemand (const char *url, + char *errmsg, + uint32_t errlen, + int use_tcp) +{ + rtsp_command_t cmd; + rtsp_decode_t *decode; + sdp_decode_info_t *sdpdecode; + int dummy; + int err; + + // streaming has seek capability (at least on demand) + session_set_seekable(1); + post("pdp_mp4playersession : creating streaming %s (use_tcp=%d)", url, use_tcp); + memset(&cmd, 0, sizeof(rtsp_command_t)); + + /* + * create RTSP session + */ + if (use_tcp != 0) { + m_rtsp_client = rtsp_create_client_for_rtp_tcp(url, &err); + } else { + m_rtsp_client = rtsp_create_client(url, &err); + } + if (m_rtsp_client == NULL) { + snprintf(errmsg, errlen, "Failed to create RTSP client"); + player_error_message("Failed to create rtsp client - error %d", err); + return (err); + } + m_rtp_over_rtsp = use_tcp; + + cmd.accept = "application/sdp"; + + /* + * Send the RTSP describe. This should return SDP information about + * the session. + */ + int rtsp_resp; + + rtsp_resp = rtsp_send_describe(m_rtsp_client, &cmd, &decode); + if (rtsp_resp != RTSP_RESPONSE_GOOD) { + int retval; + if (decode != NULL) { + retval = (((decode->retcode[0] - '0') * 100) + + ((decode->retcode[1] - '0') * 10) + + (decode->retcode[2] - '0')); + snprintf(errmsg, errlen, "RTSP describe error %d %s", retval, + decode->retresp != NULL ? decode->retresp : ""); + free_decode_response(decode); + } else { + retval = -1; + snprintf(errmsg, errlen, "RTSP return invalid %d", rtsp_resp); + } + player_error_message("Describe response not good (error=%s)\n", errmsg); + return (retval); + } + + sdpdecode = set_sdp_decode_from_memory(decode->body); + if (sdpdecode == NULL) { + snprintf(errmsg, errlen, "Memory failure"); + player_error_message("Couldn't get sdp decode\n"); + free_decode_response(decode); + return (-1); + } + + /* + * Decode the SDP information into structures we can use. + */ + err = sdp_decode(sdpdecode, &m_sdp_info, &dummy); + free(sdpdecode); + if (err != 0) { + snprintf(errmsg, errlen, "Couldn't decode session description %s", + decode->body); + player_error_message("Couldn't decode sdp %s", decode->body); + free_decode_response(decode); + return (-1); + } + if (dummy != 1) { + snprintf(errmsg, errlen, "Incorrect number of sessions in sdp decode %d", + dummy); + player_error_message(errmsg); + free_decode_response(decode); + return (-1); + } + + if (m_sdp_info->control_string != NULL) { + set_session_control(1); + } + /* + * Make sure we can use the urls in the sdp info + */ + if (decode->content_location != NULL) { + // Note - we may have problems if the content location is not absolute. + m_content_base = strdup(decode->content_location); + } else if (decode->content_base != NULL) { + m_content_base = strdup(decode->content_base); + } else { + int urllen = strlen(url); + if (url[urllen] != '/') { + char *temp; + temp = (char *)malloc(urllen + 2); + strcpy(temp, url); + strcat(temp, "/"); + m_content_base = temp; + } else { + m_content_base = strdup(url); + } + } + + convert_relative_urls_to_absolute(m_sdp_info, + m_content_base); + + free_decode_response(decode); + m_streaming = 1; + return (0); +} + +CPDPVideoSync * CPlayerSession::set_up_video_sync (void) +{ + if (m_video_sync == NULL) { + m_video_sync = pdp_create_video_sync(this, m_father); + } + return m_video_sync; +} + +CPDPAudioSync *CPlayerSession::set_up_audio_sync (void) +{ + if (m_audio_sync == NULL) { + m_audio_sync = pdp_create_audio_sync(this, m_father); + } + return m_audio_sync; +} +/* + * set_up_sync_thread. Creates the sync thread, and a sync class + * for each media + */ +void CPlayerSession::set_up_sync_thread(void) +{ + CPlayerMedia *media; + + media = m_my_media; + while (media != NULL) { + if (media->is_video()) { + media->set_video_sync(set_up_video_sync()); + } else { + media->set_audio_sync(set_up_audio_sync()); + } + media= media->get_next(); + } + m_sync_sem = SDL_CreateSemaphore(0); +} + +/* + * play_all_media - get all media to play + */ +int CPlayerSession::play_all_media (int start_from_begin, + double start_time, + char *errmsg, + uint32_t errlen) +{ + int ret; + CPlayerMedia *p; + range_desc_t *range; + + if (m_sdp_info && m_sdp_info->session_range.have_range != FALSE) { + range = &m_sdp_info->session_range; + } else { + range = NULL; + p = m_my_media; + while (range == NULL && p != NULL) { + media_desc_t *media; + media = p->get_sdp_media_desc(); + if (media && media->media_range.have_range) { + range = &media->media_range; + } + p = p->get_next(); + } + } + p = m_my_media; + m_session_state = SESSION_BUFFERING; + if (m_paused == 1 && start_time == 0.0 && start_from_begin == FALSE) { + /* + * we were paused. Continue. + */ + m_play_start_time = m_current_time; + start_time = UINT64_TO_DOUBLE(m_current_time); + start_time /= 1000.0; + player_debug_message("Restarting at " U64 ", %g", m_current_time, start_time); + } else { + /* + * We might have been paused, but we're told to seek + */ + // Indicate what time we're starting at for sync task. + m_play_start_time = (uint64_t)(start_time * 1000.0); + } + m_paused = 0; + + send_sync_thread_a_message(MSG_START_SESSION); + // If we're doing aggregate rtsp, send the play command... + + if (session_control_is_aggregate()) { + char buffer[80]; + rtsp_command_t cmd; + rtsp_decode_t *decode; + + memset(&cmd, 0, sizeof(rtsp_command_t)); + if (range != NULL) { + uint64_t stime = (uint64_t)(start_time * 1000.0); + uint64_t etime = (uint64_t)(range->range_end * 1000.0); + sprintf(buffer, "npt="U64"."U64"-"U64"."U64, + stime / 1000, stime % 1000, etime / 1000, etime % 1000); + cmd.range = buffer; + } + if (rtsp_send_aggregate_play(m_rtsp_client, + m_sdp_info->control_string, + &cmd, + &decode) != 0) { + if (errmsg != NULL) { + snprintf(errmsg, errlen, "RTSP Aggregate Play Error %s-%s", + decode->retcode, + decode->retresp != NULL ? decode->retresp : ""); + } + player_debug_message("RTSP aggregate play command failed"); + free_decode_response(decode); + return (-1); + } + if (decode->rtp_info == NULL) { + player_error_message("No rtp info field"); + } else { + player_debug_message("rtp info is \'%s\'", decode->rtp_info); + } + int ret = pdp_process_rtsp_rtpinfo(decode->rtp_info, this, NULL); + free_decode_response(decode); + if (ret < 0) { + if (errmsg != NULL) { + snprintf(errmsg, errlen, "RTSP aggregate RtpInfo response failure"); + } + player_debug_message("rtsp aggregate rtpinfo failed"); + return (-1); + } + } + + while (p != NULL) { + ret = p->do_play(start_time, errmsg, errlen); + if (ret != 0) return (ret); + p = p->get_next(); + } + return (0); +} + +/* + * pause_all_media - do a spin loop until the sync thread indicates it's + * paused. + */ +int CPlayerSession::pause_all_media (void) +{ + int ret; + CPlayerMedia *p; + m_session_state = SESSION_PAUSED; + if (session_control_is_aggregate()) { + rtsp_command_t cmd; + rtsp_decode_t *decode; + + memset(&cmd, 0, sizeof(rtsp_command_t)); + if (rtsp_send_aggregate_pause(m_rtsp_client, + m_sdp_info->control_string, + &cmd, + &decode) != 0) { + player_debug_message("RTSP aggregate pause command failed"); + free_decode_response(decode); + return (-1); + } + free_decode_response(decode); + } + p = m_my_media; + while (p != NULL) { + ret = p->do_pause(); + if (ret != 0) return (ret); + p = p->get_next(); + } + m_sync_pause_done = 0; + send_sync_thread_a_message(MSG_PAUSE_SESSION); +#ifndef NEED_SDL_VIDEO_IN_MAIN_THREAD + do { +#endif + SDL_Delay(100); +#ifndef NEED_SDL_VIDEO_IN_MAIN_THREAD + } while (m_sync_pause_done == 0); +#endif + m_paused = 1; + return (0); +} +void CPlayerSession::add_media (CPlayerMedia *m) +{ + CPlayerMedia *p; + if (m_my_media == NULL) { + m_my_media = m; + } else { + p = m_my_media; + while (p->get_next() != NULL) { + if (p == m) return; + p = p->get_next(); + } + p->set_next(m); + } +} + +int CPlayerSession::session_has_audio (void) +{ + CPlayerMedia *p; + p = m_my_media; + while (p != NULL) { + if (p->is_video() == FALSE) { + return (1); + } + p = p->get_next(); + } + return (0); +} + +int CPlayerSession::session_has_video (void) +{ + CPlayerMedia *p; + p = m_my_media; + while (p != NULL) { + if (p->is_video() != FALSE) { + return (1); + } + p = p->get_next(); + } + return (0); +} + +void CPlayerSession::set_audio_volume (int volume) +{ + m_audio_volume = volume; + if (m_audio_sync) { + m_audio_sync->set_volume(m_audio_volume); + } +} + +double CPlayerSession::get_max_time (void) +{ + CPlayerMedia *p; + double max = 0.0; + p = m_my_media; + while (p != NULL) { + double temp = p->get_max_playtime(); + if (temp > max) max = temp; + p = p->get_next(); + } + return (max); +} + +/* + * Matches a url with the corresponding media. + * Return the media, or NULL if no match. + */ +CPlayerMedia *CPlayerSession::rtsp_url_to_media (const char *url) +{ + CPlayerMedia *p = m_my_media; + while (p != NULL) { + rtsp_session_t *session = p->get_rtsp_session(); + if (rtsp_is_url_my_stream(session, url, m_content_base, + m_session_name) == 1) + return p; + p = p->get_next(); + } + return (NULL); +} + +int CPlayerSession::set_session_desc (int line, const char *desc) +{ + if (line >= SESSION_DESC_COUNT) { + return -1; + } + if (m_session_desc[line] != NULL) free((void *)m_session_desc[line]); + m_session_desc[line] = strdup(desc); + if (m_session_desc[line] == NULL) + return -1; + return (0); +} + +const char *CPlayerSession::get_session_desc (int line) +{ + return m_session_desc[line]; +} +/* + * audio_is_ready - when the audio indicates that it's ready, it will + * send a latency number, and a play time + */ +void CPlayerSession::audio_is_ready (uint64_t latency, uint64_t time) +{ + m_start = get_time_of_day(); + sync_message(LOG_DEBUG, "Aisready "U64, m_start); + m_start -= time; + m_latency = latency; + if (latency != 0) { + m_clock_wrapped = -1; + } + sync_message(LOG_DEBUG, "Audio is ready "U64" - latency "U64, time, latency); + sync_message(LOG_DEBUG, "m_start is "X64, m_start); + m_waiting_for_audio = 0; + SDL_SemPost(m_sync_sem); +} + +void CPlayerSession::adjust_start_time (int64_t time) +{ + m_start -= time; + m_clock_wrapped = -1; +#if 0 + sync_message(LOG_INFO, "Adjusting start time "LLD " to " LLU, time, + get_current_time()); +#endif + SDL_SemPost(m_sync_sem); +} + +/* + * get_current_time. Gets the time of day, subtracts off the start time + * to get the current play time. + */ +uint64_t CPlayerSession::get_current_time (void) +{ + uint64_t current_time; + + if (m_waiting_for_audio != 0) { + return 0; + } + current_time = get_time_of_day(); +#if 0 + sync_message(LOG_DEBUG, "current time %llx m_start %llx", + current_time, m_start); + if (current_time < m_start) { + if (m_clock_wrapped == -1) { + return (0); + } else { + m_clock_wrapped = 1; + } + } else{ + if (m_clock_wrapped > 0) { + uint64_t temp; + temp = 1; + temp <<= 32; + temp /= 1000; + current_time += temp; + } else { + m_clock_wrapped = 0; + } + } +#endif + //if (current_time < m_start) return 0; + m_current_time = current_time - m_start; + if (m_current_time >= m_latency) m_current_time -= m_latency; + return(m_current_time); +} + +void CPlayerSession::syncronize_rtp_bytestreams (rtcp_sync_t *sync) +{ + if (sync != NULL) { + m_audio_rtcp_sync = *sync; + m_have_audio_rtcp_sync = true; + } else { + if (!m_have_audio_rtcp_sync) + return; + } + CPlayerMedia *mptr = m_my_media; + while (mptr != NULL) { + if (mptr->is_video()) { + mptr->syncronize_rtp_bytestreams(&m_audio_rtcp_sync); + } + mptr = mptr->get_next(); + } +} + +int CPlayerSession::process_msg_queue (int state) +{ + CMsg *newmsg; + while ((newmsg = m_sync_thread_msg_queue.get_message()) != NULL) { +#ifdef DEBUG_SYNC_MSGS + sync_message(LOG_DEBUG, "Sync thread msg %d", newmsg->get_value()); +#endif + switch (newmsg->get_value()) { + case MSG_PAUSE_SESSION: + state = SYNC_STATE_PAUSED; + break; + case MSG_START_SESSION: + state = SYNC_STATE_WAIT_SYNC; + break; + case MSG_STOP_THREAD: + state = SYNC_STATE_EXIT; + break; + default: + sync_message(LOG_ERR, "Sync thread received message %d", + newmsg->get_value()); + break; + } + delete newmsg; + newmsg = NULL; + } + return (state); +} + +/*************************************************************************** + * Sync thread state handlers + ***************************************************************************/ + +/* + * sync_thread_init - wait until all the sync routines are initialized. + */ +int CPlayerSession::sync_thread_init (void) +{ + int ret = 1; + for (CPlayerMedia *mptr = m_my_media; + mptr != NULL && ret == 1; + mptr = mptr->get_next()) { + if (mptr->is_video()) { + ret = m_video_sync->initialize_video(m_session_name); + } else { + ret = m_audio_sync->initialize_audio(m_video_sync != NULL); + } + } + if (ret == -1) { + sync_message(LOG_CRIT, "Fatal error while initializing hardware"); + if (m_video_sync != NULL) { + m_video_sync->flush_sync_buffers(); + } + if (m_audio_sync != NULL) { + m_audio_sync->flush_sync_buffers(); + } + m_master_msg_queue->send_message(MSG_RECEIVED_QUIT, + NULL, + 0, + m_master_msg_queue_sem); + m_hardware_error = 1; + return (SYNC_STATE_DONE); + } + + if (ret == 1) { + return (SYNC_STATE_WAIT_SYNC); + } + CMsg *newmsg; + + newmsg = m_sync_thread_msg_queue.get_message(); + if (newmsg != NULL) { + int value = newmsg->get_value(); + delete newmsg; + + if (value == MSG_STOP_THREAD) { + return (SYNC_STATE_EXIT); + } + if (value == MSG_PAUSE_SESSION) { + m_sync_pause_done = 1; + } + } + + SDL_Delay(100); + + return (SYNC_STATE_INIT); +} + +/* + * sync_thread_wait_sync - wait until all decoding threads have put + * data into the sync classes. + */ +int CPlayerSession::sync_thread_wait_sync (void) +{ + int state; + + state = process_msg_queue(SYNC_STATE_WAIT_SYNC); + if (state == SYNC_STATE_WAIT_SYNC) { + + // We're not synced. See if the video is ready, and if the audio + // is ready. Then start them going... + int vsynced = 1, asynced = 1; + uint64_t astart, vstart; + + if (m_video_sync) { + vsynced = m_video_sync->is_video_ready(vstart); + } + + if (m_audio_sync) { + asynced = m_audio_sync->is_audio_ready(astart); + } + if (vsynced == 1 && asynced == 1) { + /* + * Audio and video are synced. + */ + if (m_audio_sync) { + /* + * If we have audio, we use that for syncing. Start it up + */ + m_first_time_played = astart; + m_current_time = astart; + sync_message(LOG_DEBUG, "Astart is %llu", astart); + m_waiting_for_audio = 1; + state = SYNC_STATE_WAIT_AUDIO; + m_audio_sync->play_audio(); + } else if (m_video_sync) { + /* + * Video only - set up the start time based on the video time + * returned + */ + m_first_time_played = vstart; + m_current_time = vstart; + m_waiting_for_audio = 0; + m_start = get_time_of_day(); + m_start -= m_current_time; + state = SYNC_STATE_PLAYING; + } + sync_message(LOG_DEBUG, + "Resynced at time "U64 " "U64, m_current_time, vstart); + } else { + SDL_Delay(10); + } + } + return (state); +} + +/* + * sync_thread_wait_audio - wait until the audio thread starts and signals + * us. + */ +int CPlayerSession::sync_thread_wait_audio (void) +{ + int state; + + state = process_msg_queue(SYNC_STATE_WAIT_AUDIO); + if (state == SYNC_STATE_WAIT_AUDIO) { + if (m_waiting_for_audio != 0) { + SDL_SemWaitTimeout(m_sync_sem, 10); + } else { + // make sure we set the current time + get_current_time(); + sync_message(LOG_DEBUG, "Current time is %llu", m_current_time); + return (SYNC_STATE_PLAYING); + } + } + return (state); +} + +/* + * sync_thread_playing - do the work of displaying the video and making + * sure we're in sync. + */ +int CPlayerSession::sync_thread_playing (void) +{ + int state; + uint64_t audio_resync_time = 0; + int64_t video_status = 0; + int have_audio_eof = 0, have_video_eof = 0; + + state = process_msg_queue(SYNC_STATE_PLAYING); + if (state == SYNC_STATE_PLAYING) { + get_current_time(); + if (m_audio_sync) { + audio_resync_time = m_audio_sync->check_audio_sync(m_current_time, + have_audio_eof); + } + if (m_video_sync) { + video_status = m_video_sync->play_video_at(m_current_time, + have_video_eof); + } + + int delay = 9; + int wait_for_signal = 0; + + if (m_video_sync && m_audio_sync) { + if (have_video_eof && have_audio_eof) { + return (SYNC_STATE_DONE); + } + if (video_status > 0 || audio_resync_time != 0) { + if (audio_resync_time != 0) { + int64_t diff = audio_resync_time - m_current_time; + delay = (int)MIN(diff, video_status); + } else { + if (video_status < 9) { + delay = 0; + } + } + if (delay < 9) { + wait_for_signal = 0; + } else { + wait_for_signal = 1; + } + } else { + wait_for_signal = 0; + } + } else if (m_video_sync) { + if (have_video_eof == 1) { + return (SYNC_STATE_DONE); + } + if (video_status >= 9) { + wait_for_signal = 1; + delay = (int)video_status; + } else { + wait_for_signal = 0; + } + } else { + // audio only + if (have_audio_eof == 1) { + return (SYNC_STATE_DONE); + } + if (audio_resync_time != 0) { + if (audio_resync_time - m_current_time > 10) { + wait_for_signal = 1; + delay = (int)(audio_resync_time - m_current_time); + } else { + wait_for_signal = 0; + } + } else { + wait_for_signal = 1; + } + } + //player_debug_message("w %d d %d", wait_for_signal, delay); + if (wait_for_signal) { + if (delay > 9) { + delay = 9; + } + //player_debug_message("sw %d", delay); + + SDL_SemWaitTimeout(m_sync_sem, delay); + } + } + return (state); +} + +/* + * sync_thread_pause - wait for messages to continue or finish + */ +int CPlayerSession::sync_thread_paused (void) +{ + int state; + state = process_msg_queue(SYNC_STATE_PAUSED); + if (state == SYNC_STATE_PAUSED) { + SDL_SemWaitTimeout(m_sync_sem, 10); + } + return (state); +} + +/* + * sync_thread_pause - wait for messages to exit, pretty much. + */ +int CPlayerSession::sync_thread_done (void) +{ + int state; + state = process_msg_queue(SYNC_STATE_DONE); + if (state == SYNC_STATE_DONE) { + SDL_SemWaitTimeout(m_sync_sem, 10); + } + return (state); +} + +/* + * sync_thread - call the correct handler, and wait for the state changes. + * Each handler should only return the new state... + */ +int CPlayerSession::sync_thread (int state) +{ + int newstate = 0; + switch (state) { + case SYNC_STATE_INIT: + m_session_state = SESSION_BUFFERING; + newstate = sync_thread_init(); + break; + case SYNC_STATE_WAIT_SYNC: + newstate = sync_thread_wait_sync(); + break; + case SYNC_STATE_WAIT_AUDIO: + newstate = sync_thread_wait_audio(); + break; + case SYNC_STATE_PLAYING: + newstate = sync_thread_playing(); + break; + case SYNC_STATE_PAUSED: + newstate = sync_thread_paused(); + break; + case SYNC_STATE_DONE: + newstate = sync_thread_done(); + break; + } +#ifdef DEBUG_SYNC_STATE + if (state != newstate) + sync_message(LOG_INFO, "sync changed state %s to %s", + sync_state[state], sync_state[newstate]); +#endif + if (state != newstate) { + state = newstate; + switch (state) { + case SYNC_STATE_WAIT_SYNC: + m_session_state = SESSION_BUFFERING; + break; + case SYNC_STATE_WAIT_AUDIO: + break; + case SYNC_STATE_PLAYING: + m_session_state = SESSION_PLAYING; + break; + case SYNC_STATE_PAUSED: + if (m_video_sync != NULL) + m_video_sync->flush_sync_buffers(); + if (m_audio_sync != NULL) + m_audio_sync->flush_sync_buffers(); + m_session_state = SESSION_PAUSED; + m_sync_pause_done = 1; + break; + case SYNC_STATE_DONE: + m_master_msg_queue->send_message(MSG_SESSION_FINISHED, + NULL, + 0, + m_master_msg_queue_sem); + m_session_state = SESSION_DONE; + break; + case SYNC_STATE_EXIT: + if (m_video_sync != NULL) + m_video_sync->flush_sync_buffers(); + if (m_audio_sync != NULL) + m_audio_sync->flush_sync_buffers(); + break; + } + } + return (state); +} + +int pdp_sync_thread (void *data) +{ + CPlayerSession *p; + int state = SYNC_STATE_INIT; + p = (CPlayerSession *)data; + do { + state = p->sync_thread(state); + } while (state != SYNC_STATE_EXIT); + return (0); +} diff --git a/modules/pdp_mp4player~.cpp b/modules/pdp_mp4player~.cpp new file mode 100644 index 0000000..613f673 --- /dev/null +++ b/modules/pdp_mp4player~.cpp @@ -0,0 +1,384 @@ +/* + * PiDiP module. + * Copyright (c) by Yves Degoyon (ydegoyon@free.fr) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +/* This object is a quicktime stream picker object + * A lot of this object code is inspired by the code from mpeg4ip + * Copyright (c) 2000, 2001, 2002 Dave Mackie, Bill May & others + * The rest is written by Yves Degoyon ( ydegoyon@free.fr ) + */ + + +#include "pdp_mp4player~.h" + +static char *pdp_mp4player_version = "pdp_mp4player~: version 0.1, a mpeg4ip stream decoder ( ydegoyon@free.fr)."; + +#ifdef __cplusplus +extern "C" +{ +#endif + +static void pdp_mp4player_audio(t_pdp_mp4player *x, t_floatarg faudio ) +{ + if ( ( faudio == 0. ) || ( faudio == 1. ) ) + { + x->x_audio = (int)faudio; + } +} + +static void pdp_mp4player_overtcp(t_pdp_mp4player *x, t_floatarg fovertcp ) +{ + if ( ( fovertcp == 0. ) || ( fovertcp == 1. ) ) + { + x->x_rtpovertcp = (t_int)fovertcp; + config.set_config_value(CONFIG_USE_RTP_OVER_RTSP, x->x_rtpovertcp); + if ( x->x_rtpovertcp ) + { + post("pdp_mp4player~ : using rtp over rtsp (tcp)" ); + } + else + { + post("pdp_mp4player~ : using rtp mode (udp)" ); + } + } +} + +static void pdp_mp4player_priority(t_pdp_mp4player *x, t_floatarg fpriority ) +{ + if ( ( x->x_priority >= MIN_PRIORITY ) && ( x->x_priority <= MAX_PRIORITY ) ) + { + x->x_priority = (int)fpriority; + } +} + +static void pdp_mp4player_vwidth(t_pdp_mp4player *x, t_floatarg fWidth ) +{ + if ( ( (t_int) fWidth <= 0 ) ) + { + post("pdp_mp4player~ : wrong width : %d", fWidth ); + return; + } + + post( "pdp_mp4player~ : setting width : %d", (t_int) fWidth ); + config.set_config_value( CONFIG_VIDEO_RAW_WIDTH, (t_int) fWidth ); + +} + +static void pdp_mp4player_vheight(t_pdp_mp4player *x, t_floatarg fHeight ) +{ + if ( ( (t_int) fHeight <= 0 ) ) + { + post("pdp_mp4player~ : wrong height : %d", fHeight ); + return; + } + + post( "pdp_mp4player~ : setting height : %d", (t_int) fHeight ); + config.set_config_value( CONFIG_VIDEO_RAW_HEIGHT, (t_int) fHeight ); + +} + +static void pdp_mp4player_disconnect(t_pdp_mp4player *x) +{ + if (!x->x_streaming) + { + post("pdp_mp4player~ : close request but no stream is played ... ignored" ); + return; + } + + x->x_streaming = 0; + + outlet_float( x->x_outlet_streaming, x->x_streaming ); + x->x_nbframes = 0; + outlet_float( x->x_outlet_nbframes, x->x_nbframes ); + + post( "pdp_mp4player~ : deleting session" ); + delete x->x_psession; + post( "pdp_mp4player~ : deleting semaphore" ); + SDL_DestroySemaphore(x->x_psem); +} + +static void *pdp_mp4player_decode(void *tdata) +{ + t_pdp_mp4player *x = (t_pdp_mp4player*)tdata; + struct sched_param schedprio; + t_int pmin, pmax; + struct timespec twait, mwait; + + twait.tv_sec = 0; + twait.tv_nsec = 10000000; // 10 ms + + schedprio.sched_priority = 0; + if ( sched_setscheduler(0, SCHED_OTHER, &schedprio) == -1) + { + post("pdp_mp4player~ : couldn't set scheduler for decoding thread.\n"); + } + if ( setpriority( PRIO_PROCESS, 0, x->x_priority ) < 0 ) + { + post("pdp_mp4player~ : couldn't set priority to %d for decoding thread.\n", x->x_priority ); + } + else + { + post("pdp_mp4player~ : priority set to %d for thread %d.\n", x->x_priority, x->x_decodechild ); + } + + while ( x->x_streaming ) + { + x->x_decodingstate = x->x_psession->sync_thread(x->x_decodingstate); + nanosleep( &twait, NULL ); // nothing to read, just wait + } + + post( "pdp_mp4player~ : decoding thread %d exiting....", x->x_decodechild ); + x->x_decodechild = 0; + pthread_exit(NULL); +} + + +static void pdp_mp4player_connect(t_pdp_mp4player *x, t_symbol *s) +{ + t_int ret, i; + char buffer[1024]; + char errmsg[512]; + pthread_attr_t decode_child_attr; + + if ( x->x_streaming ) + { + post("pdp_mp4player~ : connection request but a connection is pending ... disconnecting" ); + pdp_mp4player_disconnect(x); + } + + if ( x->x_url ) free( x->x_url ); + x->x_url = (char*) malloc( strlen( s->s_name ) + 1 ); + strcpy( x->x_url, s->s_name ); + + x->x_psem = SDL_CreateSemaphore(0); + snprintf(buffer, sizeof(buffer), "pdp_mp4player~ - %s", x->x_url); + x->x_psession = new CPlayerSession(&x->x_queue, x->x_psem, buffer, x); + if (x->x_psession == NULL) + { + post("pdp_mp4player~ : FATAL : could not create session" ); + return; + } + + ret = parse_name_for_session(x->x_psession, x->x_url, errmsg, sizeof(errmsg), NULL); + if (ret < 0) + { + post("pdp_mp4player~ : FATAL : wrong url : %s : reason : %s", x->x_url, errmsg ); + delete x->x_psession; + return; + } + + if (ret > 0) + { + post("pdp_mp4player~ : %s", errmsg ); + } + + x->x_psession->set_up_sync_thread(); + + if (x->x_psession->play_all_media(TRUE) != 0) { + post("pdp_mp4player~ : FATAL : couldn't play all medias" ); + delete x->x_psession; + return; + } + + // launch decoding thread + if ( pthread_attr_init( &decode_child_attr ) < 0 ) + { + post( "pdp_mp4player~ : could not launch decoding thread" ); + perror( "pthread_attr_init" ); + return; + } + if ( pthread_create( &x->x_decodechild, &decode_child_attr, pdp_mp4player_decode, x ) < 0 ) + { + post( "pdp_mp4player~ : could not launch decoding thread" ); + perror( "pthread_create" ); + return; + } + + post("pdp_mp4player~ : session started" ); + x->x_streaming = 1; + + return; +} + + /* decode the stream to fill up buffers */ +static t_int *pdp_mp4player_perform(t_int *w) +{ + t_float *out1 = (t_float *)(w[1]); // left audio inlet + t_float *out2 = (t_float *)(w[2]); // right audio inlet + t_pdp_mp4player *x = (t_pdp_mp4player *)(w[3]); + int n = (int)(w[4]); // number of samples + short sampleL, sampleR; + struct timeval etime; + t_int sn; + + // just read the buffer + if ( x->x_audioon ) + { + sn=0; + n=n*DEFAULT_CHANNELS; + while (n--) + { + sampleL=x->x_audio_in[ sn++ ]; + *(out1) = ((t_float)sampleL)/32768.0; + if ( DEFAULT_CHANNELS == 1 ) + { + *(out2) = *(out1); + } + if ( DEFAULT_CHANNELS == 2 ) + { + sampleR=x->x_audio_in[ sn++ ]; + *(out2) = ((t_float)sampleR)/32768.0; + } + out1++; + out2++; + } + x->x_audioin_position-=sn; + memcpy( &x->x_audio_in[0], &x->x_audio_in[sn], 4*MAX_AUDIO_PACKET_SIZE-sn ); + // post( "pdp_mp4player~ : audio in position : %d", x->x_audioin_position ); + if ( x->x_audioin_position <= sn ) + { + x->x_audioon = 0; + // post( "pdp_mp4player~ : audio off" ); + } + } + else + { + // post("pdp_mp4player~ : no available audio" ); + while (n--) + { + *(out1++) = 0.0; + *(out2++) = 0.0; + } + } + + // check if the framerate has been exceeded + if ( gettimeofday(&etime, NULL) == -1) + { + post("pdp_mp4player~ : could not read time" ); + } + if ( etime.tv_sec != x->x_cursec ) + { + x->x_cursec = etime.tv_sec; + outlet_float( x->x_outlet_framerate, x->x_secondcount ); + x->x_secondcount = 0; + } + + if ( x->x_newpicture ) + { + pdp_packet_pass_if_valid(x->x_pdp_out, &x->x_packet0); + + // update streaming status + outlet_float( x->x_outlet_streaming, x->x_streaming ); + x->x_nbframes++; + x->x_secondcount++; + outlet_float( x->x_outlet_nbframes, x->x_nbframes ); + } + + return (w+5); +} + +static void pdp_mp4player_dsp(t_pdp_mp4player *x, t_signal **sp) +{ + dsp_add(pdp_mp4player_perform, 4, sp[0]->s_vec, sp[1]->s_vec, x, sp[0]->s_n); +} + +static void pdp_mp4player_free(t_pdp_mp4player *x) +{ + int i; + + if ( x->x_streaming ) + { + pdp_mp4player_disconnect(x); + } + post( "pdp_mp4player~ : freeing object" ); + pdp_packet_mark_unused(x->x_packet0); + + // remove invalid global ports + close_plugins(); +} + +t_class *pdp_mp4player_class; + +void *pdp_mp4player_new(void) +{ + int i; + + t_pdp_mp4player *x = (t_pdp_mp4player *)pd_new(pdp_mp4player_class); + + x->x_pdp_out = outlet_new(&x->x_obj, &s_anything); + + x->x_outlet_left = outlet_new(&x->x_obj, &s_signal); + x->x_outlet_right = outlet_new(&x->x_obj, &s_signal); + + x->x_outlet_streaming = outlet_new(&x->x_obj, &s_float); + x->x_outlet_nbframes = outlet_new(&x->x_obj, &s_float); + x->x_outlet_framerate = outlet_new(&x->x_obj, &s_float); + + x->x_packet0 = -1; + x->x_nbframes = 0; + x->x_cursec = 0; + x->x_secondcount = 0; + x->x_audioin_position = 0; + x->x_priority = DEFAULT_PRIORITY; + x->x_decodechild = 0; + x->x_newpicture = 0; + + memset( &x->x_audio_buf[0], 0x0, 4*MAX_AUDIO_PACKET_SIZE*sizeof(short) ); + memset( &x->x_audio_in[0], 0x0, 4*MAX_AUDIO_PACKET_SIZE*sizeof(short) ); + + // initialize mpeg4hippies + initialize_plugins(); + config.read_config_file(); + rtsp_set_error_func(player_library_message); + rtsp_set_loglevel(config.get_config_value(CONFIG_RTSP_DEBUG)); + rtp_set_error_msg_func(player_library_message); + rtp_set_loglevel(config.get_config_value(CONFIG_RTP_DEBUG)); + sdp_set_error_func(player_library_message); + sdp_set_loglevel(config.get_config_value(CONFIG_SDP_DEBUG)); + http_set_error_func(player_library_message); + http_set_loglevel(config.get_config_value(CONFIG_HTTP_DEBUG)); + + x->x_rtpovertcp = 0; + config.set_config_value(CONFIG_USE_RTP_OVER_RTSP, x->x_rtpovertcp); + + return (void *)x; +} + + +void pdp_mp4player_tilde_setup(void) +{ + // post( pdp_mp4player_version ); + pdp_mp4player_class = class_new(gensym("pdp_mp4player~"), (t_newmethod)pdp_mp4player_new, + (t_method)pdp_mp4player_free, sizeof(t_pdp_mp4player), 0, A_NULL); + + class_addmethod(pdp_mp4player_class, (t_method)pdp_mp4player_dsp, gensym("dsp"), A_NULL); + class_addmethod(pdp_mp4player_class, (t_method)pdp_mp4player_connect, gensym("connect"), A_SYMBOL, A_NULL); + class_addmethod(pdp_mp4player_class, (t_method)pdp_mp4player_disconnect, gensym("disconnect"), A_NULL); + class_addmethod(pdp_mp4player_class, (t_method)pdp_mp4player_audio, gensym("audio"), A_FLOAT, A_NULL); + class_addmethod(pdp_mp4player_class, (t_method)pdp_mp4player_overtcp, gensym("overtcp"), A_FLOAT, A_NULL); + class_addmethod(pdp_mp4player_class, (t_method)pdp_mp4player_priority, gensym("priority"), A_FLOAT, A_NULL); + class_addmethod(pdp_mp4player_class, (t_method)pdp_mp4player_vwidth, gensym("vwidth"), A_DEFFLOAT, A_NULL); + class_addmethod(pdp_mp4player_class, (t_method)pdp_mp4player_vheight, gensym("vheight"), A_DEFFLOAT, A_NULL); + class_sethelpsymbol( pdp_mp4player_class, gensym("pdp_mp4player~.pd") ); + +} + +#ifdef __cplusplus +} +#endif diff --git a/modules/pdp_mp4videosource.cpp b/modules/pdp_mp4videosource.cpp new file mode 100644 index 0000000..5e5ef91 --- /dev/null +++ b/modules/pdp_mp4videosource.cpp @@ -0,0 +1,185 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is MPEG4IP. + * + * The Initial Developer of the Original Code is Cisco Systems Inc. + * Portions created by Cisco Systems Inc. are + * Copyright (C) Cisco Systems Inc. 2000, 2001. All Rights Reserved. + * + * Contributor(s): + * Dave Mackie dmackie@cisco.com + * Bill May wmay@cisco.com + * + * Adapted for PD/PDP by Yves Degoyon (ydegoyon@free.fr) + */ + +#ifndef debug_message +#define debug_message post +#endif + +#include <sys/mman.h> + +#include "m_pd.h" +#include "pdp_mp4videosource.h" +#include "video_util_rgb.h" + +//#define DEBUG_TIMESTAMPS 1 + +int CPDPVideoSource::ThreadMain(void) +{ + // just a stub, the threaded mode is not used ( never called ) + return 0; +} + +void CPDPVideoSource::DoStart(void) +{ + if (m_source) return; + if (!Init()) return; + m_source = true; +} + +void CPDPVideoSource::DoStop(void) +{ + if (!m_source) return; + DoStopVideo(); + m_source = false; +} + +bool CPDPVideoSource::Init(void) +{ + + if (m_pConfig->GetIntegerValue(CONFIG_VIDEO_SIGNAL) == VIDEO_SIGNAL_NTSC) + { + m_videoSrcFrameRate = VIDEO_NTSC_FRAME_RATE; + } + else + { + m_videoSrcFrameRate = VIDEO_PAL_FRAME_RATE; + } + + m_videoMbuf.frames = 2; + m_cacheTimestamp = false; + m_videoFrameMap = (struct video_mmap*) malloc(m_videoMbuf.frames * sizeof(struct video_mmap)); + if ( !m_videoFrameMap ) + { + post("pdp_mp4live~ : video source init : failed to allocate enough memory"); + return false; + } + + m_videoFrameMapFrame = (uint64_t *)malloc(m_videoMbuf.frames * sizeof(uint64_t)); + m_videoFrameMapTimestamp = (uint64_t *)malloc(m_videoMbuf.frames * sizeof(Timestamp)); + m_captureHead = 0; + m_encodeHead = -1; + + m_videoFrames = 0; + m_videoSrcFrameDuration = (Duration)(((float)TimestampTicks / m_videoSrcFrameRate) + 0.5); + for (int i = 0; i < m_videoMbuf.frames; i++) + { + // initialize frame map + m_videoFrameMap[i].frame = i; + m_videoFrameMap[i].width = m_pConfig->GetIntegerValue(CONFIG_VIDEO_RAW_WIDTH); + m_videoFrameMap[i].height = m_pConfig->GetIntegerValue(CONFIG_VIDEO_RAW_HEIGHT); + m_videoFrameMap[i].format = VIDEO_PALETTE_YUV420P; + + if (i == 0) + { + m_videoCaptureStartTimestamp = GetTimestamp(); + } + m_lastVideoFrameMapFrameLoaded = m_videoFrameMapFrame[i] = i; + m_lastVideoFrameMapTimestampLoaded = + m_videoFrameMapTimestamp[i] = CalculateVideoTimestampFromFrames(i); + } + + m_pConfig->CalculateVideoFrameSize(); + + InitVideo( + (m_pConfig->m_videoNeedRgbToYuv ? + RGBVIDEOFRAME : + YUVVIDEOFRAME), + true); + + SetVideoSrcSize( + m_pConfig->GetIntegerValue(CONFIG_VIDEO_RAW_WIDTH), + m_pConfig->GetIntegerValue(CONFIG_VIDEO_RAW_HEIGHT), + m_pConfig->GetIntegerValue(CONFIG_VIDEO_RAW_WIDTH), + true); + + m_maxPasses = (u_int8_t)(m_videoSrcFrameRate + 0.5); + + return true; +} + +int8_t CPDPVideoSource::StartTimeStamp(Timestamp &frameTimestamp) +{ + if (m_cacheTimestamp) + frameTimestamp = m_videoFrameMapTimestamp[m_captureHead]; + else + frameTimestamp = GetTimestamp(); + + int8_t capturedFrame = m_captureHead; + m_captureHead = (m_captureHead + 1) % m_videoMbuf.frames; + + return capturedFrame; +} + +bool CPDPVideoSource::EndTimeStamp(int8_t frameNumber) +{ + Timestamp calc = GetTimestamp(); + + if (calc > m_videoSrcFrameDuration + m_lastVideoFrameMapTimestampLoaded) { +#ifdef DEBUG_TIMESTAMPS + debug_message("pdp_mp4live~ : video frame delay past end of buffer - time is %llu should be %llu", + calc, + m_videoSrcFrameDuration + m_lastVideoFrameMapTimestampLoaded); +#endif + m_videoCaptureStartTimestamp = calc; + m_videoFrameMapFrame[frameNumber] = 0; + m_videoFrameMapTimestamp[frameNumber] = calc; + } else { + m_videoFrameMapFrame[frameNumber] = m_lastVideoFrameMapFrameLoaded + 1; + m_videoFrameMapTimestamp[frameNumber] = + CalculateVideoTimestampFromFrames(m_videoFrameMapFrame[frameNumber]); + } + + m_lastVideoFrameMapFrameLoaded = m_videoFrameMapFrame[frameNumber]; + m_lastVideoFrameMapTimestampLoaded = m_videoFrameMapTimestamp[frameNumber]; + return true; +} + +void CPDPVideoSource::ProcessVideo(u_int8_t *pY, u_int8_t *pU, u_int8_t *pV) +{ + // for efficiency, process ~1 second before returning to check for commands + Timestamp frameTimestamp; + for (int pass = 0; pass < m_maxPasses; pass++) { + + // get next frame from video capture device + m_encodeHead = StartTimeStamp(frameTimestamp); + if (m_encodeHead == -1) { + continue; + } + + ProcessVideoYUVFrame( + pY, + pU, + pV, + m_videoSrcWidth, + m_videoSrcWidth >> 1, + frameTimestamp); + + // release video frame buffer back to video capture device + if (EndTimeStamp(m_encodeHead)) { + m_encodeHead = (m_encodeHead + 1) % m_videoMbuf.frames; + } else { + debug_message("pdp_mp4live~ : couldn't release capture buffer!"); + } + } +} diff --git a/modules/pdp_mp4videosync.cpp b/modules/pdp_mp4videosync.cpp new file mode 100644 index 0000000..b7fca2e --- /dev/null +++ b/modules/pdp_mp4videosync.cpp @@ -0,0 +1,248 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is MPEG4IP. + * + * The Initial Developer of the Original Code is Cisco Systems Inc. + * Portions created by Cisco Systems Inc. are + * Copyright (C) Cisco Systems Inc. 2000, 2001. All Rights Reserved. + * + * Contributor(s): + * Bill May wmay@cisco.com + * video aspect ratio by: + * Peter Maersk-Moller peter@maersk-moller.net + * + * Adapted to PD/PDP by Yves Degoyon (ydegoyon@free.fr) + */ + +/* + * video.cpp - provides codec to video hardware class + */ + +#include <string.h> +#include "pdp_mp4videosync.h" +#include "pdp_mp4playersession.h" +#include "player_util.h" +#include "m_pd.h" + +#define video_message(loglevel, fmt...) message(loglevel, "videosync", fmt) + +CPDPVideoSync::CPDPVideoSync (CPlayerSession *psptr, t_pdp_mp4player *pdp_father) : CVideoSync(psptr) +{ + char buf[32]; + + m_screen = NULL; + m_image = NULL; + m_video_initialized = 0; + m_config_set = 0; + m_have_data = 0; + m_y_buffer[0] = NULL; + m_u_buffer[0] = NULL; + m_v_buffer[0] = NULL; + m_buffer_filled[0] = 0; + m_play_index = m_fill_index = 0; + m_decode_waiting = 0; + m_dont_fill = 0; + m_paused = 1; + m_behind_frames = 0; + m_total_frames = 0; + m_behind_time = 0; + m_behind_time_max = 0; + m_skipped_render = 0; + m_video_scale = 2; + m_msec_per_frame = 0; + m_consec_skipped = 0; + m_fullscreen = 0; + m_filled_frames = 0; + m_double_width = 0; + m_pixel_width = 0; + m_pixel_height = 0; + m_max_width = 0; + m_max_height = 0; + m_father = pdp_father; +} + +CPDPVideoSync::~CPDPVideoSync (void) +{ + if (m_fullscreen != 0) { + m_fullscreen = 0; + do_video_resize(); + } + if (m_image) { + m_image = NULL; + } + if (m_screen) { + m_screen = NULL; + } + if (m_y_buffer[0] != NULL) { + free(m_y_buffer[0]); + m_y_buffer[0] = NULL; + } + if (m_u_buffer[0] != NULL) { + free(m_u_buffer[0]); + m_u_buffer[0] = NULL; + } + if (m_v_buffer[0] != NULL) { + free(m_v_buffer[0]); + m_v_buffer[0] = NULL; + } +} + +void CPDPVideoSync::config (int w, int h) +{ + m_width = w; + m_height = h; + m_y_buffer[0] = (uint8_t *)malloc(w * h * sizeof(uint8_t)); + m_u_buffer[0] = (uint8_t *)malloc(w/2 * h/2 * sizeof(uint8_t)); + m_v_buffer[0] = (uint8_t *)malloc(w/2 * h/2 * sizeof(uint8_t)); + m_buffer_filled[0] = 0; + m_config_set = 1; + post( "pdp_mp4videosync : configuration done : %dx%d", m_width, m_height ); +} + +int CPDPVideoSync::initialize_video (const char *name) +{ + if (m_video_initialized == 0) { + if (m_config_set) { + int ret; + int video_scale = m_video_scale; + + int w = m_width * video_scale / 2; + if (m_double_width) w *= 2; + int h = m_height * video_scale / 2; + m_video_initialized = 1; + post( "pdp_mp4videosync : video initialized : %dx%d", m_width, m_height ); + return (1); + } else { + return (0); + } + } + return (1); +} + +int CPDPVideoSync::is_video_ready (uint64_t &disptime) +{ + return 1; +} + +void CPDPVideoSync::play_video (void) +{ + +} + +int64_t CPDPVideoSync::play_video_at (uint64_t current_time, int &have_eof) +{ + uint64_t play_this_at; + unsigned int ix; + uint8_t *to, *from; + + post( "pdp_mp4videosync : play video at : %ld", current_time ); + + return (10); +} + +int CPDPVideoSync::get_video_buffer(uint8_t **y, + uint8_t **u, + uint8_t **v) +{ + + post( "pdp_mp4videosync : get video buffer" ); + *y = m_y_buffer[m_fill_index]; + *u = m_u_buffer[m_fill_index]; + *v = m_v_buffer[m_fill_index]; + return (1); +} + +void CPDPVideoSync::filled_video_buffers (uint64_t time) +{ + int ix; + // post( "pdp_mp4videosync : filled video buffer : %ld", time ); + m_psptr->wake_sync_thread(); +} + +void CPDPVideoSync::set_video_frame(const uint8_t *y, + const uint8_t *u, + const uint8_t *v, + int pixelw_y, + int pixelw_uv, + uint64_t time) +{ + // post( "pdp_mp4videosync : set video frame : %dx%d", m_width, m_height ); + m_psptr->wake_sync_thread(); + + // pass the data to the pdp object + m_father->x_newpicture = 1; + return; +} + +void CPDPVideoSync::flush_sync_buffers (void) +{ + post( "pdp_mp4videosync : flush sync buffer" ); +} + +void CPDPVideoSync::flush_decode_buffers (void) +{ + post( "pdp_mp4videosync : flush decode buffer" ); +} + +static void pdp_video_configure (void *ifptr, + int w, + int h, + int format, + double aspect_ratio ) +{ + ((CPDPVideoSync *)ifptr)->config(w, h); +} + +static int pdp_video_get_buffer (void *ifptr, + uint8_t **y, + uint8_t **u, + uint8_t **v) +{ + return (((CPDPVideoSync *)ifptr)->get_video_buffer(y, u, v)); +} + +static void pdp_video_filled_buffer(void *ifptr, uint64_t time) +{ + ((CPDPVideoSync *)ifptr)->filled_video_buffers(time); +} + +static void pdp_video_have_frame (void *ifptr, + const uint8_t *y, + const uint8_t *u, + const uint8_t *v, + int m_pixelw_y, + int m_pixelw_uv, + uint64_t time) +{ + CPDPVideoSync *foo = (CPDPVideoSync *)ifptr; + + foo->set_video_frame(y, u, v, m_pixelw_y, m_pixelw_uv, time); +} + +video_vft_t video_vft = +{ + message, + pdp_video_configure, + pdp_video_get_buffer, + pdp_video_filled_buffer, + pdp_video_have_frame, +}; + +video_vft_t *get_video_vft (void) +{ + return (&video_vft); +} + +CPDPVideoSync *pdp_create_video_sync (CPlayerSession *psptr, t_pdp_mp4player *pdp_father) +{ + return new CPDPVideoSync(psptr, pdp_father); +} diff --git a/modules/pdp_spotlight.c b/modules/pdp_spotlight.c new file mode 100644 index 0000000..e294642 --- /dev/null +++ b/modules/pdp_spotlight.c @@ -0,0 +1,320 @@ +/* + * PiDiP module. + * Copyright (c) by Yves Degoyon (ydegoyon@free.fr) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +/* This object is an adaptation of lens effect from effectv + * Originally written by Fukuchi Kentaro & others + * Pd-fication by Yves Degoyon + */ + + + +#include "pdp.h" +#include "yuv.h" +#include <math.h> + +static char *pdp_spotlight_version = "pdp_spotlight: version 0.1, specially made for cabaret, written by Yves Degoyon (ydegoyon@free.fr)"; + +typedef struct pdp_spotlight_struct +{ + t_object x_obj; + t_float x_f; + + t_outlet *x_outlet0; + t_int x_packet0; + t_int x_packet1; + t_int x_dropped; + t_int x_queue_id; + + t_int x_vwidth; + t_int x_vheight; + t_int x_vsize; + t_int x_cx; // coordinates of lens center + t_int x_cy; // coordinates of lens center + t_int x_ssize; // width of the spotlight + t_float x_strength; // strength of the light (0<=strength<=1) + + t_int x_colorR; // red component of the color + t_int x_colorG; // green component of the color + t_int x_colorB; // blue component of the color + +} t_pdp_spotlight; + +static void pdp_spotlight_ssize(t_pdp_spotlight *x, t_floatarg fssize ) +{ + if ( fssize>=0 ) + { + x->x_ssize = (int)fssize; + } +} + +static void pdp_spotlight_cy(t_pdp_spotlight *x, t_floatarg fcy ) +{ + if ( fcy>0 ) + { + x->x_cy = (int)fcy; + } +} + +static void pdp_spotlight_cx(t_pdp_spotlight *x, t_floatarg fcx ) +{ + if ( fcx>0 ) + { + x->x_cx = (int)fcx; + } +} + +static void pdp_spotlight_r(t_pdp_spotlight *x, t_floatarg fr ) +{ + if ( ( fr >= 0 ) && ( fr <= 255 ) ) + { + x->x_colorR = (int)fr; + } +} + +static void pdp_spotlight_g(t_pdp_spotlight *x, t_floatarg fg ) +{ + if ( ( fg >= 0 ) && ( fg <= 255 ) ) + { + x->x_colorG = (int)fg; + } +} + +static void pdp_spotlight_b(t_pdp_spotlight *x, t_floatarg fb ) +{ + if ( ( fb >= 0 ) && ( fb <= 255 ) ) + { + x->x_colorB = (int)fb; + } +} + +static void pdp_spotlight_strength(t_pdp_spotlight *x, t_floatarg fstrength ) +{ + if ( ( fstrength >= 0.0 ) && ( fstrength <= 1.0 ) ) + { + x->x_strength = fstrength; + } +} + +static void pdp_spotlight_process_yv12(t_pdp_spotlight *x) +{ + t_pdp *header = pdp_packet_header(x->x_packet0); + short int *data = (short int *)pdp_packet_data(x->x_packet0); + t_pdp *newheader = pdp_packet_header(x->x_packet1); + short int *newdata = (short int *)pdp_packet_data(x->x_packet1); + int i; + + short int *poy, *pou, *pov, *pny, *pnu, *pnv; + short int pmx, pMx, pmy, pMy; + int px, py, ray2; + + x->x_vwidth = header->info.image.width; + x->x_vheight = header->info.image.height; + x->x_vsize = x->x_vwidth*x->x_vheight; + + newheader->info.image.encoding = header->info.image.encoding; + newheader->info.image.width = x->x_vwidth; + newheader->info.image.height = x->x_vheight; + + memcpy(newdata, data, (x->x_vsize + (x->x_vsize>>1))<<1); + + poy = data; + pou = data + x->x_vsize; + pov = data + x->x_vsize + (x->x_vsize>>2); + pny = newdata; + pnu = newdata + x->x_vsize; + pnv = newdata + x->x_vsize + (x->x_vsize>>2); + if ( x->x_cy-x->x_ssize < 0 ) + { + pmy=0; + } + else + { + pmy=x->x_cy-x->x_ssize; + } + if ( x->x_cy+x->x_ssize > x->x_vheight ) + { + pMy=x->x_vheight-1; + } + else + { + pMy=x->x_cy+x->x_ssize; + } + if ( x->x_cx-x->x_ssize < 0 ) + { + pmx=0; + } + else + { + pmx=x->x_cx-x->x_ssize; + } + if ( x->x_cx+x->x_ssize > x->x_vwidth ) + { + pMx=x->x_vwidth-1; + } + else + { + pMx=x->x_cx+x->x_ssize; + } + ray2 = pow( x->x_ssize, 2 ); + for (py = pmy; py <= pMy ; py++) + { + for (px = pmx; px <= pMx; px++) + { + if ( ( pow( (px-x->x_cx), 2 ) + pow( (py-x->x_cy), 2 ) ) < ray2 ) + { + *(pny+py*x->x_vwidth+px) = + (t_float)(*(pny+py*x->x_vwidth+px))*(1.-x->x_strength) + + (t_float)(((yuv_RGBtoY( (x->x_colorB << 16) + (x->x_colorG << 8) + x->x_colorR ))<<7)) + *(x->x_strength); + *(pnu+(py>>1)*(x->x_vwidth>>1)+(px>>1)) = + (t_float)(*(pnu+(py>>1)*(x->x_vwidth>>1)+(px>>1)))*(1.-x->x_strength) + + (t_float)(((yuv_RGBtoU( (x->x_colorB << 16) + (x->x_colorG << 8) + + x->x_colorR ))-128)<<8)*(x->x_strength); + *(pnv+(py>>1)*(x->x_vwidth>>1)+(px>>1)) = + (t_float)(*(pnv+(py>>1)*(x->x_vwidth>>1)+(px>>1)))*(1.-x->x_strength) + + (t_float)(((yuv_RGBtoV( (x->x_colorB << 16) + (x->x_colorG << 8) + + x->x_colorR ))-128)<<8)*(x->x_strength); + } + } + } + + return; +} + +static void pdp_spotlight_sendpacket(t_pdp_spotlight *x) +{ + /* release the packet */ + pdp_packet_mark_unused(x->x_packet0); + x->x_packet0 = -1; + + /* unregister and propagate if valid dest packet */ + pdp_packet_pass_if_valid(x->x_outlet0, &x->x_packet1); +} + +static void pdp_spotlight_process(t_pdp_spotlight *x) +{ + int encoding; + t_pdp *header = 0; + + /* check if image data packets are compatible */ + if ( (header = pdp_packet_header(x->x_packet0)) + && (PDP_IMAGE == header->type)){ + + /* pdp_spotlight_process inputs and write into active inlet */ + switch(pdp_packet_header(x->x_packet0)->info.image.encoding){ + + case PDP_IMAGE_YV12: + x->x_packet1 = pdp_packet_clone_rw(x->x_packet0); + pdp_queue_add(x, pdp_spotlight_process_yv12, pdp_spotlight_sendpacket, &x->x_queue_id); + break; + + default: + /* don't know the type, so dont pdp_spotlight_process */ + break; + + } + } +} + +static void pdp_spotlight_input_0(t_pdp_spotlight *x, t_symbol *s, t_floatarg f) +{ + /* if this is a register_ro message or register_rw message, register with packet factory */ + + if (s== gensym("register_rw")) + { + x->x_dropped = pdp_packet_convert_ro_or_drop(&x->x_packet0, (int)f, pdp_gensym("image/YCrCb/*") ); + } + + if ((s == gensym("process")) && (-1 != x->x_packet0) && (!x->x_dropped)) + { + /* add the process method and callback to the process queue */ + pdp_spotlight_process(x); + + } +} + +static void pdp_spotlight_free(t_pdp_spotlight *x) +{ + int i; + + pdp_queue_finish(x->x_queue_id); + pdp_packet_mark_unused(x->x_packet0); +} + +t_class *pdp_spotlight_class; + +void *pdp_spotlight_new(void) +{ + int i; + + t_pdp_spotlight *x = (t_pdp_spotlight *)pd_new(pdp_spotlight_class); + inlet_new(&x->x_obj, &x->x_obj.ob_pd, &s_float, gensym("cx")); + inlet_new(&x->x_obj, &x->x_obj.ob_pd, &s_float, gensym("cy")); + inlet_new(&x->x_obj, &x->x_obj.ob_pd, &s_float, gensym("ssize")); + inlet_new(&x->x_obj, &x->x_obj.ob_pd, &s_float, gensym("r")); + inlet_new(&x->x_obj, &x->x_obj.ob_pd, &s_float, gensym("g")); + inlet_new(&x->x_obj, &x->x_obj.ob_pd, &s_float, gensym("b")); + inlet_new(&x->x_obj, &x->x_obj.ob_pd, &s_float, gensym("strength")); + + x->x_outlet0 = outlet_new(&x->x_obj, &s_anything); + + x->x_packet0 = -1; + x->x_packet1 = -1; + x->x_queue_id = -1; + + x->x_cx = 70; + x->x_cy = 70; + x->x_ssize = 50; + x->x_colorR = 255; + x->x_colorG = 255; + x->x_colorB = 255; + x->x_strength = 0.5; + + return (void *)x; +} + + +#ifdef __cplusplus +extern "C" +{ +#endif + + +void pdp_spotlight_setup(void) +{ +// post( pdp_spotlight_version ); + pdp_spotlight_class = class_new(gensym("pdp_spotlight"), (t_newmethod)pdp_spotlight_new, + (t_method)pdp_spotlight_free, sizeof(t_pdp_spotlight), 0, A_NULL); + + class_addmethod(pdp_spotlight_class, (t_method)pdp_spotlight_input_0, gensym("pdp"), A_SYMBOL, A_DEFFLOAT, A_NULL); + class_addmethod(pdp_spotlight_class, (t_method)pdp_spotlight_cx, gensym("cx"), A_FLOAT, A_NULL); + class_addmethod(pdp_spotlight_class, (t_method)pdp_spotlight_cy, gensym("cy"), A_FLOAT, A_NULL); + class_addmethod(pdp_spotlight_class, (t_method)pdp_spotlight_ssize, gensym("ssize"), A_FLOAT, A_NULL); + class_addmethod(pdp_spotlight_class, (t_method)pdp_spotlight_r, gensym("r"), A_FLOAT, A_NULL); + class_addmethod(pdp_spotlight_class, (t_method)pdp_spotlight_g, gensym("g"), A_FLOAT, A_NULL); + class_addmethod(pdp_spotlight_class, (t_method)pdp_spotlight_b, gensym("b"), A_FLOAT, A_NULL); + class_addmethod(pdp_spotlight_class, (t_method)pdp_spotlight_strength, gensym("strength"), A_FLOAT, A_NULL); + class_sethelpsymbol( pdp_spotlight_class, gensym("pdp_spotlight.pd") ); + +} + +#ifdef __cplusplus +} +#endif diff --git a/patches/pdp_cabaret.pd b/patches/pdp_cabaret.pd new file mode 100644 index 0000000..1f72e30 --- /dev/null +++ b/patches/pdp_cabaret.pd @@ -0,0 +1,119 @@ +#N canvas 275 13 818 664 10; +#X obj 191 19 bng 15 250 50 0 empty empty empty 20 8 0 8 -262144 -1 +-1; +#X msg 46 91 loop \$1; +#X obj 47 69 tgl 15 0 empty empty empty 20 8 0 8 -262144 -1 -1 0 1 +; +#X msg 75 52 open \$1; +#X obj 74 28 openpanel; +#X obj 59 11 bng 15 250 50 0 empty empty empty 20 8 0 8 -262144 -1 +-1; +#X floatatom 239 54 5 0 0 0 - - -; +#X msg 148 20 stop; +#X obj 246 23 hsl 300 15 0 1000 0 0 empty empty empty -2 -6 0 8 -262144 +-1 -1 0 1; +#X obj 180 90 metro 70; +#X floatatom 264 435 5 0 0 0 - - -; +#X floatatom 278 457 5 0 0 0 - - -; +#X floatatom 291 478 5 0 0 0 - - -; +#X obj 175 122 pdp_yqt; +#X obj 313 118 pdp_v4l; +#X obj 322 87 metro 70; +#X obj 367 53 bng 15 250 50 0 empty empty empty 20 8 0 8 -262144 -1 +-1; +#X msg 324 54 stop; +#X msg 409 85 open /dev/video; +#X obj 547 523 tgl 15 0 empty empty empty 0 -6 0 8 -262144 -1 -1 0 +1; +#X obj 547 575 pdp_control; +#X msg 547 548 thread \$1; +#X floatatom 547 636 5 0 0 0 - - -; +#X obj 547 607 route pdp_drop; +#X text 100 619 author : Yves Degoyon ( ydegoyon@free.fr ); +#X obj 416 394 colorgrid colorgrid1 256 0 256 50 0 50 0 1 1 10 10 481 +420; +#X obj 680 393 vsl 8 50 0 1 0 0 empty empty empty 0 -8 0 8 -262144 +-1 -1 0 1; +#X obj 213 200 pdp_shape; +#X obj 258 243 -; +#X floatatom 265 266 5 0 0 0 - - -; +#X text 310 267 Width; +#X obj 370 241 -; +#X floatatom 372 265 5 0 0 0 - - -; +#X text 418 265 Height; +#X obj 221 555 pdp_xv; +#X obj 32 452 route press drag release; +#X msg 54 230 pick \$1 \$2; +#X msg 53 258 detect \$1 \$2; +#X msg 167 420 0; +#X obj 324 321 max; +#X obj 236 331 +; +#X obj 234 356 / 2; +#X obj 279 332 +; +#X obj 277 357 / 2; +#X obj 324 356 / 2; +#X msg 330 196 shape 0; +#X obj 330 173 loadbang; +#X obj 165 397 loadbang; +#X msg 295 542 cursor 1; +#X text 100 605 pdp_cabaret : specially made for pep'; +#X obj 223 515 pdp_spotlight; +#X connect 0 0 9 0; +#X connect 1 0 13 0; +#X connect 2 0 1 0; +#X connect 3 0 13 0; +#X connect 4 0 3 0; +#X connect 5 0 4 0; +#X connect 6 0 9 1; +#X connect 7 0 9 0; +#X connect 8 0 6 0; +#X connect 9 0 13 0; +#X connect 10 0 50 1; +#X connect 11 0 50 2; +#X connect 12 0 50 3; +#X connect 13 0 27 0; +#X connect 14 0 27 0; +#X connect 15 0 14 0; +#X connect 16 0 15 0; +#X connect 17 0 15 0; +#X connect 18 0 14 0; +#X connect 19 0 21 0; +#X connect 20 0 23 0; +#X connect 21 0 20 0; +#X connect 23 0 22 0; +#X connect 25 0 50 4; +#X connect 25 1 50 5; +#X connect 25 2 50 6; +#X connect 26 0 50 7; +#X connect 27 0 48 0; +#X connect 27 0 50 0; +#X connect 27 1 28 1; +#X connect 27 1 40 0; +#X connect 27 2 31 1; +#X connect 27 2 42 0; +#X connect 27 3 28 0; +#X connect 27 3 40 1; +#X connect 27 4 31 0; +#X connect 27 4 42 1; +#X connect 28 0 29 0; +#X connect 29 0 39 0; +#X connect 31 0 32 0; +#X connect 32 0 39 1; +#X connect 34 0 35 0; +#X connect 35 0 36 0; +#X connect 35 0 38 0; +#X connect 35 2 37 0; +#X connect 36 0 27 0; +#X connect 37 0 27 0; +#X connect 38 0 12 0; +#X connect 39 0 44 0; +#X connect 40 0 41 0; +#X connect 41 0 10 0; +#X connect 42 0 43 0; +#X connect 43 0 11 0; +#X connect 44 0 12 0; +#X connect 45 0 27 0; +#X connect 46 0 45 0; +#X connect 47 0 38 0; +#X connect 48 0 34 0; +#X connect 50 0 34 0; |