diff options
Diffstat (limited to 'modules/pdp_live~.c')
-rw-r--r-- | modules/pdp_live~.c | 324 |
1 files changed, 209 insertions, 115 deletions
diff --git a/modules/pdp_live~.c b/modules/pdp_live~.c index cae6a65..c403a15 100644 --- a/modules/pdp_live~.c +++ b/modules/pdp_live~.c @@ -35,6 +35,8 @@ #include <sys/time.h> #include <sys/resource.h> #include <avformat.h> +#include <sys/types.h> +#include <signal.h> #define VIDEO_BUFFER_SIZE (1024*1024) #define MAX_AUDIO_PACKET_SIZE (128 * 1024) @@ -46,8 +48,8 @@ #define DEFAULT_HEIGHT 240 #define DEFAULT_FRAME_RATE 25 #define END_OF_STREAM 20 -#define MIN_PRIORITY -20 -#define DEFAULT_PRIORITY 0 +#define MIN_PRIORITY 0 +#define DEFAULT_PRIORITY 1 #define MAX_PRIORITY 20 /* a trick to cope with ffmpeg versions */ @@ -66,7 +68,7 @@ typedef struct pdp_live_struct t_int x_dropped; t_pdp *x_header; - short int *x_data; + unsigned char *x_data; t_int x_vwidth; t_int x_vheight; t_int x_vsize; @@ -82,10 +84,14 @@ typedef struct pdp_live_struct pthread_t x_connectchild; // thread used for connecting to a stream pthread_t x_decodechild; // stream decoding thread t_int x_usethread; // flag to activate decoding in a thread + t_int x_autoplay; // flag to autoplay the file ( default = true ) + t_int x_nextimage; // flag to play next image in manual mode t_int x_priority; // priority of decoding thread char *x_url; t_int x_streaming; // streaming flag + t_int x_decoding; // decoding flag + t_int x_loop; // looping flag ( default = on ) t_int x_nopackets; // no packet to decode t_int x_endofstream; // end of the stream reached t_int x_nbframes; // number of frames emitted @@ -108,6 +114,7 @@ typedef struct pdp_live_struct AVPicture x_picture_decoded; long long int x_pts; // presentation time stamp long long int x_previouspts; // previous presentation time stamp + long long int x_firstpts; // first presentation time stamp ( time origin ) t_int x_newpicture; /* audio structures */ @@ -137,19 +144,80 @@ static void pdp_live_threadify(t_pdp_live *x, t_floatarg fusethread ) static void pdp_live_audio(t_pdp_live *x, t_floatarg faudio ) { - if ( ( faudio == 0.0 ) || ( faudio == 1 ) ) + if ( ( faudio == 0. ) || ( faudio == 1. ) ) { x->x_audio = (int)faudio; } } +static void pdp_live_autoplay(t_pdp_live *x, t_floatarg fautoplay ) +{ + if ( ( fautoplay == 0. ) || ( fautoplay == 1. ) ) + { + x->x_autoplay = (int)fautoplay; + } +} + +static void pdp_live_loop(t_pdp_live *x, t_floatarg floop ) +{ + if ( ( floop == 0. ) || ( floop == 1. ) ) + { + x->x_loop = (int)floop; + } +} + +static void pdp_live_bang(t_pdp_live *x) +{ + if ( x->x_nextimage == 1 ) + { + // post( "pdp_live~ : banging too fast, previous image is not decoded yet... ignored" ); + return; + } + x->x_nextimage = 1; +} + +static void pdp_live_frame_cold(t_pdp_live *x, t_floatarg frameindex) +{ + int frame = (int)frameindex; + int ret; + uint64_t newpts; + + if (!(x->x_streaming)) return; + +#if FFMPEG_VERSION_INT >= 0x000409 + if ( ( ( x->x_avcontext->iformat->flags & AVFMT_NOFILE ) == 0 ) && ( x->x_videoindex != -1 ) ) + { + if ( x->x_framerate != 0 ) + { + newpts = x->x_firstpts + ( frame * 1000000 ) / x->x_framerate; + } + else + { + post( "pdp_live~ : couldn't seek requested frame ( framerate = %d )", x->x_framerate ); + return; + } + if ( ( ret = av_seek_frame(x->x_avcontext, x->x_videoindex, newpts) ) < 0 ) + { + post( "pdp_live~ : couldn't seek the requested frame (ret=%d)", ret ); + } + } +#else + post( "pdp_live~ : no seek function with ffmpeg < 4.0.9"); +#endif +} + static t_int pdp_live_decode_packet(t_pdp_live *x) { - t_int chunksize=0, length; + t_int chunksize=0, length, err, ret; t_int audiosize, sizeout, imagesize, pictureok; AVFrame frame; uint8_t *pcktptr; - struct timeval etime; + unsigned char *pY, *pU, *pV; + uint8_t *psY, *psU, *psV; + t_int px, py; + long long tplaying; + long long ttheoretical; + struct timeval ctime; struct timespec mwait; if ( !x->x_streaming ) @@ -159,13 +227,35 @@ static t_int pdp_live_decode_packet(t_pdp_live *x) // post( "pdp_live~ : trying to read packet" ); // read new packet on the stream - if (av_read_packet(x->x_avcontext, &x->x_pkt) < 0) + if ( x->x_streaming && av_read_packet(x->x_avcontext, &x->x_pkt) < 0) { x->x_nopackets++; // post( "pdp_live~ : decoding thread : nothing to decode : no packets :%d", x->x_nopackets ); if ( x->x_nopackets > END_OF_STREAM ) { x->x_endofstream = 1; + if ( x->x_loop ) + { + +#if FFMPEG_VERSION_INT >= 0x000409 + + if ( ( x->x_avcontext->iformat->flags & AVFMT_NOFILE ) == 0 ) + { + post( "pdp_live~ : looping file reading..." ); + if ( ( ret = av_seek_frame(x->x_avcontext, x->x_videoindex, 0) ) < 0 ) + { + post( "pdp_live~ : couldn't seek the requested frame (ret=%d)", ret ); + } + else + { + x->x_endofstream = 0; + } + } + +#else + post( "pdp_live~ : no seek function with ffmpeg < 4.0.9"); +#endif + } } return -1; } @@ -182,6 +272,7 @@ static t_int pdp_live_decode_packet(t_pdp_live *x) pcktptr = x->x_pkt.data; while (length > 0) { + if ( !x->x_streaming ) break; switch(x->x_avcontext->streams[x->x_pkt.stream_index]->codec.codec_type) { case CODEC_TYPE_AUDIO: @@ -240,7 +331,7 @@ static t_int pdp_live_decode_packet(t_pdp_live *x) post( "pdp_live~ : audio overflow : packet ignored..."); x->x_audioin_position = 0; } - if ( ( x->x_audioin_position > x->x_blocksize ) && (!x->x_audioon) ) + if ( ( x->x_audioin_position > 4*x->x_blocksize ) && (!x->x_audioon) ) { x->x_audioon = 1; // post( "pdp_live~ : audio on" ); @@ -253,20 +344,9 @@ static t_int pdp_live_decode_packet(t_pdp_live *x) x->x_avcontext->streams[x->x_pkt.stream_index]->codec.height * 3) / 2; // yuv planar x->x_framerate = x->x_avcontext->streams[x->x_pkt.stream_index]->codec.frame_rate / 10000; + if ( x->x_framerate == 0 ) x->x_framerate = DEFAULT_FRAME_RATE; x->x_videoindex = x->x_pkt.stream_index; - // calculate actual frame rate - // if ( gettimeofday(&etime, NULL) == -1) - // { - // post("pdp_live~ : could not read time" ); - // } - // if ( ( etime.tv_sec - x->x_starttime.tv_sec ) > 0 ) - // { - // x->x_framerate = x->x_nbframes / ( etime.tv_sec - x->x_starttime.tv_sec ); - // } - // if ( x->x_framerate == 0 ) x->x_framerate = 1; - // post ("pdp_live~ : frame rate is %d", x->x_framerate ); - chunksize = avcodec_decode_video( &x->x_avcontext->streams[x->x_pkt.stream_index]->codec, &frame, &pictureok, @@ -304,37 +384,74 @@ static t_int pdp_live_decode_packet(t_pdp_live *x) else { x->x_newpicture=1; - x->x_previouspts = x->x_pts; - x->x_pts = frame.pts; - // post( "pdp_live : frame pts : %ld", x->x_pts ); x->x_vwidth = x->x_avcontext->streams[x->x_pkt.stream_index]->codec.width; x->x_vheight = x->x_avcontext->streams[x->x_pkt.stream_index]->codec.height; x->x_vsize = x->x_vwidth*x->x_vheight; - if ( x->x_previouspts != -1 ) + // create a new pdp packet from BITMAP YV12 image format + x->x_packet0 = pdp_packet_new_bitmap_yv12( x->x_vwidth, x->x_vheight ); + x->x_header = pdp_packet_header(x->x_packet0); + x->x_data = (unsigned char *)pdp_packet_data(x->x_packet0); + + pY = x->x_data; + pV = x->x_data+x->x_vsize; + pU = x->x_data+x->x_vsize+(x->x_vsize>>2); + + psY = x->x_picture_decoded.data[0]; + psU = x->x_picture_decoded.data[1]; + psV = x->x_picture_decoded.data[2]; + + for ( py=0; py<x->x_vheight; py++) + { + memcpy( (void*)pY, (void*)psY, x->x_vwidth ); + pY += x->x_vwidth; + psY += x->x_picture_decoded.linesize[0]; + if ( py%2==0 ) + { + memcpy( (void*)pU, (void*)psU, (x->x_vwidth>>1) ); + memcpy( (void*)pV, (void*)psV, (x->x_vwidth>>1) ); + pU += (x->x_vwidth>>1); + pV += (x->x_vwidth>>1); + psU += x->x_picture_decoded.linesize[1]; + psV += x->x_picture_decoded.linesize[2]; + } + } + + if ( x->x_firstpts == -1 ) { - mwait.tv_sec = 0; - mwait.tv_nsec = (x->x_pts - x->x_previouspts)*1000; - - if ( ( x->x_pts == 0 ) ) - { - // post("pdp_live~ : no presentation time stamp, using framerate :%d", - // x->x_framerate ); - mwait.tv_sec = 0; - mwait.tv_nsec = 1000000000/((x->x_framerate+5)); // the +5 is experimental - // it comes from the time used in decoding - } - - nanosleep( &mwait, NULL ); // wait between the two successive frames - // i know, cheap flow control + x->x_firstpts = x->x_pts; } + x->x_previouspts = x->x_pts; + x->x_pts = frame.pts; + // post( "pdp_live : frame pts : %ld", x->x_pts ); + if ( x->x_nbframes > 0 ) + { + if ( gettimeofday(&ctime, NULL) == -1) + { + post("pdp_theorin~ : could not read time" ); + } + + tplaying = ( ctime.tv_sec-x->x_starttime.tv_sec )*1000 + + ( ctime.tv_usec-x->x_starttime.tv_usec )/1000; + ttheoretical = ((x->x_nbframes)*1000 )/x->x_framerate; + // post( "pdp-theorin~ : %d playing since : %lldms ( theory : %lldms )", + // x->x_nbframes, tplaying, ttheoretical ); + + if ( tplaying < ttheoretical ) + { + mwait.tv_sec = 0; + mwait.tv_nsec = (ttheoretical - tplaying)*1000000; + + // wait between the two successive frames + if ( x->x_autoplay ) nanosleep( &mwait, NULL ); + } + } } break; } pcktptr += chunksize; length -= chunksize; - if ( !x->x_streaming ) break; } av_free_packet(&x->x_pkt); @@ -353,18 +470,10 @@ static void *pdp_decode_stream_from_url(void *tdata) twait.tv_sec = 0; twait.tv_nsec = 10000000; // 10 ms - schedprio.sched_priority = 0; - if ( sched_setscheduler(0,SCHED_OTHER,&schedprio) == -1) - { - post("pdp_live~ : couldn't set scheduler for decoding thread.\n"); - } - if ( setpriority( PRIO_PROCESS, 0, x->x_priority ) < 0 ) - { - post("pdp_live~ : couldn't set priority to %d for decoding thread.\n", x->x_priority ); - } - else + schedprio.sched_priority = sched_get_priority_min(SCHED_FIFO) + x->x_priority; + if ( sched_setscheduler(0,SCHED_FIFO,&schedprio) == -1) { - post("pdp_live~ : priority set to %d for thread %d.\n", x->x_priority, x->x_decodechild ); + post("pdp_theorin~ : couldn't set priority for decoding thread."); } if ( ! (x->x_avcontext->iformat->flags & AVFMT_NOHEADER ) ) @@ -376,19 +485,27 @@ static void *pdp_decode_stream_from_url(void *tdata) post( "pdp_live~ : read header." ); } - while ( x->x_streaming ) + while ( x->x_decodechild ) { - while ( x->x_newpicture ) nanosleep( &twait, NULL ); + if ( ( x->x_streaming ) && ( ( x->x_autoplay ) || ( x->x_nextimage == 1 ) ) ) + { + x->x_decoding = 1; + while ( x->x_newpicture ) nanosleep( &twait, NULL ); - // decode incoming packets - if ( pdp_live_decode_packet( x ) < 0 ) + // decode incoming packets + if ( ( x->x_streaming ) && ( pdp_live_decode_packet( x ) < 0 ) ) + { + nanosleep( &twait, NULL ); // nothing to read, just wait + } + x->x_nextimage = -1; + } + else { - nanosleep( &twait, NULL ); // nothing to read, just wait + x->x_decoding = 0; + nanosleep( &twait, NULL ); // nothing to do, just wait } - } - post( "pdp_live~ : decoding thread %d exiting....", x->x_decodechild ); x->x_decodechild = 0; pthread_exit(NULL); } @@ -419,7 +536,6 @@ static void *pdp_live_connect_to_url(void *tdata) if ( err == -5 ) post( "pdp_live~ : not enough memory" ); if ( err == -6 ) post( "pdp_live~ : unknown format ( stream not found? )" ); x->x_connectchild = 0; - x->x_avcontext = av_mallocz(sizeof(AVFormatContext)); pthread_exit(NULL); } /* If not enough info to get the stream parameters, we decode the @@ -507,11 +623,13 @@ static void *pdp_live_connect_to_url(void *tdata) x->x_nopackets = 0; x->x_endofstream = 0; x->x_nbframes = 0; + x->x_pts = -1; + x->x_previouspts = -1; + x->x_firstpts = -1; - x->x_connectchild = 0; - - if ( x->x_usethread ) + if ( x->x_usethread && ( x->x_decodechild == 0 )) { + x->x_decodechild = 1; // trick & treets // launch decoding thread if ( pthread_attr_init( &decode_child_attr ) < 0 ) { @@ -556,15 +674,15 @@ static void pdp_live_disconnect(t_pdp_live *x) { x->x_streaming = 0; x->x_newpicture = 0; - post("pdp_live~ : waiting for the end of decoding thread..." ); - while ( x->x_decodechild && ( count < 100 ) ) + post("pdp_live~ : waiting end of decoding..." ); + while ( x->x_decoding ) nanosleep( &twait, NULL ); + /* close each decoder */ + for(i=0;i<x->x_avcontext->nb_streams;i++) { - count++; - sleep( 1 ); - } - if ( x->x_decodechild ) - { - post("pdp_live~ : zombie thread, i guess" ); + if (avcodec_close(&x->x_avcontext->streams[i]->codec) < 0) + { + post("pdp_live~ : error while closing codec for stream #%d", i); + } } post("pdp_live~ : closing input file..." ); av_close_input_file(x->x_avcontext); @@ -631,9 +749,6 @@ static t_int *pdp_live_perform(t_int *w) t_float *out2 = (t_float *)(w[2]); // right audio inlet t_pdp_live *x = (t_pdp_live *)(w[3]); int n = (int)(w[4]); // number of samples - short int *pY, *pU, *pV; - uint8_t *psY, *psU, *psV; - t_int pixRGB, px, py; short sampleL, sampleR; struct timeval etime; t_int sn; @@ -650,7 +765,6 @@ static t_int *pdp_live_perform(t_int *w) if ( x->x_audioon ) { sn=0; - n=n*DEFAULT_CHANNELS; while (n--) { sampleL=x->x_audio_in[ sn++ ]; @@ -670,7 +784,7 @@ static t_int *pdp_live_perform(t_int *w) memcpy( &x->x_audio_in[0], &x->x_audio_in[sn], 4*MAX_AUDIO_PACKET_SIZE-sn ); x->x_audioin_position-=sn; // post( "pdp_live~ : audio in position : %d", x->x_audioin_position ); - if ( x->x_audioin_position <= sn ) + if ( x->x_audioin_position <= 0 ) { x->x_audioon = 0; // post( "pdp_live~ : audio off" ); @@ -694,7 +808,7 @@ static t_int *pdp_live_perform(t_int *w) if ( etime.tv_sec != x->x_cursec ) { x->x_cursec = etime.tv_sec; - if (x->x_streaming) outlet_float( x->x_outlet_framerate, x->x_framerate ); + if (x->x_streaming) outlet_float( x->x_outlet_framerate, x->x_secondcount ); x->x_secondcount = 0; } if ( x->x_secondcount >= x->x_framerate ) @@ -705,45 +819,13 @@ static t_int *pdp_live_perform(t_int *w) // output image if there's a new one decoded if ( x->x_newpicture ) { - // create a new pdp packet from PIX_FMT_YUV420P image format - x->x_packet0 = pdp_packet_new_image_YCrCb( x->x_vwidth, x->x_vheight ); - x->x_header = pdp_packet_header(x->x_packet0); - x->x_data = (short int *)pdp_packet_data(x->x_packet0); - - pY = x->x_data; - pV = x->x_data+x->x_vsize; - pU = x->x_data+x->x_vsize+(x->x_vsize>>2); - - psY = x->x_picture_decoded.data[0]; - psU = x->x_picture_decoded.data[1]; - psV = x->x_picture_decoded.data[2]; - - for ( py=0; py<x->x_vheight; py++) - { - for ( px=0; px<x->x_vwidth; px++) - { - *(pY) = ( *(psY+px) << 7 ); - *(pV) = ( ((*(psV+(px>>1)))-128) << 8 ); - *(pU) = ( ((*(psU+(px>>1)))-128) << 8 ); - pY++; - if ( (px%2==0) && (py%2==0) ) - { - pV++; pU++; - } - } - psY += x->x_picture_decoded.linesize[0]; - if ( py%2==0 ) psU += x->x_picture_decoded.linesize[1]; - if ( py%2==0 ) psV += x->x_picture_decoded.linesize[2]; - } - pdp_packet_pass_if_valid(x->x_pdp_out, &x->x_packet0); + x->x_newpicture = 0; // update streaming status x->x_nbframes++; x->x_secondcount++; outlet_float( x->x_outlet_nbframes, x->x_nbframes ); - - x->x_newpicture = 0; } outlet_float( x->x_outlet_streaming, x->x_streaming ); outlet_float( x->x_outlet_endofstream, x->x_endofstream ); @@ -760,18 +842,19 @@ static void pdp_live_free(t_pdp_live *x) { int i; + if ( x->x_decodechild ) + { + x->x_decodechild = 0; + x->x_decoding = 0; + sleep( 5 ); // let the decoding thread exiting + } + if ( x->x_streaming ) { pdp_live_disconnect(x); - sleep(3); } post( "pdp_live~ : freeing object" ); pdp_packet_mark_unused(x->x_packet0); - if (x->x_audio_resample_ctx) - { - audio_resample_close(x->x_audio_resample_ctx); - x->x_audio_resample_ctx = NULL; - } av_free_static(); } @@ -783,6 +866,8 @@ void *pdp_live_new(void) t_pdp_live *x = (t_pdp_live *)pd_new(pdp_live_class); + inlet_new(&x->x_obj, &x->x_obj.ob_pd, gensym("float"), gensym("frame_cold")); + x->x_pdp_out = outlet_new(&x->x_obj, &s_anything); x->x_outlet_left = outlet_new(&x->x_obj, &s_signal); @@ -796,6 +881,7 @@ void *pdp_live_new(void) x->x_packet0 = -1; x->x_connectchild = 0; x->x_decodechild = 0; + x->x_decoding = 0; x->x_usethread = 1; x->x_priority = DEFAULT_PRIORITY; x->x_framerate = DEFAULT_FRAME_RATE; @@ -807,15 +893,19 @@ void *pdp_live_new(void) x->x_secondcount = 0; x->x_audio_resample_ctx = NULL; x->x_nbvideostreams = 0; - x->x_videoindex = 0; + x->x_videoindex = -1; x->x_audioin_position = 0; x->x_newpicture = 0; x->x_endofstream = 0; x->x_nopackets = 0; x->x_blocksize = MIN_AUDIO_SIZE; + x->x_autoplay = 1; + x->x_loop = 1; + x->x_nextimage = 0; x->x_pts = -1; x->x_previouspts = -1; + x->x_firstpts = -1; x->x_avcontext = av_mallocz(sizeof(AVFormatContext)); if ( !x->x_avcontext ) @@ -851,7 +941,11 @@ void pdp_live_tilde_setup(void) class_addmethod(pdp_live_class, (t_method)pdp_live_disconnect, gensym("disconnect"), A_NULL); class_addmethod(pdp_live_class, (t_method)pdp_live_priority, gensym("priority"), A_FLOAT, A_NULL); class_addmethod(pdp_live_class, (t_method)pdp_live_audio, gensym("audio"), A_FLOAT, A_NULL); + class_addmethod(pdp_live_class, (t_method)pdp_live_autoplay, gensym("autoplay"), A_FLOAT, A_NULL); + class_addmethod(pdp_live_class, (t_method)pdp_live_loop, gensym("loop"), A_FLOAT, A_NULL); class_addmethod(pdp_live_class, (t_method)pdp_live_threadify, gensym("thread"), A_FLOAT, A_NULL); + class_addmethod(pdp_live_class, (t_method)pdp_live_bang, gensym("bang"), A_NULL); + class_addmethod(pdp_live_class, (t_method)pdp_live_frame_cold, gensym("frame_cold"), A_FLOAT, A_NULL); class_sethelpsymbol( pdp_live_class, gensym("pdp_live~.pd") ); } |