/* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % M M PPPP EEEEE GGGG % % MM MM P P E G % % M M M PPPP EEE G GG % % M M P E G G % % M M P EEEEE GGGG % % % % % % Read/Write MPEG Image Format. % % % % Software Design % % John Cristy % % July 1999 % % % % % % Copyright 1999-2007 ImageMagick Studio LLC, a non-profit organization % % dedicated to making software imaging solutions freely available. % % % % You may not use this file except in compliance with the License. You may % % obtain a copy of the License at % % % % http://www.imagemagick.org/script/license.php % % % % Unless required by applicable law or agreed to in writing, software % % distributed under the License is distributed on an "AS IS" BASIS, % % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. % % See the License for the specific language governing permissions and % % limitations under the License. % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % */ /* Include declarations. */ #include "magick/studio.h" #include "magick/blob.h" #include "magick/blob-private.h" #include "magick/constitute.h" #include "magick/delegate.h" #include "magick/exception.h" #include "magick/exception-private.h" #include "magick/geometry.h" #include "magick/image.h" #include "magick/image-private.h" #include "magick/layer.h" #include "magick/list.h" #include "magick/log.h" #include "magick/magick.h" #include "magick/memory_.h" #include "magick/resource_.h" #include "magick/quantum-private.h" #include "magick/static.h" #include "magick/string_.h" #include "magick/module.h" #include "magick/transform.h" #include "magick/utility.h" /* Forward declarations. */ static MagickBooleanType WriteMPEGImage(const ImageInfo *image_info,Image *image); /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % I s M P E G % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % IsMPEG() returns MagickTrue if the image format type, identified by the % magick string, is MPEG. % % The format of the IsMPEG method is: % % MagickBooleanType IsMPEG(const unsigned char *magick,const size_t length) % % A description of each parameter follows: % % o magick: This string is generally the first few bytes of an image file % or blob. % % o length: Specifies the length of the magick string. % */ static MagickBooleanType IsMPEG(const unsigned char *magick,const size_t length) { if (length < 4) return(MagickFalse); if (memcmp(magick,"\000\000\001\263",4) == 0) return(MagickTrue); return(MagickFalse); } /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % R e a d M P E G I m a g e % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % ReadMPEGImage() reads an binary file in the MPEG video stream format % and returns it. It allocates the memory necessary for the new Image % structure and returns a pointer to the new image. % % The format of the ReadMPEGImage method is: % % Image *ReadMPEGImage(const ImageInfo *image_info, % ExceptionInfo *exception) % % A description of each parameter follows: % % o image_info: The image info. % % o exception: return any errors or warnings in this structure. % */ static Image *ReadMPEGImage(const ImageInfo *image_info, ExceptionInfo *exception) { Image *image, *images; ImageInfo *read_info; MagickBooleanType status; register long i; /* Open image file. */ assert(image_info != (const ImageInfo *) NULL); assert(image_info->signature == MagickSignature); if (image_info->debug != MagickFalse) (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s", image_info->filename); assert(exception != (ExceptionInfo *) NULL); assert(exception->signature == MagickSignature); image=AllocateImage(image_info); status=OpenBlob(image_info,image,ReadBinaryBlobMode,exception); if (status == MagickFalse) { image=DestroyImageList(image); return((Image *) NULL); } CloseBlob(image); (void) DestroyImageList(image); /* Convert MPEG to PPM with delegate. */ image=AllocateImage(image_info); read_info=CloneImageInfo(image_info); (void) InvokeDelegate(read_info,image,"mpeg:decode",(char *) NULL,exception); image=DestroyImage(image); /* Read PPM files. */ images=NewImageList(); for (i=(long) read_info->scene; ; i++) { (void) FormatMagickString(read_info->filename,MaxTextExtent,"%s%ld.ppm", read_info->unique,i); if (IsAccessible(read_info->filename) == MagickFalse) break; image=ReadImage(read_info,exception); if (image == (Image *) NULL) break; (void) CopyMagickString(image->magick,image_info->magick,MaxTextExtent); image->scene=(unsigned long) i; AppendImageToList(&images,image); if (read_info->number_scenes != 0) if (i >= (long) (read_info->scene+read_info->number_scenes-1)) break; } /* Free resources. */ for (i=0; ; i++) { (void) FormatMagickString(read_info->filename,MaxTextExtent,"%s%ld.ppm", read_info->unique,i); if (IsAccessible(read_info->filename) == MagickFalse) break; (void) RelinquishUniqueFileResource(read_info->filename); } read_info=DestroyImageInfo(read_info); return(images); } /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % R e g i s t e r M P E G I m a g e % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % RegisterMPEGImage() adds attributes for the MPEG image format to % the list of supported formats. The attributes include the image format % tag, a method to read and/or write the format, whether the format % supports the saving of more than one frame to the same file or blob, % whether the format supports native in-memory I/O, and a brief % description of the format. % % The format of the RegisterMPEGImage method is: % % unsigned long RegisterMPEGImage(void) % */ ModuleExport unsigned long RegisterMPEGImage(void) { MagickInfo *entry; entry=SetMagickInfo("MPEG"); entry->decoder=(DecodeImageHandler *) ReadMPEGImage; entry->encoder=(EncodeImageHandler *) WriteMPEGImage; entry->magick=(IsImageFormatHandler *) IsMPEG; entry->blob_support=MagickFalse; entry->description=ConstantString("MPEG Video Stream"); entry->module=ConstantString("MPEG"); (void) RegisterMagickInfo(entry); entry=SetMagickInfo("MPG"); entry->decoder=(DecodeImageHandler *) ReadMPEGImage; entry->encoder=(EncodeImageHandler *) WriteMPEGImage; entry->magick=(IsImageFormatHandler *) IsMPEG; entry->blob_support=MagickFalse; entry->description=ConstantString("MPEG Video Stream"); entry->module=ConstantString("MPEG"); (void) RegisterMagickInfo(entry); entry=SetMagickInfo("M2V"); entry->decoder=(DecodeImageHandler *) ReadMPEGImage; entry->encoder=(EncodeImageHandler *) WriteMPEGImage; entry->magick=(IsImageFormatHandler *) IsMPEG; entry->blob_support=MagickFalse; entry->description=ConstantString("MPEG Video Stream"); entry->module=ConstantString("MPEG"); (void) RegisterMagickInfo(entry); return(MagickImageCoderSignature); } /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % U n r e g i s t e r M P E G I m a g e % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % UnregisterMPEGImage() removes format registrations made by the % BIM module from the list of supported formats. % % The format of the UnregisterBIMImage method is: % % UnregisterMPEGImage(void) % */ ModuleExport void UnregisterMPEGImage(void) { (void) UnregisterMagickInfo("M2V"); (void) UnregisterMagickInfo("MPEG"); (void) UnregisterMagickInfo("MPG"); } /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % W r i t e M P E G I m a g e % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % WriteMPEGImage() writes an image to a file in MPEG video stream format. % Lawrence Livermore National Laboratory (LLNL) contributed code to adjust % the MPEG parameters to correspond to the compression quality setting. % % The format of the WriteMPEGImage method is: % % MagickBooleanType WriteMPEGImage(const ImageInfo *image_info,Image *image) % % A description of each parameter follows. % % o image_info: The image info. % % o image: The image. % */ static inline double MagickMax(const double x,const double y) { if (x > y) return(x); return(y); } static inline double MagickMin(const double x,const double y) { if (x < y) return(x); return(y); } static MagickBooleanType WriteMPEGParameterFiles(const ImageInfo *image_info, Image *image,const char *basename) { char filename[MaxTextExtent]; double delay, q; FILE *file, *parameter_file; long quant, vertical_factor; MagickBooleanType mpeg; register Image *p; register long i; ssize_t count; static int q_matrix[]= { 8, 16, 19, 22, 26, 27, 29, 34, 16, 16, 22, 24, 27, 29, 34, 37, 19, 22, 26, 27, 29, 34, 34, 38, 22, 22, 26, 27, 29, 34, 37, 40, 22, 26, 27, 29, 32, 35, 40, 48, 26, 27, 29, 32, 35, 40, 48, 58, 26, 27, 29, 34, 38, 46, 56, 69, 27, 29, 35, 38, 46, 56, 69, 83 }; /* Write parameter file (see mpeg2encode documentation for details). */ file=MagickOpenStream(basename,"w"); if (file == (FILE *) NULL) return(MagickFalse); (void) fprintf(file,"MPEG\n"); /* comment */ (void) fprintf(file,"%s.%%d\n",image->filename); /* source frame file */ (void) fprintf(file,"-\n"); /* reconstructed frame file */ if (image->quality == UndefinedCompressionQuality) (void) fprintf(file,"-\n"); /* default intra quant matrix */ else { /* Write intra quant matrix file. */ (void) FormatMagickString(filename,MaxTextExtent,"%s.iqm",basename); (void) fprintf(file,"%s\n",filename); parameter_file=MagickOpenStream(filename,"w"); if (parameter_file == (FILE *) NULL) return(MagickFalse); if (image->quality >= 75) { q=MagickMax(2.0*(image->quality-75.0),1.0); for (i=0; i < 64; i++) { quant=(long) MagickMin(MagickMax(q_matrix[i]/q,1.0),255.0); (void) fprintf(parameter_file," %ld",quant); if ((i % 8) == 7) (void) fprintf(parameter_file,"\n"); } } else { q=MagickMax((75.0-image->quality)/8.0,1.0); for (i=0; i < 64; i++) { quant=(long) MagickMin(MagickMax(q*q_matrix[i]+0.5,1.0),255.0); (void) fprintf(parameter_file," %ld",quant); if ((i % 8) == 7) (void) fprintf(parameter_file,"\n"); } } (void) fclose(parameter_file); } if (image->quality == UndefinedCompressionQuality) (void) fprintf(file,"-\n"); /* default non intra quant matrix */ else { /* Write non intra quant matrix file. */ (void) FormatMagickString(filename,MaxTextExtent,"%s.niq",basename); (void) fprintf(file,"%s\n",filename); parameter_file=MagickOpenStream(filename,"w"); if (parameter_file == (FILE *) NULL) return(MagickFalse); q=MagickMin(MagickMax(66.0-(2*image->quality)/3.0,1.0),255); for (i=0; i < 64; i++) { (void) fprintf(parameter_file," %d",(int) q); if ((i % 8) == 7) (void) fprintf(parameter_file,"\n"); } (void) fclose(parameter_file); } (void) fprintf(file,"%s.log\n",basename); /* statistics log */ (void) fprintf(file,"1\n"); /* input picture file format */ count=0; for (p=image; p != (Image *) NULL; p=GetNextImageInList(p)) { delay=100.0*p->delay/MagickMax(1.0*p->ticks_per_second,1.0); count+=(ssize_t) MagickMax((1.0*delay+1.0)/3.0,1.0); } (void) fprintf(file,"%lu\n",(unsigned long) count); /* number of frames */ (void) fprintf(file,"0\n"); /* number of first frame */ (void) fprintf(file,"00:00:00:00\n"); /* timecode of first frame */ mpeg=LocaleCompare(image_info->magick,"M2V") != 0 ? MagickTrue : MagickFalse; if (image->quality > 98) (void) fprintf(file,"1\n"); else (void) fprintf(file,"%d\n",mpeg != MagickFalse ? 12 : 15); if (image->quality > 98) (void) fprintf(file,"1\n"); else (void) fprintf(file,"3\n"); (void) fprintf(file,"%d\n",mpeg != MagickFalse ? 1 : 0); /* ISO/IEC 11172-2 stream */ (void) fprintf(file,"0\n"); /* select frame picture coding */ (void) fprintf(file,"%lu\n",image->columns+ ((image->columns & 0x01) != 0 ? 1 : 0)); (void) fprintf(file,"%lu\n",image->rows+((image->rows & 0x01) != 0 ? 1 : 0)); (void) fprintf(file,"%d\n",mpeg != MagickFalse ? 8 : 2); /* aspect ratio */ (void) fprintf(file,"%d\n",mpeg != MagickFalse ? 3 : 5); /* frame rate code */ (void) fprintf(file,"%g\n",mpeg != MagickFalse ? 1152000.0 : 5000000.0); /* bit rate */ (void) fprintf(file,"%d\n",mpeg != MagickFalse ? 20 : 112); /* vbv buffer size */ (void) fprintf(file,"0\n"); /* low delay */ (void) fprintf(file,"%d\n",mpeg != MagickFalse ? 1 : 0); /* constrained parameter */ (void) fprintf(file,"%d\n",mpeg != MagickFalse ? 4 : 1); /* profile ID */ (void) fprintf(file,"%d\n",mpeg != MagickFalse ? 8 : 4); /* level ID */ (void) fprintf(file,"%d\n",mpeg != MagickFalse ? 1 : 0); /* progressive sequence */ vertical_factor=2; if (image_info->sampling_factor != (char *) NULL) { GeometryInfo geometry_info; long horizontal_factor; MagickStatusType flags; flags=ParseGeometry(image_info->sampling_factor,&geometry_info); horizontal_factor=(long) geometry_info.rho; vertical_factor=(long) geometry_info.sigma; if ((flags & SigmaValue) == 0) vertical_factor=horizontal_factor; if (mpeg != MagickFalse) { if ((horizontal_factor != 2) || (vertical_factor != 2)) { (void) fclose(file); return(MagickFalse); } } else if ((horizontal_factor != 2) || ((vertical_factor != 1) && (vertical_factor != 2))) { (void) fclose(file); return(MagickFalse); } } (void) fprintf(file,"%d\n",vertical_factor == 2 ? 1 : 2); /* chroma format */ (void) fprintf(file,"%d\n",mpeg != MagickFalse ? 1 : 2); /* video format */ (void) fprintf(file,"5\n"); /* color primaries */ (void) fprintf(file,"5\n"); /* transfer characteristics */ (void) fprintf(file,"%d\n",mpeg != MagickFalse ? 5 : 4); /* matrix coefficients */ (void) fprintf(file,"%lu\n",image->columns+ ((image->columns & 0x01) != 0 ? 1 : 0)); (void) fprintf(file,"%lu\n",image->rows+((image->rows & 0x01) != 0 ? 1 : 0)); (void) fprintf(file,"0\n"); /* intra dc precision */ (void) fprintf(file,"%d\n",mpeg != MagickFalse ? 0 : 1); /* top field */ (void) fprintf(file,"%d %d %d\n",mpeg != MagickFalse ? 1 : 0, mpeg != MagickFalse ? 1 : 0, mpeg != MagickFalse ? 1 : 0); (void) fprintf(file,"0 0 0\n"); /* concealment motion vector */ (void) fprintf(file,"%d %d %d\n",mpeg != MagickFalse ? 0 : 1, mpeg != MagickFalse ? 0 : 1,mpeg != MagickFalse ? 0 : 1); (void) fprintf(file,"%d 0 0\n",mpeg != MagickFalse ? 0 : 1); /* intra vlc format */ (void) fprintf(file,"0 0 0\n"); /* alternate scan */ (void) fprintf(file,"0\n"); /* repeat first field */ (void) fprintf(file,"%d\n",mpeg != MagickFalse ? 1 : 0); /* progressive frame */ (void) fprintf(file,"0\n"); /* intra slice refresh period */ (void) fprintf(file,"0\n"); /* reaction parameter */ (void) fprintf(file,"0\n"); /* initial average activity */ (void) fprintf(file,"0\n"); (void) fprintf(file,"0\n"); (void) fprintf(file,"0\n"); (void) fprintf(file,"0\n"); (void) fprintf(file,"0\n"); (void) fprintf(file,"0\n"); (void) fprintf(file,"2 2 11 11\n"); (void) fprintf(file,"1 1 3 3\n"); (void) fprintf(file,"1 1 7 7\n"); (void) fprintf(file,"1 1 7 7\n"); (void) fprintf(file,"1 1 3 3\n"); (void) fclose(file); return(MagickTrue); } static MagickBooleanType WriteMPEGImage(const ImageInfo *image_info, Image *image) { char basename[MaxTextExtent], filename[MaxTextExtent]; double delay; Image *coalesce_image, *next_image; ImageInfo *write_info; int file; MagickBooleanType status; register Image *p; register long i; size_t length; unsigned char *blob; unsigned long count, scene; /* Open output image file. */ assert(image_info != (const ImageInfo *) NULL); assert(image_info->signature == MagickSignature); assert(image != (Image *) NULL); assert(image->signature == MagickSignature); if (image->debug != MagickFalse) (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); status=OpenBlob(image_info,image,WriteBinaryBlobMode,&image->exception); if (status == MagickFalse) return(status); CloseBlob(image); /* Determine if the sequence of images have identical page info. */ coalesce_image=image; for (next_image=image; next_image != (Image *) NULL; ) { if ((image->columns != next_image->columns) || (image->rows != next_image->rows)) break; if ((image->page.x != next_image->page.x) || (image->page.y != next_image->page.y)) break; next_image=GetNextImageInList(next_image); } if (next_image != (Image *) NULL) { coalesce_image=CoalesceImages(image,&image->exception); if (coalesce_image == (Image *) NULL) return(MagickFalse); } /* Write YUV files. */ file=AcquireUniqueFileResource(basename); if (file != -1) file=close(file)-1; (void) FormatMagickString(coalesce_image->filename,MaxTextExtent,"%s", basename); write_info=CloneImageInfo(image_info); status=WriteMPEGParameterFiles(write_info,coalesce_image,basename); if (status == MagickFalse) { if (coalesce_image != image) coalesce_image=DestroyImage(coalesce_image); (void) RelinquishUniqueFileResource(basename); if (image->quality != UndefinedCompressionQuality) { (void) FormatMagickString(filename,MaxTextExtent,"%s.iqm", basename); (void) RelinquishUniqueFileResource(filename); (void) FormatMagickString(filename,MaxTextExtent,"%s.niq", basename); (void) RelinquishUniqueFileResource(filename); } ThrowWriterException(CoderError,"UnableToWriteMPEGParameters"); } count=0; write_info->interlace=PlaneInterlace; for (p=coalesce_image; p != (Image *) NULL; p=GetNextImageInList(p)) { char previous_image[MaxTextExtent]; blob=(unsigned char *) NULL; length=0; scene=p->scene; delay=100.0*p->delay/MagickMax(1.0*p->ticks_per_second,1.0); for (i=0; i < (long) MagickMax((1.0*delay+1.0)/3.0,1.0); i++) { p->scene=count; count++; status=MagickFalse; switch (i) { case 0: { Image *frame; (void) FormatMagickString(p->filename,MaxTextExtent,"%s.%lu.yuv", basename,p->scene); (void) FormatMagickString(filename,MaxTextExtent,"%s.%lu.yuv", basename,p->scene); (void) FormatMagickString(previous_image,MaxTextExtent, "%s.%lu.yuv",basename,p->scene); frame=CloneImage(p,0,0,MagickTrue,&p->exception); if (frame == (Image *) NULL) break; status=WriteImage(write_info,frame); frame=DestroyImage(frame); break; } case 1: { blob=(unsigned char *) FileToBlob(previous_image,~0UL,&length,&image->exception); } default: { (void) FormatMagickString(filename,MaxTextExtent,"%s.%lu.yuv", basename,p->scene); if (length > 0) status=BlobToFile(filename,blob,length,&image->exception); break; } } if (image->debug != MagickFalse) { if (status != MagickFalse) (void) LogMagickEvent(CoderEvent,GetMagickModule(), "%lu. Wrote YUV file for scene %lu:",i,p->scene); else (void) LogMagickEvent(CoderEvent,GetMagickModule(), "%lu. Failed to write YUV file for scene %lu:",i,p->scene); (void) LogMagickEvent(CoderEvent,GetMagickModule(),"%s", filename); } } p->scene=scene; if (blob != (unsigned char *) NULL) blob=(unsigned char *) RelinquishMagickMemory(blob); if (status == MagickFalse) break; } /* Convert YUV to MPEG. */ (void) CopyMagickString(coalesce_image->filename,basename,MaxTextExtent); status=InvokeDelegate(write_info,coalesce_image,(char *) NULL,"mpeg:encode", &image->exception); write_info=DestroyImageInfo(write_info); /* Free resources. */ count=0; for (p=coalesce_image; p != (Image *) NULL; p=GetNextImageInList(p)) { delay=100.0*p->delay/MagickMax(1.0*p->ticks_per_second,1.0); for (i=0; i < (long) MagickMax((1.0*delay+1.0)/3.0,1.0); i++) { (void) FormatMagickString(p->filename,MaxTextExtent,"%s.%lu.yuv", basename,count++); (void) RelinquishUniqueFileResource(p->filename); } (void) CopyMagickString(p->filename,image_info->filename,MaxTextExtent); } (void) RelinquishUniqueFileResource(basename); (void) FormatMagickString(filename,MaxTextExtent,"%s.iqm",basename); (void) RelinquishUniqueFileResource(filename); (void) FormatMagickString(filename,MaxTextExtent,"%s.niq",basename); (void) RelinquishUniqueFileResource(filename); (void) FormatMagickString(filename,MaxTextExtent,"%s.log",basename); (void) RelinquishUniqueFileResource(filename); if (coalesce_image != image) coalesce_image=DestroyImage(coalesce_image); if (image->debug != MagickFalse) (void) LogMagickEvent(CoderEvent,GetMagickModule(),"exit"); return(status); }