diff options
author | N.N. <sevyves@users.sourceforge.net> | 2004-03-30 02:59:55 +0000 |
---|---|---|
committer | N.N. <sevyves@users.sourceforge.net> | 2004-03-30 02:59:55 +0000 |
commit | fe5ae89555ead1863420c1a9c389553e75e4527c (patch) | |
tree | dc55fbaeb1c0328bc6033b21ac0d06b9c7425f14 /modules | |
parent | e166e8943388389bb4fc2927aac8758387c42846 (diff) |
New in PiDiP 0.12.13
svn path=/trunk/externals/pidip/; revision=1509
Diffstat (limited to 'modules')
-rw-r--r-- | modules/pdp_ieee1394.c | 483 | ||||
-rw-r--r-- | modules/pdp_mp4audiosource.cpp | 204 | ||||
-rw-r--r-- | modules/pdp_mp4audiosync.cpp | 505 | ||||
-rw-r--r-- | modules/pdp_mp4config.cpp | 136 | ||||
-rw-r--r-- | modules/pdp_mp4live~.cpp | 638 | ||||
-rw-r--r-- | modules/pdp_mp4playermedia.cpp | 2029 | ||||
-rw-r--r-- | modules/pdp_mp4playersession.cpp | 1045 | ||||
-rw-r--r-- | modules/pdp_mp4player~.cpp | 384 | ||||
-rw-r--r-- | modules/pdp_mp4videosource.cpp | 185 | ||||
-rw-r--r-- | modules/pdp_mp4videosync.cpp | 248 | ||||
-rw-r--r-- | modules/pdp_spotlight.c | 320 |
11 files changed, 6177 insertions, 0 deletions
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 |