/* * yuvafps.c * Copyright (C) 2002 Alfonso Garcia-Patiņo Barbolani * Linear Frame averaging modification 2004 Mark Heath * * * Upsamples or downsamples a yuv stream to a specified frame rate * Interlace modification 6 Sep 2006 * Interlace fixes 12 Sep 2007 * Chroma subsampling modification 12 Sep 2007 * Major bugfixes and code cleanup 13 Sep 2007 * * 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. * * gcc yuvafps.c -I/sw/include/mjpegtools -lmjpegutils -o yuvafps */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include "yuv4mpeg.h" #include "mpegconsts.h" #define YUVFPS_VERSION "0.1" static void print_usage() { fprintf (stderr, "usage: yuvfps [-Ib|t] -r NewFpsNum:NewFpsDen [-s [InputFpsNum:InputFpsDen]] [-c] [-v -h]\n" "yuvfps resamples a yuv video stream read from stdin to a new stream, identical\n" "to the source with frames repeated/copied/removed written to stdout.\n" "\n" "\t -r Frame rate for the resulting stream (in X:Y fractional form)\n" "\t -s Assume this source frame rate ignoring source YUV header\n" "\t -c Change only the output header frame rate, does not modify stream\n" "\t -I Force interlace conversion\n" "\t -v Verbosity degree : 0=quiet, 1=normal, 2=verbose/debug\n" "\t -h print this help\n" ); } // Resamples the video stream coming from fdIn and writes it // to fdOut. // There are two variations of the same theme: // // Upsampling: frames are duplicated when needed // Downsampling: frames from the original are skipped // Allocate a uint8_t frame int chromalloc(uint8_t *m[3],y4m_stream_info_t *sinfo) { int fs,cfs; fs = y4m_si_get_plane_length(sinfo,0); cfs = y4m_si_get_plane_length(sinfo,1); m[0] = (uint8_t *)malloc( fs ); m[1] = (uint8_t *)malloc( cfs); m[2] = (uint8_t *)malloc( cfs); if( !m[0] || !m[1] || !m[2]) { return -1; } else { return 0; } } //Copy a uint8_t frame int chromacpy(uint8_t *m[3],uint8_t *n[3],y4m_stream_info_t *sinfo) { int fs,cfs; fs = y4m_si_get_plane_length(sinfo,0); cfs = y4m_si_get_plane_length(sinfo,1); memcpy (m[0],n[0],fs); memcpy (m[1],n[1],cfs); memcpy (m[2],n[2],cfs); } // returns the opposite field ordering int invert_order(int f) { switch (f) { case Y4M_ILACE_TOP_FIRST: return Y4M_ILACE_BOTTOM_FIRST; case Y4M_ILACE_BOTTOM_FIRST: return Y4M_ILACE_TOP_FIRST; case Y4M_ILACE_NONE: return Y4M_ILACE_NONE; default: return Y4M_UNKNOWN; } } // Mix the source frame into the destination double precision frame. // does top field, bottom field or both fields (progressive) void mix(y4m_stream_info_t *sinfo, uint8_t * const input[], double *output[], double percent, int field) { // ilace: 0 progressive. 1 bottom first. 2 top first int x,y; double c; int yinc, ystart; int width,height; int cwidth,cheight; height = y4m_si_get_plane_height(sinfo,0) ; width = y4m_si_get_plane_width(sinfo,0); cheight = y4m_si_get_plane_height(sinfo,1) ; cwidth = y4m_si_get_plane_width(sinfo,1); if (field == Y4M_ILACE_NONE) { yinc = 1; ystart = 0; } if (field == Y4M_ILACE_TOP_FIRST) { yinc = 2; ystart = 0; } if (field == Y4M_ILACE_BOTTOM_FIRST) { yinc = 2; ystart = 1; } for (y=ystart; y< height; y+=yinc) for (x=0; xds) && (se=ds)) return (se-ds)/(de-ds); // start of source frame within destination // end of source outside destination if ((ss>ds) && (se>=de) && (ss<=de)) return (de-ss)/(de-ds); // destination frame completely contained in source frame if ((ss<=ds) && (se>=de)) return 1.0; // no overlap return 0.0; } // calculate percent based on source and destination frame counters and frame lengths double calc_per_fc (int sc, double sl, int dc, double dl, double fo) { return calc_per((sc+fo)*sl, (sc+fo+1.0)*sl, (dc+fo)*dl, (dc+fo+1.0)*dl); } static void resample( int fdIn , y4m_stream_info_t *inStrInfo , y4m_ratio_t src_frame_rate , int fdOut , y4m_stream_info_t *outStrInfo , y4m_ratio_t dst_frame_rate , int not_normalize ) { y4m_frame_info_t in_frame ; uint8_t *yuv_data[3] ; uint8_t *yuv_idata[3] ; uint8_t *yuv_odata[3] ; double *yuv_fdata[3]; int frame_data_size, chroma_frame_data_size ; int read_error_code ; int write_error_code ; int src_frame_counter ; int src_iframe_counter ; int dst_frame_counter ; double srcfl, dstfl, ssrcf,sdstf,esrcf,edstf,per; double osrcf,odstf,nper,iper; int w,h; int interlaced; // Allocate memory for the YUV channels interlaced = y4m_si_get_interlace(inStrInfo); h = y4m_si_get_height(inStrInfo) ; w = y4m_si_get_width(inStrInfo); frame_data_size = y4m_si_get_plane_length(inStrInfo,0); chroma_frame_data_size = y4m_si_get_plane_length(inStrInfo,1); if (chromalloc(yuv_data,inStrInfo)) mjpeg_error_exit1 ("Could'nt allocate memory for the YUV4MPEG data!"); if (chromalloc(yuv_odata,inStrInfo)) mjpeg_error_exit1 ("Could'nt allocate memory for the YUV4MPEG data!"); if (interlaced != Y4M_ILACE_NONE) { if (chromalloc(yuv_idata,inStrInfo)) mjpeg_error_exit1 ("Could'nt allocate memory for the YUV4MPEG data!"); } // should functionise this code for conversion to fixed point precision (uint32_t) yuv_fdata[0] = (double *)malloc(sizeof(double) * frame_data_size); yuv_fdata[1] = (double *)malloc(sizeof(double) * chroma_frame_data_size); yuv_fdata[2] = (double *)malloc(sizeof(double) * chroma_frame_data_size); if( !yuv_fdata[0] || !yuv_fdata[1] || !yuv_fdata[2] ) mjpeg_error_exit1 ("Could'nt allocate memory for the YUV4MPEG data!"); // source frame length, destination frame length. srcfl = (double) src_frame_rate.d / (double)src_frame_rate.n; dstfl = (double) dst_frame_rate.d / (double)dst_frame_rate.n; /* Initialize counters */ write_error_code = Y4M_OK ; read_error_code = Y4M_OK ; src_frame_counter = 0 ; src_iframe_counter = 0 ; dst_frame_counter = 0 ; y4m_init_frame_info( &in_frame ); read_error_code = y4m_read_frame(fdIn, inStrInfo, &in_frame, yuv_data ); if (interlaced != Y4M_ILACE_NONE) chromacpy (yuv_idata,yuv_data,inStrInfo); black(yuv_fdata,inStrInfo); nper = 0; iper = 0; while( Y4M_ERR_EOF != read_error_code && write_error_code == Y4M_OK ) { // Do all types of frames here Interlaced or progressive if ((per = calc_per_fc(src_frame_counter,srcfl,dst_frame_counter,dstfl,0.0)) > 0.0 && (1.0 - nper > 0.00001)) { // fprintf (stderr,"P PER %g += NPER %g\n",nper,per); mix (inStrInfo,yuv_data,yuv_fdata,per,interlaced); nper += per; } // for interlace frames, change the time offset and copy the remaining field if (interlaced != Y4M_ILACE_NONE) { if ((per = calc_per_fc(src_iframe_counter,srcfl,dst_frame_counter,dstfl,0.5)) > 0.0 && (1.0 - iper > 0.00001)) { // fprintf (stderr,"I PER %g += IPER %g\n",iper,per); if (interlaced == Y4M_ILACE_TOP_FIRST) mix (inStrInfo,yuv_idata,yuv_fdata,per,Y4M_ILACE_BOTTOM_FIRST); else mix (inStrInfo,yuv_idata,yuv_fdata,per,Y4M_ILACE_TOP_FIRST); iper += per; } // mjpeg_warn ("Add percent: %f src: %f-%f dst: %f-%f ",per,ssrcf,esrcf,sdstf,edstf); } // if the end of the source frame is passed the end of the destination frame, // then we write the destination frame if ((interlaced == Y4M_ILACE_NONE && ((src_frame_counter +1.0) * srcfl >= (dst_frame_counter + 1.0) * dstfl)) || (interlaced != Y4M_ILACE_NONE && ((src_frame_counter +1.0) * srcfl >= (dst_frame_counter + 1.0) * dstfl) && ((src_iframe_counter+1.5) * srcfl >= (dst_frame_counter + 1.5) * dstfl)) ) { intise(yuv_odata,yuv_fdata,inStrInfo); write_error_code = y4m_write_frame( fdOut, outStrInfo, &in_frame, yuv_odata ); mjpeg_info( "Writing source frame %d at dest frame %d", src_frame_counter,dst_frame_counter ); // fprintf (stderr,"WRITING FRAME %d %g %g\n", dst_frame_counter,nper,iper); black(yuv_fdata,inStrInfo); nper = 0; iper = 0; dst_frame_counter++; } else { // Do not read any new frames if we have written a frame if (interlaced != Y4M_ILACE_NONE) { // read the interlace frame if the end of the source frame is before the end of the destination if ((src_iframe_counter+1.5) * srcfl <= (dst_frame_counter + 1.5) * dstfl) { if (src_frame_counter > src_iframe_counter) { chromacpy (yuv_idata,yuv_data,inStrInfo); } else { y4m_fini_frame_info( &in_frame ); y4m_init_frame_info( &in_frame ); read_error_code = y4m_read_frame(fdIn, inStrInfo,&in_frame,yuv_idata ); // fprintf (stderr,"READING I FRAME %d\n", src_iframe_counter); } src_iframe_counter++ ; } } // read the frame if the end of the source frame is before the end of the destination if ((src_frame_counter+1.0) * srcfl <= (dst_frame_counter + 1.0) * dstfl) { if ((interlaced != Y4M_ILACE_NONE) && (src_frame_counter < src_iframe_counter)) { chromacpy (yuv_data,yuv_idata,inStrInfo); } else { y4m_fini_frame_info( &in_frame ); y4m_init_frame_info( &in_frame ); read_error_code = y4m_read_frame(fdIn, inStrInfo,&in_frame,yuv_data ); // fprintf (stderr,"READING P FRAME %d\n", src_frame_counter); } src_frame_counter++ ; } } } // Clean-up regardless an error happened or not y4m_fini_frame_info( &in_frame ); if (interlaced != Y4M_ILACE_NONE) { free( yuv_idata[0] ); free( yuv_idata[1] ); free( yuv_idata[2] ); } free( yuv_data[0] ); free( yuv_data[1] ); free( yuv_data[2] ); free( yuv_odata[0] ); free( yuv_odata[1] ); free( yuv_odata[2] ); free( yuv_fdata[0] ); free( yuv_fdata[1] ); free( yuv_fdata[2] ); if( read_error_code != Y4M_ERR_EOF ) mjpeg_error_exit1 ("Error reading from input stream!"); if( write_error_code != Y4M_OK ) mjpeg_error_exit1 ("Error writing output stream!"); } // ************************************************************************************* // MAIN // ************************************************************************************* int main (int argc, char *argv[]) { int verbose = 4; // LOG_ERROR ; int change_header_only = 0 ; int not_normalize = 0; int have_src_framerate = 0; int have_framerate = 0; int yuv_interlacing = Y4M_UNKNOWN; int fdIn = 0 ; int fdOut = 1 ; y4m_stream_info_t in_streaminfo, out_streaminfo ; y4m_ratio_t frame_rate, src_frame_rate ; const static char *legal_flags = "I:r:s:cnv:h"; int c ; while ((c = getopt (argc, argv, legal_flags)) != -1) { switch (c) { case 'v': verbose = atoi (optarg); if (verbose < 0 || verbose > 2) mjpeg_error_exit1 ("Verbose level must be [0..2]"); break; case 'I': switch (optarg[0]) { case 'p': yuv_interlacing = Y4M_ILACE_NONE; break; case 't': yuv_interlacing = Y4M_ILACE_TOP_FIRST; break; case 'b': yuv_interlacing = Y4M_ILACE_BOTTOM_FIRST; break; default: mjpeg_error("Unknown value for interlace: '%c'", optarg[0]); return -1; break; } break; case 'h': case '?': print_usage (argv); return 0 ; break; // New frame rate case 'r': if( Y4M_OK != y4m_parse_ratio(&frame_rate, optarg) ) mjpeg_error_exit1 ("Syntax for frame rate should be Numerator:Denominator"); mjpeg_warn( "New Frame rate %d:%d", frame_rate.n,frame_rate.d ); have_framerate =1 ; break; // Assumed frame rate for source (useful when the header contains an // invalid frame rate) case 's': if( Y4M_OK != y4m_parse_ratio(&src_frame_rate,optarg) ) mjpeg_error_exit1 ("Syntax for frame rate should be Numerator:Denominator"); have_src_framerate = 1; break ; // Only change header frame-rate, not the stream itself case 'c': change_header_only = 1 ; case 'n': not_normalize = 1; break; } } // mjpeg tools global initialisations mjpeg_default_handler_verbosity (verbose); // Initialize input streams y4m_init_stream_info (&in_streaminfo); y4m_init_stream_info (&out_streaminfo); // *************************************************************** // Get video stream informations (size, framerate, interlacing, aspect ratio). // The streaminfo structure is filled in // *************************************************************** // INPUT comes from stdin, we check for a correct file header if (y4m_read_stream_header (fdIn, &in_streaminfo) != Y4M_OK) mjpeg_error_exit1 ("Could'nt read YUV4MPEG header!"); // Prepare output stream if (!have_src_framerate) src_frame_rate = y4m_si_get_framerate( &in_streaminfo ); if (!have_framerate) frame_rate = src_frame_rate ; y4m_copy_stream_info( &out_streaminfo, &in_streaminfo ); optind = 0; // Information output mjpeg_info ("yuvafps (version " YUVFPS_VERSION ") is a linear interpolating frame resampling utility for yuv streams"); mjpeg_info ("(C) 2007 Mark Heath mjpeg1 at silicontrip dot org"); mjpeg_info ("yuvafps -h for help"); y4m_si_set_framerate( &out_streaminfo, frame_rate ); y4m_write_stream_header(fdOut,&out_streaminfo); if (yuv_interlacing != Y4M_UNKNOWN) y4m_si_set_interlace( &in_streaminfo, yuv_interlacing); if( change_header_only ) frame_rate = src_frame_rate ; /* in that function we do all the important work */ resample( fdIn,&in_streaminfo, src_frame_rate, fdOut,&out_streaminfo, frame_rate, not_normalize ); y4m_fini_stream_info (&in_streaminfo); y4m_fini_stream_info (&out_streaminfo); return 0; } /* * Local variables: * tab-width: 8 * indent-tabs-mode: nil * End: */