From 3f6ad372d2f45b9e21e4cec9843a064429332738 Mon Sep 17 00:00:00 2001 From: "N.N." Date: Tue, 4 Oct 2005 20:37:14 +0000 Subject: PiDiP 0.12.19 svn path=/trunk/externals/pidip/; revision=3657 --- modules/Makefile | 2 +- modules/pdp_ffmpeg~.c | 272 +++++++++++++++++++++++++++++++++++++++++++++++++- modules/pdp_live~.c | 97 ++++++++++++++++-- 3 files changed, 358 insertions(+), 13 deletions(-) (limited to 'modules') diff --git a/modules/Makefile b/modules/Makefile index bc263e7..a00c7d3 100644 --- a/modules/Makefile +++ b/modules/Makefile @@ -21,6 +21,6 @@ OBJECTS = pdp_intrusion.o pdp_yqt.o pdp_simura.o pdp_underwatch.o \ pdp_theorout~.o pdp_cropper.o pdp_background.o \ pdp_mapper.o pdp_theonice~.o pdp_icedthe~.o\ pdp_fdiff.o pdp_hue.o \ - # pdp_xcanvas.o pdp_aa.o + pdp_live~.o pdp_ffmpeg~.o # pdp_xcanvas.o pdp_aa.o all_modules: $(OBJECTS) diff --git a/modules/pdp_ffmpeg~.c b/modules/pdp_ffmpeg~.c index fec1c35..f573a9a 100644 --- a/modules/pdp_ffmpeg~.c +++ b/modules/pdp_ffmpeg~.c @@ -112,6 +112,28 @@ static int pdp_ffmpeg_read_ffserver_streams(t_pdp_ffmpeg *x) memcpy(st, ic->streams[i], sizeof(AVStream)); x->x_avcontext->streams[i] = st; +#if FFMPEG_VERSION_INT >= 0x000409 + if ( ic->streams[i]->codec->codec_type == CODEC_TYPE_UNKNOWN ) + { + post( "pdp_ffmpeg~ : stream #%d # type : unknown", i ); + } + if ( ic->streams[i]->codec->codec_type == CODEC_TYPE_AUDIO ) + { + post( "pdp_ffmpeg~ : stream #%d # type : audio # id : %d # bitrate : %d", + i, ic->streams[i]->codec->codec_id, ic->streams[i]->codec->bit_rate ); + post( "pdp_ffmpeg~ : sample rate : %d # channels : %d", + ic->streams[i]->codec->sample_rate, ic->streams[i]->codec->channels ); + x->x_nbaudiostreams++; + } + if ( ic->streams[i]->codec->codec_type == CODEC_TYPE_VIDEO ) + { + post( "pdp_ffmpeg~ : stream #%d # type : video # id : %d # bitrate : %d", + i, ic->streams[i]->codec->codec_id, ic->streams[i]->codec->bit_rate ); + post( "pdp_ffmpeg~ : framerate : %d # width : %d # height : %d", + av_q2d( ic->streams[i]->r_frame_rate ), ic->streams[i]->codec->width, ic->streams[i]->codec->height ); + x->x_nbvideostreams++; + } +#else if ( ic->streams[i]->codec.codec_type == CODEC_TYPE_UNKNOWN ) { post( "pdp_ffmpeg~ : stream #%d # type : unknown", i ); @@ -132,6 +154,7 @@ static int pdp_ffmpeg_read_ffserver_streams(t_pdp_ffmpeg *x) ic->streams[i]->codec.frame_rate/10000, ic->streams[i]->codec.width, ic->streams[i]->codec.height ); x->x_nbvideostreams++; } +#endif } if ( x->x_secondcount ) free( x->x_secondcount ); @@ -188,7 +211,11 @@ static void pdp_ffmpeg_starve(t_pdp_ffmpeg *x) for(i=0;ix_avcontext->nb_streams;i++) { +#if FFMPEG_VERSION_INT >= 0x000409 + avcodec_close( x->x_avcontext->streams[i]->codec ); +#else avcodec_close( &x->x_avcontext->streams[i]->codec ); +#endif av_free( x->x_avcontext->streams[i] ); } @@ -247,7 +274,11 @@ static void pdp_ffmpeg_feed(t_pdp_ffmpeg *x, t_symbol *s) for(i=0;ix_avcontext->nb_streams;i++) { AVCodec *codec; +#if FFMPEG_VERSION_INT >= 0x000409 + codec = avcodec_find_encoder(x->x_avcontext->streams[i]->codec->codec_id); +#else codec = avcodec_find_encoder(x->x_avcontext->streams[i]->codec.codec_id); +#endif if (!codec) { post("pdp_ffmpeg~ : unsupported codec for output stream #%d\n", i ); @@ -255,7 +286,11 @@ static void pdp_ffmpeg_feed(t_pdp_ffmpeg *x, t_symbol *s) outlet_float( x->x_outlet_streaming, x->x_streaming ); return; } +#if FFMPEG_VERSION_INT >= 0x000409 + if (avcodec_open(x->x_avcontext->streams[i]->codec, codec) < 0) +#else if (avcodec_open(&x->x_avcontext->streams[i]->codec, codec) < 0) +#endif { post("pdp_ffmpeg~ : error while opening codec for stream #%d - maybe incorrect parameters such as bit_rate, rate, width or height\n", i); x->x_streaming = 0; @@ -371,8 +406,240 @@ static void pdp_ffmpeg_process_yv12(t_pdp_ffmpeg *x) // output all video streams svideoindex=0; saudioindex=0; - for (i=0; ix_avcontext->nb_streams; i++) - { +#if FFMPEG_VERSION_INT >= 0x000409 + for (i=0; ix_avcontext->nb_streams; i++) + { + /* convert pixel format and size if needed */ + if ( x->x_avcontext->streams[i]->codec->codec_type == CODEC_TYPE_VIDEO ) + { + t_int size; + + // check if the framerate has been exceeded + 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; + outlet_float( x->x_outlet_framerate, x->x_secondcount[ svideoindex ] ); + for (j=0; jx_nbvideostreams; j++) + { + x->x_secondcount[ j ] = 0; + } + } + framerate = av_q2d( x->x_avcontext->streams[i]->r_frame_rate ); + ttime = ( ( x->x_nbframes + 1 ) % framerate ) * ( 1000 / framerate ); + atime = ( etime.tv_usec / 1000 ); + // post("pdp_theonice~ : actual : %d, theoretical : %d", atime, ttime ); + if ( atime < ttime ) + { + x->x_nbframes_dropped++; + continue; + } + + if ( x->x_avcontext->streams[i]->codec->pix_fmt != PIX_FMT_YUV420P ) + { + /* create temporary picture */ + size = avpicture_get_size(x->x_avcontext->streams[i]->codec->pix_fmt, + x->x_vwidth, x->x_vheight ); + if (!x->x_buf1) x->x_buf1 = (uint8_t*) malloc(size); + if (!x->x_buf1) + { + post ("pdp_ffmpeg~ : severe error : could not allocate image buffer" ); + return; + } + x->x_formatted_picture = &x->x_picture_format; + avpicture_fill(x->x_formatted_picture, x->x_buf1, + x->x_avcontext->streams[i]->codec->pix_fmt, + x->x_vwidth, x->x_vheight ); + + if (img_convert(x->x_formatted_picture, + x->x_avcontext->streams[i]->codec->pix_fmt, + &pdppict, PIX_FMT_YUV420P, + x->x_vwidth, x->x_vheight ) < 0) + { + post ("pdp_ffmpeg~ : error : image conversion failed" ); + } + } + else + { + x->x_formatted_picture = &pdppict; + } + + // post ( "pdp_ffmpeg~ : resampling [%d,%d] -> [%d,%d]", + // x->x_vwidth, x->x_vheight, + // x->x_avcontext->streams[i]->codec->width, + // x->x_avcontext->streams[i]->codec->height ); + if ( ( x->x_avcontext->streams[i]->codec->width < x->x_vwidth ) && + ( x->x_avcontext->streams[i]->codec->height < x->x_vheight ) ) + { + owidth = x->x_avcontext->streams[i]->codec->width; + oheight = x->x_avcontext->streams[i]->codec->height; + + if (x->x_img_resample_ctx) img_resample_close(x->x_img_resample_ctx); + +#if LIBAVCODEC_BUILD > 4715 + x->x_img_resample_ctx = img_resample_full_init( + owidth, oheight, + x->x_vwidth, x->x_vheight, + 0, 0, 0, 0, + 0, 0, 0, 0); +#else + x->x_img_resample_ctx = img_resample_full_init( + owidth, oheight, + x->x_vwidth, x->x_vheight, 0, 0, 0, 0); +#endif + + size = avpicture_get_size(x->x_avcontext->streams[i]->codec->pix_fmt, + owidth, oheight ); + if ( !x->x_buf2 ) + { + x->x_buf2 = (uint8_t*) malloc(size); + } + if (!x->x_buf2) + { + post ("pdp_ffmpeg~ : severe error : could not allocate image buffer" ); + return; + } + x->x_final_picture = &x->x_picture_final; + avpicture_fill(x->x_final_picture, x->x_buf2, + x->x_avcontext->streams[i]->codec->pix_fmt, + owidth, oheight ); + + img_resample(x->x_img_resample_ctx, x->x_final_picture, x->x_formatted_picture); + + } + else + { + x->x_final_picture = x->x_formatted_picture; + } + + // encode and send the picture + { + AVFrame aframe; +#if LIBAVCODEC_BUILD > 4715 + AVPacket vpkt; +#endif + t_int fsize, ret; + + memset(&aframe, 0, sizeof(AVFrame)); + *(AVPicture*)&aframe= *x->x_final_picture; + aframe.pts = etime.tv_sec*1000000 + etime.tv_usec; + aframe.quality = x->x_avcontext->streams[i]->quality; + + fsize = avcodec_encode_video(x->x_avcontext->streams[i]->codec, + x->x_video_buffer, VIDEO_BUFFER_SIZE, + &aframe); + +#if LIBAVCODEC_BUILD > 4715 + av_init_packet(&vpkt); + + vpkt.pts = aframe.pts; + if(x->x_avcontext->streams[i]->codec->coded_frame->key_frame) + vpkt.flags |= PKT_FLAG_KEY; + vpkt.stream_index= i; + vpkt.data= (uint8_t *)x->x_video_buffer; + vpkt.size= fsize; + + if ( ( ret = av_write_frame( x->x_avcontext, &vpkt) ) < 0 ) +#else + if ( ( ret = av_write_frame( x->x_avcontext, i, x->x_video_buffer, fsize) ) < 0 ) +#endif + { + post ("pdp_ffmpeg~ : error : could not send frame : (ret=%d)", ret ); + return; + } + else + { + x->x_nbframes++; + x->x_secondcount[ svideoindex ]++; + // post ("pdp_ffmpeg~ : index=%d count=%d", svideoindex, x->x_secondcount[ svideoindex ] ); + svideoindex++; + } + } + } + + /* convert and stream audio data */ + if ( x->x_avcontext->streams[i]->codec->codec_type == CODEC_TYPE_AUDIO ) + { + // we assume audio is synchronized on next video stream + if ( ( (i+1) < x->x_avcontext->nb_streams ) && + ( x->x_avcontext->streams[i+1]->codec->codec_type == CODEC_TYPE_VIDEO ) ) + { + x->x_audio_per_frame = + // 2*( (int) sys_getsr() ) / av_q2d( x->x_avcontext->streams[i+1]->r_frame_rate ) ; + AUDIO_PACKET_SIZE; + // post ("pdp_ffmpeg~ : transmit %d samples", x->x_audio_per_frame ); + } + else + { + post ("pdp_ffmpeg~ : can't stream audio : video stream is not found" ); + continue; + } + + if ( x->x_audioin_position > x->x_audio_per_frame ) + { + size = x->x_audioin_position; + if ( ( x->x_avcontext->streams[i]->codec->sample_rate != (int)sys_getsr() ) || + ( x->x_avcontext->streams[i]->codec->channels != 2 ) ) + { + if (x->x_audio_resample_ctx) audio_resample_close(x->x_audio_resample_ctx); + x->x_audio_resample_ctx = + audio_resample_init(x->x_avcontext->streams[i]->codec->channels, 2, + x->x_avcontext->streams[i]->codec->sample_rate, + (int)sys_getsr()); + sizeout = audio_resample(x->x_audio_resample_ctx, + x->x_audio_enc_buf, + x->x_audio_buf, + size / (x->x_avcontext->streams[i]->codec->channels * 2)); + pencbuf = (short*) &x->x_audio_enc_buf; + sizeout = sizeout * x->x_avcontext->streams[i]->codec->channels * 2; + } + else + { + pencbuf = (short*) &x->x_audio_buf; + sizeout = size; + } + + /* output resampled raw samples */ + fifo_write(&x->x_audio_fifo[saudioindex], (uint8_t*)pencbuf, sizeout, + &x->x_audio_fifo[saudioindex].wptr); + + framebytes = x->x_avcontext->streams[i]->codec->frame_size * 2 * + x->x_avcontext->streams[i]->codec->channels; + + while (fifo_read(&x->x_audio_fifo[saudioindex], (uint8_t*)pencbuf, framebytes, + &x->x_audio_fifo[saudioindex].rptr) == 0) + { +#if LIBAVCODEC_BUILD > 4715 + AVPacket apkt; +#endif + encsize = avcodec_encode_audio(x->x_avcontext->streams[i]->codec, + (uint8_t*)&x->x_audio_out, sizeof(x->x_audio_out), + (short *)pencbuf); +#if LIBAVCODEC_BUILD > 4715 + av_init_packet(&apkt); + + apkt.pts = etime.tv_sec*1000000 + etime.tv_usec; + if(x->x_avcontext->streams[i]->codec->coded_frame->key_frame) + apkt.flags |= PKT_FLAG_KEY; + apkt.stream_index= i; + apkt.data= (uint8_t *)x->x_audio_out; + apkt.size= encsize; + + av_write_frame(x->x_avcontext, &apkt); +#else + av_write_frame(x->x_avcontext, i, x->x_audio_out, encsize); +#endif + } + saudioindex++; + } + } + } +#else + for (i=0; ix_avcontext->nb_streams; i++) + { /* convert pixel format and size if needed */ if ( x->x_avcontext->streams[i]->codec.codec_type == CODEC_TYPE_VIDEO ) { @@ -601,6 +868,7 @@ static void pdp_ffmpeg_process_yv12(t_pdp_ffmpeg *x) } } } +#endif x->x_audioin_position=0; } return; diff --git a/modules/pdp_live~.c b/modules/pdp_live~.c index 2404aba..8b5e69d 100644 --- a/modules/pdp_live~.c +++ b/modules/pdp_live~.c @@ -276,7 +276,11 @@ static t_int pdp_live_decode_packet(t_pdp_live *x) while (length > 0) { if ( !x->x_streaming ) break; +#if FFMPEG_VERSION_INT >= 0x000409 + switch(x->x_avcontext->streams[x->x_pkt.stream_index]->codec->codec_type) +#else switch(x->x_avcontext->streams[x->x_pkt.stream_index]->codec.codec_type) +#endif { case CODEC_TYPE_AUDIO: if ( !x->x_audio ) @@ -284,7 +288,11 @@ static t_int pdp_live_decode_packet(t_pdp_live *x) av_free_packet(&x->x_pkt); return 0; } +#if FFMPEG_VERSION_INT >= 0x000409 + chunksize = avcodec_decode_audio(x->x_avcontext->streams[x->x_pkt.stream_index]->codec, +#else chunksize = avcodec_decode_audio(&x->x_avcontext->streams[x->x_pkt.stream_index]->codec, +#endif &x->x_audio_buf[0], &audiosize, pcktptr, length); if (chunksize < 0) @@ -316,6 +324,21 @@ static t_int pdp_live_decode_packet(t_pdp_live *x) // x->x_avcontext->streams[x->x_pkt.stream_index]->codec.channels, // (int)sys_getsr(), 2, x->x_audioin_position ); +#if FFMPEG_VERSION_INT >= 0x000409 + x->x_audiochannels = x->x_avcontext->streams[x->x_pkt.stream_index]->codec->channels; + x->x_samplerate = x->x_avcontext->streams[x->x_pkt.stream_index]->codec->sample_rate; + if (x->x_audio_resample_ctx) audio_resample_close(x->x_audio_resample_ctx); + x->x_audio_resample_ctx = + audio_resample_init(DEFAULT_CHANNELS, + x->x_avcontext->streams[x->x_pkt.stream_index]->codec->channels, + (int)sys_getsr(), + x->x_avcontext->streams[x->x_pkt.stream_index]->codec->sample_rate); + + sizeout = audio_resample(x->x_audio_resample_ctx, + &x->x_audio_in[x->x_audioin_position], + &x->x_audio_buf[0], + audiosize/(x->x_avcontext->streams[x->x_pkt.stream_index]->codec->channels * sizeof(short))); +#else x->x_audiochannels = x->x_avcontext->streams[x->x_pkt.stream_index]->codec.channels; x->x_samplerate = x->x_avcontext->streams[x->x_pkt.stream_index]->codec.sample_rate; if (x->x_audio_resample_ctx) audio_resample_close(x->x_audio_resample_ctx); @@ -329,6 +352,7 @@ static t_int pdp_live_decode_packet(t_pdp_live *x) &x->x_audio_in[x->x_audioin_position], &x->x_audio_buf[0], audiosize/(x->x_avcontext->streams[x->x_pkt.stream_index]->codec.channels * sizeof(short))); +#endif sizeout = sizeout * DEFAULT_CHANNELS; if ( ( x->x_audioin_position + sizeout ) < 3*MAX_AUDIO_PACKET_SIZE ) @@ -354,6 +378,25 @@ static t_int pdp_live_decode_packet(t_pdp_live *x) case CODEC_TYPE_VIDEO: +#if FFMPEG_VERSION_INT >= 0x000409 + imagesize = (x->x_avcontext->streams[x->x_pkt.stream_index]->codec->width * + x->x_avcontext->streams[x->x_pkt.stream_index]->codec->height * 3) / 2; // yuv planar + + x->x_framerate = ( t_int ) av_q2d( x->x_avcontext->streams[x->x_pkt.stream_index]->r_frame_rate ); + if ( x->x_framerate == 0 ) x->x_framerate = DEFAULT_FRAME_RATE; + x->x_videoindex = x->x_pkt.stream_index; + + chunksize = avcodec_decode_video( + x->x_avcontext->streams[x->x_pkt.stream_index]->codec, + &frame, &pictureok, + pcktptr, length); + if ( x->x_avcontext->streams[x->x_pkt.stream_index]->codec->pix_fmt != PIX_FMT_YUV420P ) + { + post( "pdp_live~ : unsupported image format : %d", + x->x_avcontext->streams[x->x_pkt.stream_index]->codec->pix_fmt ); + pictureok = 0; + } +#else imagesize = (x->x_avcontext->streams[x->x_pkt.stream_index]->codec.width * x->x_avcontext->streams[x->x_pkt.stream_index]->codec.height * 3) / 2; // yuv planar @@ -371,15 +414,7 @@ static t_int pdp_live_decode_packet(t_pdp_live *x) x->x_avcontext->streams[x->x_pkt.stream_index]->codec.pix_fmt ); pictureok = 0; } - // post( "pdp_live~ : decoded new frame : type=%d format=%d (w=%d) (h=%d) (linesizes=%d,%d,%d,%d)", - // frame.pict_type, - // x->x_avcontext->streams[x->x_pkt.stream_index]->codec.pix_fmt, - // x->x_avcontext->streams[x->x_pkt.stream_index]->codec.width, - // x->x_avcontext->streams[x->x_pkt.stream_index]->codec.height, - // frame.linesize[0], - // frame.linesize[1], - // frame.linesize[2], - // frame.linesize[3] ); +#endif x->x_picture_decoded = *(AVPicture*)&frame; x->x_avcontext->streams[x->x_pkt.stream_index]->quality= frame.quality; if (chunksize < 0) @@ -403,8 +438,13 @@ static t_int pdp_live_decode_packet(t_pdp_live *x) perror( "pthread_mutex_lock" ); } x->x_newpicture=1; +#if FFMPEG_VERSION_INT >= 0x000409 + 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; +#else 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; +#endif x->x_vsize = x->x_vwidth*x->x_vheight; // create a new pdp packet from BITMAP YV12 image format @@ -543,7 +583,6 @@ static void *pdp_live_connect_to_url(void *tdata) memset(&x->x_avparameters, 0, sizeof(AVFormatParameters)); x->x_avparameters.sample_rate = sys_getsr(); x->x_avparameters.channels = DEFAULT_CHANNELS; - x->x_avparameters.frame_rate = DEFAULT_FRAME_RATE; x->x_avparameters.width = DEFAULT_WIDTH; if ( x->x_framerate = 0 ) x->x_framerate = DEFAULT_FRAME_RATE; x->x_avparameters.height = DEFAULT_HEIGHT; @@ -584,6 +623,31 @@ static void *pdp_live_connect_to_url(void *tdata) { AVStream *st; +#if FFMPEG_VERSION_INT >= 0x000409 + if ( x->x_avcontext->streams[i]->codec->codec_type == CODEC_TYPE_UNKNOWN ) + { + post( "pdp_live~ : stream #%d # type : unknown", i ); + } + if ( x->x_avcontext->streams[i]->codec->codec_type == CODEC_TYPE_AUDIO ) + { + post( "pdp_live~ : stream #%d # type : audio # id : %d # bitrate : %d", + i, x->x_avcontext->streams[i]->codec->codec_id, x->x_avcontext->streams[i]->codec->bit_rate ); + post( "pdp_live~ : sample rate : %d # channels : %d", + x->x_avcontext->streams[i]->codec->sample_rate, x->x_avcontext->streams[i]->codec->channels ); + x->x_nbaudiostreams++; + } + if ( x->x_avcontext->streams[i]->codec->codec_type == CODEC_TYPE_VIDEO ) + { + post( "pdp_live~ : stream #%d # type : video # id : %d # bitrate : %d", + i, x->x_avcontext->streams[i]->codec->codec_id, + x->x_avcontext->streams[i]->codec->bit_rate ); + post( "pdp_live~ : framerate : %d # width : %d # height : %d", + av_q2d( x->x_avcontext->streams[i]->r_frame_rate )/10000, + x->x_avcontext->streams[i]->codec->width, + x->x_avcontext->streams[i]->codec->height ); + x->x_nbvideostreams++; + } +#else if ( x->x_avcontext->streams[i]->codec.codec_type == CODEC_TYPE_UNKNOWN ) { post( "pdp_live~ : stream #%d # type : unknown", i ); @@ -607,6 +671,7 @@ static void *pdp_live_connect_to_url(void *tdata) x->x_avcontext->streams[i]->codec.height ); x->x_nbvideostreams++; } +#endif } /* open each decoder */ @@ -614,7 +679,11 @@ static void *pdp_live_connect_to_url(void *tdata) { AVCodec *codec; post("pdp_live~ : opening decoder for stream #%d", i); +#if FFMPEG_VERSION_INT >= 0x000409 + codec = avcodec_find_decoder(x->x_avcontext->streams[i]->codec->codec_id); +#else codec = avcodec_find_decoder(x->x_avcontext->streams[i]->codec.codec_id); +#endif if (!codec) { post("pdp_live~ : unsupported codec for output stream #%d\n", i ); @@ -624,7 +693,11 @@ static void *pdp_live_connect_to_url(void *tdata) x->x_avcontext = av_mallocz(sizeof(AVFormatContext)); pthread_exit(NULL); } +#if FFMPEG_VERSION_INT >= 0x000409 + if (avcodec_open(x->x_avcontext->streams[i]->codec, codec) < 0) +#else if (avcodec_open(&x->x_avcontext->streams[i]->codec, codec) < 0) +#endif { post("pdp_live~ : error while opening codec for stream #%d - maybe incorrect parameters such as bit_rate, rate, width or height\n", i); x->x_streaming = 0; @@ -703,7 +776,11 @@ static void pdp_live_disconnect(t_pdp_live *x) /* close each decoder */ for(i=0;ix_avcontext->nb_streams;i++) { +#if FFMPEG_VERSION_INT >= 0x000409 + if (avcodec_close(x->x_avcontext->streams[i]->codec) < 0) +#else if (avcodec_close(&x->x_avcontext->streams[i]->codec) < 0) +#endif { post("pdp_live~ : error while closing codec for stream #%d", i); } -- cgit v1.2.1